Enumerate grid points on 2D plane with descending order of (x * y)

前端 未结 8 1271
轻奢々
轻奢々 2021-02-09 11:55

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

8条回答
  •  借酒劲吻你
    2021-02-09 11:59

    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.

提交回复
热议问题