1 | {-
|
2 |
|
3 | Pearl 1: Smallest Free Number
|
4 |
|
5 | Problem:
|
6 | Assume X is a list of distinct Natural numbers (non-negative integers), in no particular order.
|
7 | Find the smallest number not in the list.
|
8 |
|
9 | We will show two algorithms that do it in LINEAR time.
|
10 | On first sight, it is not clear such exist. After all, sorting a list is no linear time.
|
11 | But there's a trick.
|
12 |
|
13 | The key trick: not every number in the range [0..length xs] can be in xs!
|
14 | Thus, the smallest number NOT in xs is then the smallest number not in
|
15 | filter (<=n) xs, where n=length xs.
|
16 |
|
17 | The two programs:
|
18 | Array based: builds a checklist of the numbers present in filter (<=n) xs.
|
19 |
|
20 | Divide and conquer:
|
21 | express minfree(xs++ys) in terms of minfree xs and minfree ys.
|
22 |
|
23 | -}
|
24 | module P1 where
|
25 |
|
26 | import Data.Array (Array, elems, accumArray)
|
27 | import Data.List (partition)
|
28 | testX = [8,23,9,0,12,11,1,10,13,7,41,21,5,17,3,19,2,6] :: [Int]
|
29 |
|
30 | type Nat = Int -- Just for ease of reading things here. Not correct!!
|
31 |
|
32 | -- Brute force approach
|
33 | -- Requires O(N^2) steps
|
34 | -- (see implemntation of (\\) )
|
35 | minfree :: [Nat] -> Nat
|
36 | minfree xs = head ([0..] \\ xs)
|
37 |
|
38 | -- where us (\\) vs denotes the list of those elements in us remaining after removing
|
39 | -- any elements in vs.
|
40 | (\\) :: Eq a => [a] -> [a] -> [a]
|
41 | us \\ vs = filter ( `notElem` vs) us
|
42 |
|
43 | -- Array based method: Build the checklist as an array
|
44 | -- We will use the 'tricky' function accumArray, which will enable us to do it in linear time.
|
45 | -- accumArray: Constructs an immutable array from a list of associations.
|
46 | -- Unlike array, the same index is allowed to occur multiple times in the list of associations;
|
47 | -- an accumulating function is used to combine the values of elements with the same index.
|
48 |
|
49 | checkList :: [Nat] -> Array Nat Bool
|
50 | checkList xs = accumArray --
|
51 | (||) -- accumlating function. not realy used, as no duplicates!
|
52 | False -- inital entry for each index
|
53 | (0,n) -- (lower/upper) indices of new array
|
54 | (zip (filter (<= n) xs) (repeat True) ) -- index-value pairs
|
55 | where
|
56 | n = length xs
|
57 | -- Just for fun: Another use of accumArray: Sorting array in LINEAR time, if we know all
|
58 | -- values lie between (0,n)
|
59 | -- in our case, we can then look for the first '0' in the resulting array
|
60 |
|
61 | countList :: [Nat] -> Array Nat Nat
|
62 | countList xs = accumArray (+) 0 (0,n) ( zip xs (repeat 1))
|
63 | where
|
64 | n = length xs
|
65 |
|
66 | -- Once we have the array, finding the minimum is simple:
|
67 | -- (elems converts array into a list)
|
68 | search :: Array Nat Bool -> Nat
|
69 | search = length.takeWhile id.elems
|
70 |
|
71 | -- Divide and conquer
|
72 | minFree xs = minFrom 0 (length xs,xs)
|
73 | minFrom a (n,xs) | n==0 = a
|
74 | | m==b-a = minFrom b (n-m,vs) -- if us has exactly the number of spots,
|
75 | -- and since there's no repetiion, it means
|
76 | -- the gap have to be in the OTHER series:
|
77 | -- so we continue the search with vs
|
78 | | otherwise = minFrom a (m,us) -- if us had the gap ==> keep searching there!
|
79 | where
|
80 | (us,vs) = partition (<b) xs -- partitioning to 'two halves'
|
81 | b = a + 1 + n `div` 2 -- b is the middle of [a..n]
|
82 | m = length us
|
83 |
|
84 | -- Main
|
85 |
|
86 | main = do
|
87 | putStr "Brute force : "
|
88 | print $ minfree testX
|
89 | putStr "Array method : "
|
90 | print $ search $ checkList testX
|
91 | putStr "Divide and Conquer: "
|
92 | print $ minFree testX
|