ac-library-hs-1.5.3.1: src/AtCoder/Extra/Bisect.hs
{-# LANGUAGE LambdaCase #-}
-- | Bisection methods and binary search functions.
--
-- __Known bug__: `maxRight` and their variants have a bug ([#140](https://github.com/toyboot4e/ac-library-hs/issues/140)),
-- so don't use them.
--
-- ==== __Example__
-- Perform index compression with `lowerBound`:
--
-- >>> import AtCoder.Extra.Bisect
-- >>> import Data.Vector.Algorithms.Intro qualified as VAI
-- >>> import Data.Vector.Unboxed qualified as VU
-- >>> let xs = VU.fromList ([0, 20, 10, 40, 30] :: [Int])
-- >>> let dict = VU.uniq $ VU.modify VAI.sort xs
-- >>> VU.map (lowerBound dict) xs
-- [0,2,1,4,3]
--
-- @since 1.3.0.0
module AtCoder.Extra.Bisect
( -- * C++-like binary searches
lowerBound,
lowerBoundIn,
upperBound,
upperBoundIn,
-- * Generic bisection methods
--
-- __Known bug_:_ `maxRight` and their variants have a bug ([#140](https://github.com/toyboot4e/ac-library-hs/issues/140)),
-- so don't use them.
maxRight,
maxRightM,
minLeft,
minLeftM,
)
where
import AtCoder.Internal.Assert qualified as ACIA
import Data.Functor.Identity
import Data.Vector.Generic qualified as VG
import GHC.Stack (HasCallStack)
-- | \(O(\log n)\) Returns the maximum \(r\) where \(x_i \lt x_{ref}\) holds for \(i \in [0, r)\).
--
-- @
-- Y Y Y Y Y N N N N N Y: x_i < x_ref
-- --------- *---------> x N: not Y
-- R R: the right boundary point returned
-- @
--
-- ==== __Example__
-- >>> import Data.Vector.Unboxed qualified as VU
-- >>> let xs = VU.fromList [1, 1, 2, 2, 4, 4]
-- >>> lowerBound xs 1
-- 0
--
-- >>> lowerBound xs 2
-- 2
--
-- >>> lowerBound xs 3
-- 4
--
-- >>> lowerBound xs 4
-- 4
--
-- >>> lowerBound xs 5
-- 6
--
-- @since 1.3.0.0
{-# INLINE lowerBound #-}
lowerBound :: (HasCallStack, VG.Vector v a, Ord a) => v a -> a -> Int
lowerBound vec = lowerBoundIn 0 (VG.length vec) vec
-- | \(O(\log n)\) Computes the `lowerBound` for a slice of a vector within the interval \([l, r)\).
--
-- ==== Constraints
-- - \(0 \le l \le r \le n\)
--
-- ==== __Example__
-- >>> import Data.Vector.Unboxed qualified as VU
-- >>> let xs = VU.fromList [10, 10, 20, 20, 40, 40]
-- >>> -- *---*---*
-- >>> lowerBoundIn 2 5 xs 10
-- 2
--
-- >>> lowerBoundIn 2 5 xs 20
-- 2
--
-- >>> lowerBoundIn 2 5 xs 30
-- 4
--
-- >>> lowerBoundIn 2 5 xs 40
-- 4
--
-- >>> lowerBoundIn 2 5 xs 50
-- 5
--
-- @since 1.3.0.0
{-# INLINE lowerBoundIn #-}
lowerBoundIn :: (HasCallStack, VG.Vector v a, Ord a) => Int -> Int -> v a -> a -> Int
lowerBoundIn l r vec target = maxRight l r $ \i -> vec VG.! i < target
where
!_ = ACIA.checkIntervalBounded "AtCoder.Extra.Bisect.lowerBoundIn" l r $ VG.length vec
-- | \(O(\log n)\) Returns the maximum \(r\) where \(x_i \le x_{ref}\) holds for \(i \in [0, r)\).
--
-- @
-- Y Y Y Y Y N N N N N Y: x_i <= x_ref
-- --------- *---------> x N: not Y
-- R R: the right boundary point returned
-- @
--
-- ==== __Example__
-- >>> import Data.Vector.Unboxed qualified as VU
-- >>> let xs = VU.fromList [10, 10, 20, 20, 40, 40]
-- >>> upperBound xs 0
-- 0
--
-- >>> upperBound xs 10
-- 2
--
-- >>> upperBound xs 20
-- 4
--
-- >>> upperBound xs 30
-- 4
--
-- >>> upperBound xs 39
-- 4
--
-- >>> upperBound xs 40
-- 6
--
-- @since 1.3.0.0
{-# INLINE upperBound #-}
upperBound :: (HasCallStack, VG.Vector v a, Ord a) => v a -> a -> Int
upperBound vec = upperBoundIn 0 (VG.length vec) vec
-- | \(O(\log n)\) Computes the `upperBound` for a slice of a vector within the interval \([l, r)\).
--
-- ==== Constraints
-- - \(0 \le l \le r \le n\)
--
-- ==== __Example__
-- >>> import Data.Vector.Unboxed qualified as VU
-- >>> let xs = VU.fromList [10, 10, 20, 20, 40, 40]
-- >>> -- *---*---*
-- >>> upperBoundIn 2 5 xs 0
-- 2
--
-- >>> upperBoundIn 2 5 xs 10
-- 2
--
-- >>> upperBoundIn 2 5 xs 20
-- 4
--
-- >>> upperBoundIn 2 5 xs 30
-- 4
--
-- >>> upperBoundIn 2 5 xs 40
-- 5
--
-- >>> upperBoundIn 2 5 xs 50
-- 5
--
-- @since 1.3.0.0
{-# INLINE upperBoundIn #-}
upperBoundIn :: (HasCallStack, VG.Vector v a, Ord a) => Int -> Int -> v a -> a -> Int
upperBoundIn l r vec target = maxRight l r $ \i -> vec VG.! i <= target
where
!_ = ACIA.checkIntervalBounded "AtCoder.Extra.Bisect.upperBoundIn" l r $ VG.length vec
-- | \(O(\log n)\) Applies the bisection method on a half-open interval \([l, r)\) and returns the
-- right boundary point.
--
-- @
-- Y Y Y Y Y N N N N N Y: p(i) returns `true`
-- --------- *---------> x N: not Y
-- R R: the right boundary point returned
-- @
--
-- __Known bug__: user function \(p\) takes __closed intervals__ \([l, r]\).
--
-- ==== __Example__
-- >>> import Data.Vector.Unboxed qualified as VU
-- >>> let xs = VU.fromList [10, 10, 20, 20, 30, 30]
-- >>> let n = VU.length xs
-- >>> maxRight 0 n ((<= 20) . (xs VU.!))
-- 4
--
-- >>> maxRight 0 n ((<= 0) . (xs VU.!))
-- 0
--
-- >>> maxRight 0 n ((<= 100) . (xs VU.!))
-- 6
--
-- >>> maxRight 0 3 ((<= 20) . (xs VU.!))
-- 3
--
-- @since 1.3.0.0
{-# INLINE maxRight #-}
maxRight ::
(HasCallStack) =>
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(p\)
(Int -> Bool) ->
-- | Maximum \(r' (r' \le r)\) where \(p(i)\) holds for \(i \in [l, r')\).
Int
maxRight l r p = runIdentity $ maxRightM l r (pure . p)
-- | \(O(\log n)\) Monadic variant of `maxRight`.
--
-- __Known bug__: user function \(p\) takes __closed intervals__ \([l, r]\).
--
-- @since 1.3.0.0
{-# INLINE maxRightM #-}
maxRightM :: (HasCallStack, Monad m) => Int -> Int -> (Int -> m Bool) -> m Int
maxRightM l0 r0 p = bisectImpl (l0 - 1) r0 p
where
!_ = ACIA.checkInterval "AtCoder.Extra.Bisect.maxRightM" l0 r0
-- | \(O(\log n)\) Applies the bisection method on a half-open interval \([l, r)\) and returns the
-- left boundary point.
--
-- @
-- N N N N N Y Y Y Y Y Y: p(i) returns `true`
-- --------* ----------> x N: not Y
-- L L: the left boundary point returned
-- @
--
-- __Known bug__: user function \(p\) takes __closed intervals__ \([l, r]\).
--
-- ==== __Example__
-- >>> import Data.Vector.Unboxed qualified as VU
-- >>> let xs = VU.fromList [10, 10, 20, 20, 30, 30]
-- >>> let n = VU.length xs
-- >>> minLeft 0 n ((>= 20) . (xs VU.!))
-- 2
--
-- >>> minLeft 0 n ((>= 0) . (xs VU.!))
-- 0
--
-- >>> minLeft 0 n ((>= 100) . (xs VU.!))
-- 6
--
-- @since 1.3.0.0
{-# INLINE minLeft #-}
minLeft ::
(HasCallStack) =>
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(p\)
(Int -> Bool) ->
-- | Minimum \(l' (l' \ge l)\) where \(p(i)\) holds for \(i \in [l', r)\)
Int
minLeft l r p = runIdentity $ minLeftM l r (pure . p)
-- | \(O(\log n)\) Monadic variant of `minLeft`.
--
-- __Known bug__: user function \(p\) takes __closed intervals__ \([l, r]\).
--
-- @since 1.3.0.0
{-# INLINE minLeftM #-}
minLeftM :: (HasCallStack, Monad m) => Int -> Int -> (Int -> m Bool) -> m Int
minLeftM l r p = (+ 1) <$> bisectImpl r (l - 1) p
where
!_ = ACIA.checkInterval "AtCoder.Extra.Bisect.minLeftM" l r
-- | Takes an open interval (l, r) or (r, l).
{-# INLINE bisectImpl #-}
bisectImpl :: (HasCallStack, Monad m) => Int -> Int -> (Int -> m Bool) -> m Int
bisectImpl l0 r0 p = inner l0 r0
where
inner l r
| abs (r - l) <= 1 = pure r
| otherwise =
p mid >>= \case
True -> inner mid r
False -> inner l mid
where
mid = (l + r) `div` 2