Given N > 0
and M > 0
, I want to enumerate all (x, y) pairs such that 1 <= x <= N and 1 <= y <= M in descending order of (x * y).
An
In Haskell it produces the output immediately. Here's an illustration:
-------
-*------
-**------
-***------
-****------
-*****------
-******------
-*******------
Each starred point produces both (x,y) and (y,x). The algorithm "eats into" this thing from the top right corner, comparing the top elements in each column. The length of the frontier is never more than N
(we assume N >= M
).
enuNM n m | n= M
enuNM n m = let
b = [ [ (x*y,(x,y)) | y<- [m,m-1..1]] | x<-[n,n-1..m+1]]
a = [ (x*x,(x,x)) :
concat [ [(z,(x,y)),(z,(y,x))] -- two symmetrical pairs,
| y<- [x-1,x-2..1] -- below and above the diagonal
, let z=x*y ] | x<-[m,m-1..1]]
in
foldi (\(x:xs) ys-> x : merge xs ys) [] (b ++ a)
merge a@(x:xs) b@(y:ys) = if (fst y) > (fst x)
then y : merge a ys
else x : merge xs b
merge a [] = a
merge [] b = b
foldi f z [] = z
foldi f z (x:xs) = f x (foldi f z (pairs f xs))
pairs f (x:y:t) = f x y : pairs f t
pairs f t = t
foldi builds a skewed infinitely deepening tree serving as a heap, joining all the producer streams, each for each x
, which are created already sorted in descending order. Since all the initial values of producer streams are guaranteed to be in decreasing order, each initial value can be popped without comparison, allowing the tree to be built lazily.
The code for a
produces the pairs above diagonal line using the corresponding pairs from below the diagonal line (under the assumption N >= M
, for each (x,y)
where x <= M & y < x
, (y,x)
is also to be produced.)
It should be practically O(1) for each of the few first values produced which are very near the top of the tree of comparisons.
Prelude Main> take 10 $ map snd $ enuNM (2000) (3000) [(3000,2000),(2999,2000),(3000,1999),(2998,2000),(2999,1999),(3000,1998),(2997,2 000),(2998,1999),(2999,1998),(2996,2000)] (0.01 secs, 1045144 bytes) Prelude Main> let xs=take 10 $ map (log.fromIntegral.fst) $ enuNM (2000) (3000) Prelude Main> zipWith (>=) xs (tail xs) [True,True,True,True,True,True,True,True,True] Prelude Main> take 10 $ map snd $ enuNM (2*10^8) (3*10^8) [(300000000,200000000),(299999999,200000000),(300000000,199999999),(299999998,20 0000000),(299999999,199999999),(300000000,199999998),(299999997,200000000),(2999 99998,199999999),(299999999,199999998),(299999996,200000000)] (0.01 secs, 2094232 bytes)
We can assess the empirical run-time complexity:
Prelude Main> take 10 $ drop 50000 $ map (log.fromIntegral.fst) $ enuNM (2*10^8) (3*10^8) [38.633119670465554,38.633119670465554,38.63311967046555,38.63311967046554,38.63 311967046554,38.63311967046553,38.63311967046553,38.633119670465526,38.633119670 465526,38.63311967046552] (0.17 secs, 35425848 bytes) Prelude Main> take 10 $ drop 100000 $ map (log.fromIntegral.fst) $ enuNM (2*10^8 ) (3*10^8) [38.63311913546512,38.633119135465115,38.633119135465115,38.63311913546511,38.63 311913546511,38.6331191354651,38.6331191354651,38.633119135465094,38.63311913546 5094,38.63311913546509] (0.36 secs, 71346352 bytes) *Main> let x=it *Main> zipWith (>=) x (tail x) [True,True,True,True,True,True,True,True,True] Prelude Main> logBase 2 (0.36/0.17) 1.082462160191973 -- O(n^1.08) for n=100000 values produced
This can be translated into e.g. Python using generators for Haskell streams in a strightforward manner as can be seen here.