【问题标题】:Construct infinite sorted list without adding duplicates构造无限排序列表而不添加重复项
【发布时间】:2023-03-19 15:15:01
【问题描述】:

我对 Haskell 比较陌生,但我正在尝试通过阅读和尝试解决 Project Euler 中的问题来学习。我目前正在尝试实现一个函数,该函数采用无限的整数列表并返回所述列表中元素的成对和的有序列表。我真的在寻找针对我所面临的具体问题的解决方案,而不是关于不同策略或方法的建议,但这些也是受欢迎的,因为作为编码人员并不意味着知道如何实施策略,而是选择最好的可用的策略。

我的方法依赖于遍历无限生成器的无限列表并按顺序检索元素,其中有几个数学属性对实现我的解决方案很有用。

如果我试图获得自然数的成对和的序列,例如,这将是我的代码:

myList :: [Integer]
myList = [1..]

myGens :: [[Integer]]
myGens = gens myList
    where
        gens = \xs -> map (\x -> [x+y|y<-(dropWhile (<x) xs)]) xs

无论使用的数字集如何,只要它是排序的,以下条件都成立:

  • ∀ i ≥ 0, head (gens xs !! i) == 2*(myList !! i)
  • ∀ i,j,k ≥ 0, l > 0, (((gens xs) !! i) !! j) &lt; (((gens xs) !! i+k) !! j+l)

第二种情况的特殊情况是:

  • ∀ i,j ≥ 0, (((gens xs) !! i) !! j) &lt; (((gens xs) !! i+1) !! j)
  • ∀ i,j ≥ 0, k > 0, (((gens xs) !! i) !! j) &lt; (((gens xs) !! i+k) !! j)

这是我要修改的特定代码:

stride :: [Integer] -> [Int] -> [[Integer]] -> [Integer]
stride xs cs xss = x : stride xs counts streams
        where
            (x,i) = step xs cs xss
            counts = inc i cs
            streams = chop i xss

step :: [Integer] -> [Int] -> [[Integer]] -> (Integer,Int)
step xs cs xss = pace xs (defer cs xss)

pace :: [Integer] -> [(Integer,Int)] -> (Integer,Int)
pace hs xs@((x,i):xt) = minim (x,i) hs xt
    where
        minim :: (Integer,Int) -> [Integer] -> [(Integer,Int)] -> (Integer,Int)
        minim m _ [] = m
        minim m@(g,i) hs (y@(h,n):ynt) | g > h && 2*(hs !! n) > h = y
                                       | g > h = minim y hs ynt
                                       | 2*(hs !! n) > g = m
                                       | otherwise = minim m hs ynt


defer :: [Int] -> [[a]] -> [(a,Int)]
defer cs xss = (infer (zip cs (zip (map head xss) [0..])))

infer :: [(Int,(a,Int))] -> [(a,Int)]
infer [] = []
infer ((c,xi):xis) | c == 0 = xi:[]
                   | otherwise = xi:(infer (dropWhile (\(p,(q,r)) -> p>=c) xis))

我正在使用的有问题的集合具有多个不同对产生相同总和的属性。我想要一种有效的方法来一次处理所有重复元素,以避免增加计算所有成对总和的成本,因为如果 M 是重复的数量,它需要 M 更多的测试。

有人有什么建议吗?

编辑:

我对代码进行了一些更改,独立于建议的内容,希望对我的原始代码、修改后的代码和迄今为止的提案的相对效率提供反馈。

stride :: [Integer] -> [Int] -> [[Integer]] -> [Integer]
stride xs cs xss = x : stride xs counts streams
where
    (x,is) = step xs cs xss
    counts = foldr (\i -> inc i) cs is
    streams = foldr (\i -> chop i) xss is

step :: [Integer] -> [Int] -> [[Integer]] -> (Integer,[Int])
step xs cs xss = pace xs (defer cs xss)

pace :: [Integer] -> [(Integer,Int)] -> (Integer,[Int])
pace hs xs@((x,i):xt) = minim (x,(i:[])) hs xt
    where
    minim :: (Integer,[Int]) -> [Integer] -> [(Integer,Int)] -> (Integer,[Int])
    minim m _ [] = m
    minim m@(g,is@(i:_)) hs (y@(h,n):ynt) | g > h && 2*(hs !! n) > h = (h,[n])
                              | g > h = minim (h,[n]) hs ynt
                              | g == h && 2*(hs !! n) > h = (g,n:is)
                          | g == h = minim (g,n:is) hs ynt
                          | g < h && 2*(hs !! n) > g = m
                          | g < h = minim m hs ynt

