1 
{-
   2 
 
   3 
Pearl 4: Selection problem
   4 
 
   5 
Definitions:
   6 
X and Y two finite disjoint sets, sorted.
   7 
 
   8 
Problem:
   9 
Find the k-smallest elemnt in the set (X union Y)
  10 
 
  11 
Solution:
  12 
You can do it of course in linear time O(K).
  13 
But it turns out you can do faster by divide and conquer.
  14 
The trick is to do the merge-decisions in the right way.
  15 
 
  16 
We then also use arrays rather than lists, to get efficency in time.
  17 
 
  18 
<== And this is where it seems the book messed up on indices. Corrected in my code.
  19 
 
  20 
I left all the debugging and printing in there, so one can see the equivalence between
  21 
Array-indices and Lists.
  22 
 
  23 
-}
  24 
module P4 where
  25 
 
  26 
import Data.Array   (Array, listArray, bounds, (!))
  27 
import Debug.Trace
  28 
 
  29 
-- Test function(s)
  30 
{-
  31 
x = [7,14..28]
  32 
y = [5,10..35]
  33 
k = 4 
  34 
-}
  35 
y = [7,14..28]
  36 
x = [5,10..35]
  37 
--k = 4 
  38 
k = 7
  39 
 
  40 
xa = listArray (0,length x-1) x
  41 
ya = listArray (0,length y-1) y
  42 
 
  43 
-- Brute force
  44 
-- Requires (z+1)^2 evaluations of f
  45 
smallest1             ::     Ord a => Int -> ([a],[a]) -> a
  46 
smallest1 k (xs,ys)  =     union (xs,ys) !!k
  47 
 
  48 
union (xs,[])        =     xs
  49 
union ([],ys)        =     ys
  50 
union (x:xs,y:ys)    |    x < y     = x : union(xs,y:ys)
  51 
                    |     x > y     = y : union(x:xs,ys)
  52 
 
  53 
 
  54 
 
  55 
-- Divide and Conquer
  56 
smallest2 k ([],ws)    = ws !! k
  57 
smallest2 k (zs,[])    = zs !! k
  58 
smallest2 k (zs,ws)    =
  59 
    trace  ("zs=" ++ show zs ++
  60 
    "  and  ws=" ++ show ws  ++
  61 
    " and k=" ++ show k ++ "\n" ++
  62 
    " and (p,q)=" ++ show (p,q) ++
  63 
    "and (a,b)=" ++ show(a,b) )$
  64 
    case (a < b, k <= p+q) of
  65 
        (True,True)   -> trace "--> (1)" $ smallest2 k (zs,us)
  66 
        (True,False-> trace "--> (2)" $ smallest2 (k-p-1) (ys,ws)
  67 
        (False,True-> trace "--> (3)" $ smallest2 k (xs,ws)
  68 
        (False,False) -> trace "--> (4)" $ smallest2 (k-q-1) (zs,vs)
  69 
    where
  70 
        p               = (length zs) `div` 2
  71 
        q               = (length ws) `div` 2
  72 
        (xs,a:ys)      = splitAt p zs
  73 
        (us,b:vs)      = splitAt q ws
  74 
 
  75 
 
  76 
-- Divide and Conquer, using arrays
  77 
 
  78 
smallest3             ::     Ord a => Show a => Int -> (Array Int a, Array Int a) -> a
  79 
smallest3 k (xa,ya)    =     search k (0,m+1) (0,n+1)
  80 
                        where
  81 
                            (0,m) = bounds xa
  82 
                            (0,n) = bounds ya
  83 
                            -- The below is actually writing smallest2, but using array notation!
  84 
                            search k (lx,rx) (ly,ry)
  85 
                                | lx==rx     = ya ! ly     -- x = []
  86 
                                | ly==ry     = xa ! lx     -- y = []
  87 
                                | otherwise    =     trace  ("(lx,rx)=" ++ show (lx,rx) ++
  88 
                                                "  and  (ly,ry)=" ++ show (ly,ry)  ++
  89 
                                                " and k=" ++ show k ++ "\n" ++
  90 
                                                " and (mx,my)=" ++ show (mx,my) ++
  91 
                                                " and (mx',my')=" ++ show (mx',my') ++
  92 
                                                "and (xa!mx,ya!my)=" ++ show(xa!mx,ya!my) )$
  93 
                                                case ( (xa!mx) < (ya!my), k <= mx'+my') of
  94 
                                                (True,True)   -> trace "--> (1)" $ search k (lx,rx) (ly,my)
  95 
                                                (True,False-> trace "--> (2)" $ search (k-mx'-1) (mx+1,rx) (ly,ry)
  96 
                                                (False,True-> trace "--> (3)" $ search k (lx,mx) (ly,ry)
  97 
                                                (False,False) -> trace "--> (4)" $ search (k-my'-1) (lx,rx) (my+1,ry)
  98 
                                            where
  99 
                                                mx               = (lx + rx) `div` 2
 100 
                                                my               = (ly + ry) `div` 2
 101 
                                                mx'           = (rx - lx) `div` 2
 102 
                                                my'           = (ry - ly) `div` 2
 103 
 
 104 
-- Main
 105 
 
 106 
main = do
 107 
    print $ "x=" ++ (show x)
 108 
    print $ "y=" ++ (show y)
 109 
    print $ "union (x,y)=" ++ (show $ union (x,y))
 110 
    print $ "Looking for element : " ++ (show k) ++ " (remember, indexing starts from 0)"
 111 
    putStr "Brute force                   : "
 112 
    print $ smallest1 k (x,y)
 113 
    putStrLn "Divide-and-Conquer (w/ lists) : "
 114 
    print $ smallest2 k (x,y)
 115 
    putStr "Divide-and-Conquer (w/ arrays): "
 116 
    print $ smallest3 k (xa,ya)