1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
|
-----------------------------------------------------------------------------
-- |
-- Module : Numeric.Search.Integer
-- Copyright : (c) Ross Paterson 2008
-- License : BSD-style
-- 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
|