另外,我省略了incchop 的代码:

alter :: (a->a) -> Int -> [a] -> [a]
alter = \f -> \n -> \xs -> (take (n) xs) ++ [f (xs !! n)] ++ (drop (n+1) xs)

inc :: Int -> [Int] -> [Int]
inc = alter (1+)

chop :: Int -> [[a]] -> [[a]]
chop = alter (tail)

【问题讨论】:

    标签: algorithm list haskell duplicates generator


    【解决方案1】:

    我将提出一个使用无限 pairing heap 的解决方案。每个构造的元素都会有对数开销,但no one knows how to do better(在具有基于比较的方法和实数的模型中)。

    第一段代码就是标准的配对堆。

    module Queue where
    import Data.Maybe (fromMaybe)
    
    data Queue k = E
                 | T k [Queue k]
                 deriving Show
    
    fromOrderedList :: (Ord k) => [k] -> Queue k
    fromOrderedList [] = E
    fromOrderedList [k] = T k []
    fromOrderedList (k1 : ks'@(k2 : _ks''))
      | k1 <= k2 = T k1 [fromOrderedList ks']
    
    mergePairs :: (Ord k) => [Queue k] -> Queue k
    mergePairs [] = E
    mergePairs [q] = q
    mergePairs (q1 : q2 : qs'') = merge (merge q1 q2) (mergePairs qs'')
    
    merge :: (Ord k) => Queue k -> Queue k -> Queue k
    merge (E) q2 = q2
    merge q1 (E) = q1
    merge q1@(T k1 q1's) q2@(T k2 q2's)
      = if k1 <= k2 then T k1 (q2 : q1's) else T k2 (q1 : q2's)
    
    deleteMin :: (Ord k) => Queue k -> Maybe (k, Queue k)
    deleteMin (E) = Nothing
    deleteMin (T k q's) = Just (k, mergePairs q's)
    
    toOrderedList :: (Ord k) => Queue k -> [k]
    toOrderedList q
      = fromMaybe [] $
          do (k, q') <- deleteMin q
             return (k : toOrderedList q')
    

    请注意,fromOrderedList 接受无限列表。我认为这在理论上可以通过假装无限的后代列表有效地“及时”合并来证明是合理的。这感觉像是纯函数式数据结构的文献中应该有的东西,但我会偷懒,现在不看。

    函数mergeOrderedByMin 更进一步,合并了一个可能无限的队列列表,其中每个队列中的最小元素是非递减的。我不认为我们可以重用merge,因为merge 似乎不够懒惰。

    mergeOrderedByMin :: (Ord k) => [Queue k] -> Queue k
    mergeOrderedByMin [] = E
    mergeOrderedByMin (E : qs') = mergeOrderedByMin qs'
    mergeOrderedByMin (T k q's : qs')
      = T k (mergeOrderedByMin qs' : q's)
    

    下一个函数从排序列表中删除重复项。在m09建议的库中,但为了完整起见,我在这里定义一下。

    nubOrderedList :: (Ord k) => [k] -> [k]
    nubOrderedList [] = []
    nubOrderedList [k] = [k]
    nubOrderedList (k1 : ks'@(k2 : _ks''))
      | k1 < k2 = k1 : nubOrderedList ks'
      | k1 == k2 = nubOrderedList ks'
    

    最后,我们把它们放在一起。我将以方块为例。

    squares :: [Integer]
    squares = map (^ 2) [0 ..]
    
    sumsOfTwoSquares :: [Integer]
    sumsOfTwoSquares
      = nubOrderedList $ toOrderedList $
          mergeOrderedByMin
            [fromOrderedList (map (s +) squares) | s <- squares]
    

    【讨论】:

      【解决方案2】:

      如果您不想修改太多代码,可以使用 Data.List.Ordered 的 nub 函数(可由 cabal install data-ordlist 安装)过滤掉重复项。

      它以线性时间运行,即复杂性方面您的算法不会改变。

      【讨论】:

        【解决方案3】:

        对于您的示例[1..],结果只是[2..]。来自具有隐式堆的通用解决方案的“非常智能的编译器”could deduce this,如下所示。

        gens xs 更好地表达为

        gens xs = map (\t@(x:_) -> map (x+) t) $ tails xs   -- or should it be
             --   map (\(x:ys) -> map (x+) ys) $ tails xs   --                ?
        

        它的结果列表很容易被tree-like folding1 (pictured here) 合并而不会重复,与

        pairsums xs = foldi (\(x:l) r-> x : union l r) $ gens xs
        

        这假设输入列表是按升序排列的。如果它只是按非递减顺序(当然,其中只有有限的 runs 等号),您需要在其上添加一个 orderedNub (as m09 mentions),

        pairsums' = orderedNub . pairsums
        

        仅通过使用foldifoldr 可以工作,我们通常可以将算法的复杂度从n 提高到log n,这是一个非常显着的加速。我一直将它用作通用工具。

        1代码,仅针对无限列表进行了调整:

        foldi f (x:xs)  = f x (foldi f (pairs f xs))
        pairs f (x:y:t) = f x y : pairs f t
        union (x:xs) (y:ys) = case compare x y of           
                                  LT -> x : union  xs (y:ys) 
                                  EQ -> x : union  xs    ys  
                                  GT -> y : union (x:xs) ys  
        

        另见:

        【讨论】:

        • 奇怪的是,即使是主题启动者也没有支持这个答案。
        • @SassaNF 我猜唯一的赞成票来自你? :) :) 我并不感到惊讶,SO 上的新人经常没有参与他们的问题。在这种情况下,OP 最后一次在线是在May 14,所以他们甚至没有看到这个答案。一年后,甚至更多,我的一些答案被 OP 接受/赞成。 SO 非常依赖时间,人群大多遵循最新的答案。 -- 关于 this 问题,我仍然没有时间将性能与接受的答案进行比较(当然,在它正确对角化之后,这是微不足道的)......
        • 感谢您的帮助。 [1..] 纯粹是一个例子,所以你的建议虽然受欢迎,但不是必需的。 ~OP
        • @archaephyrryx 我的回答不仅仅是第一句话。 :) 我将进行编辑以澄清这是一个通用解决方案,与接受答案中的代码平行。
        • @WillNess 我知道,我只是指回答中的第一句话。我对其他任何事情都没有任何问题。
        【解决方案4】:

        我建议在对角线上方构建对,这样甚至不会生成很多重复:

        sums xs = zipWith (map . (+)) hs ts where
          (hs:ts) = tails xs
        

        现在您有一个列表列表,每个列表都包含排序的总和。因为它们是排序的,所以可以在有限的步骤中确定序列的下一个元素:

        filtermerge :: (Ord a) => [[a]]->[a]
        filtermerge ((h:t):ts) = h : filtermerge (insert t ts) where
          insert [] ts = ts
          insert xs [] = [xs]   
          insert h ([]:t) = insert h t
          insert (h:t) ts@((h1:t1):t2)
            | h < h1  = (h:t):ts
            | h == h1 = insert (h:t) $ insert t1 t2
            | otherwise = insert (h1:t1) $ insert (h:t) t2
        filtermerge _ = []
        

        【讨论】:

        • 您对“pairwise”的定义可能更有意义,但为了对应 OP 的代码,它应该是 sums xs = zipWith (map . (+)) xs (tails xs)(即它们也包括对角线)。
        • 另外,它看起来像 filtermerge ~= foldr (\(x:l) r-&gt; x:union l r) [] (具有通常的 union 定义,就像我的回答一样)。 IE。 it's linear,所以在复杂性上会带来 ~ n/log n 的惩罚——因为有问题的列表都是无限的,所以 [] 处理案例都不会触发,所以在确定“下一个元素”时的“步骤数”序列“有限的,但是是O(i)i是序列中的当前索引。
        • @WillNess 谢谢,尤其是复杂性分析。我确实认为这个问题中的列表是无限的,但写了一个更通用的函数来合并任何排序的列表。
        • 只需使用foldi 而不是foldr,复杂度就会提高到从n 记录n。我一直使用它,作为通用工具。
        • @WillNess 你能说明你是如何得到log n的吗? (如果你提到的一些参考资料中有解释,我没有读过)pairs 的第一遍让你得到n/2 的功能应用。第二遍让你n/4。然后n/8等。这些的无穷和是n
        猜你喜欢
        • 2021-02-25
        • 1970-01-01
        • 2013-04-26
        • 1970-01-01
        • 2020-03-27
        • 2014-07-23
        • 2012-10-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多