```-----------------------------------------------------------------------------
-- |
-- Module      :  Numeric.Search.Integer
-- Copyright   :  (c) Ross Paterson 2008
-- Maintainer  :  ross@soi.city.ac.uk
-- Stability   :  experimental
-- Portability :  portable
--
-- Searching unbounded intervals of integers for the boundary of an
-- upward-closed set, using a combination of exponential and binary
-- search.
--
-----------------------------------------------------------------------------

module Numeric.Search.Integer (
-- * One-dimensional searches
search, searchFrom, searchTo,
-- * Two-dimensional searches
search2) where

import Data.Maybe (fromMaybe)

-- | /O(log(abs n))/.
-- Search the integers.
--
-- If @p@ is an upward-closed predicate, @search p@ returns the least
-- @n@ satisfying @p@.  If no such @n@ exists, either because no integer
-- satisfies @p@ or all do, @search p@ does not terminate.
--
-- For example, the following function computes discrete logarithms (base 2):
--
-- > discreteLog :: Integer -> Integer
-- > discreteLog n = search (\ k -> 2^k <= n)
--
search :: (Integer -> Bool) -> Integer
search p = fromMaybe (searchFrom p 1) (searchTo p 0)

-- | /O(log(n-l))/.
-- Search the integers from a given lower bound.
--
-- If @p@ is an upward-closed predicate,
-- @searchFrom p l = 'search' (\\ i -> i >= l && p i)@.
-- If no such @n@ exists (because no integer satisfies @p@),
-- @searchFrom p@ does not terminate.
searchFrom :: (Integer -> Bool) -> Integer -> Integer
searchFrom p = search_from 1
where search_from step l
| p l' = searchIntegerRange p l (l'-1)
| otherwise = search_from (2*step) (l'+1)
where l' = l + step

-- | /O(log(h-n))/.
-- Search the integers up to a given upper bound.
--
-- If @p@ is an upward-closed predicate, @searchTo p h == 'Just' n@
-- if and only if @n@ is the least number @n <= h@ satisfying @p@.
searchTo :: (Integer -> Bool) -> Integer -> Maybe Integer
searchTo p h0
| p h0 = Just (search_to 1 h0)
| otherwise = Nothing
where search_to step h                -- @step >= 1 && p h@
| p h' = search_to (2*step) h'
| otherwise = searchSafeRange p (h'+1) h
where h' = h - step

-- | /O(m log(n\/m))/.
-- Two-dimensional search, using an algorithm due described in
--
-- * Richard Bird, /Saddleback search: a lesson in algorithm design/,
--   in /Mathematics of Program Construction/ 2006,
--   Springer LNCS vol. 4014, pp82-89.
--
-- If @p@ is closed upwards in each argument on non-negative integers,
-- @search2 p@ returns the minimal non-negative pairs satisfying @p@,
-- listed in order of increasing x-coordinate.
--
-- /m/ and /n/ refer to the smaller and larger dimensions of the
-- rectangle containing the boundary.
--
-- For example,
--
-- > search2 (\ x y -> x^2 + y^2 >= 25)  ==  [(0,5),(3,4),(4,3),(5,0)]
--
search2 :: (Integer -> Integer -> Bool) -> [(Integer,Integer)]
search2 p = search2Rect p 0 0 hx hy []
where
hx = searchFrom (\ x -> p x 0) 0
hy = searchFrom (\ y -> p 0 y) 0

search2Rect :: (Integer -> Integer -> Bool) ->
Integer -> Integer -> Integer -> Integer ->
[(Integer,Integer)] -> [(Integer,Integer)]
search2Rect p lx ly hx hy
| lx > hx || ly > hy = id
| lx == hx && ly == hy = if p lx ly then ((lx, ly) :) else id
| hx-lx > hy-ly =
let        mx = (lx+hx) `div` 2
my = searchIntegerRange (\ y -> p mx y) ly hy
in search2Rect p lx my mx hy . search2Rect p (mx+1) ly hx (my-1)
| otherwise =
let        mx = searchIntegerRange (\ x -> p x my) lx hx
my = (ly+hy) `div` 2
in search2Rect p lx (my+1) (mx-1) hy . search2Rect p mx ly hx my

-- | Search a bounded interval of integers.
--
-- If @p@ is an upward-closed predicate,
--
-- > searchIntegerRange p l h = 'search' (\ i -> i >= l && p i || i > h)
--
-- Cost: /O(log(h-l))/ calls to @p@.
searchIntegerRange :: (Integer -> Bool) -> Integer -> Integer -> Integer
searchIntegerRange p l h
| h < l = h+1
| p m = searchIntegerRange p l (m-1)
| otherwise = searchIntegerRange p (m+1) h
where m = (l+h) `div` 2

-- | Like 'search', but assuming @l <= h && p h@.
searchSafeRange :: (Integer -> Bool) -> Integer -> Integer -> Integer
searchSafeRange p l h
| l == h = l
| p m = searchSafeRange p l m
| otherwise = searchSafeRange p (m+1) h
where m = (l + h) `div` 2        -- If l < h, then l <= m < h
```