【问题标题】:How to make a sorted list of multiples for several numbers?如何为多个数字制作一个排序的倍数列表?
【发布时间】:2019-06-04 02:03:10
【问题描述】:

我在完成 Haskell 课程的作业时遇到问题。我已经解决了这个任务的一个部分问题:我必须编写一个函数,它接受一个 Int 并创建一个包含该 Int 的倍数的无限列表。

function :: Int -> [Int]
function d = [d*x | x <- [1..]]

控制台:

ghci> take 10 (function 3)

给了

[3,6,9,12,15,18,21,24,27,30]

在第二个任务中,我必须扩展该函数,以便它接受一个数字列表并将该列表的每个值用作一个因子(之前的 d)。例如:

ghci> take 10 (function [3, 5])

应该给

[3,5,6,9,10,12,15,18,20,21]

已经尝试过类似的列表理解

function d = [y*x | y <- [1..], x <- d]

但该函数以未排序的形式返回列表:

[3,5,6,10,9,15,12,20,15,25]

我们得到了应该使用 Haskell 的模函数的提示,但我不知道如何准确地进行。你有什么好的建议吗?

【问题讨论】:

  • 尝试将filter与模函数一起使用
  • 什么是“不需要的形式”?
  • @talex 我目前的解决方案是:ghci> take 10 (function [3,5]) ---> [3,5,6,10,9,15,12,20,15, 25] (1*3, 1*5, 2*3, 2*5, 3*3, 3*5, 4*3, 4*5, 5*3, 5*5),但要升序排序像 [3,5,6,9,10,12,15,18,20,21] (1*3, 1*5, 2*3, 3*3, 2*5, 4*3, 3*5 , 5*3, 4*5, 6*3)
  • 不要从无开始并添加“正确”数字,而是尝试从所有数字开始并删除“错误”数字。
  • [d*x | x &lt;- [1..]] 可以简化为[d, d+d ..]。

标签: list haskell


【解决方案1】:

如果你认为d 不是一个因素

y = x * d 

而是

y `mod` d == 0,

然后您可以从列表[1..] 中获取列表推导并添加谓词函数,例如:

function ds 
    | null ds   = [1..]
    | otherwise = [ x | x <- [1..], qualifies x ]
    where
      qualifies x = any (==0) $ (flip mod) <$> ds <*> [x]

一个更有表现力的版本,一开始可能更容易掌握:

function' ds   
    | null ds   = [1..]
    | otherwise = [ x | x <- [1..], divByAnyIn ds x ]
    where
      divByAnyIn ds x = 
          case ds of
            (d:ds') -> if x `mod` d == 0 then True 
                                         else divByAnyIn ds' x
            _       -> False

【讨论】:

    【解决方案2】:

    我只有一个班轮。

    import Data.List (nub)
    
    f xs = nub [x|x<-[1..], d<-xs, x `mod` d == 0]
    
    take 10 $ f [3,5] -- [3,5,6,9,10,12,15,18,20,21]
    

    从结果列表中运行时间应该是 O(n² + n*d)。 nub 在 O(n²) 中运行。摆脱它会很好。

    g xs = [x |x<-[1..], let ys = map (mod x) xs in 0 `elem` ys]
    

    这表现相当不错。它应该在 O (n*d) 中运行。我也有这个版本,我认为它的性能至少和 g 一样好,但显然它的性能比 f 好,比 g 差。

    h xs = [x |x<-[1..], or [x `mod` d == 0 |d<-xs] ]
    

    我不确定为什么会这样, 或 据我所知是懒惰的,我看不出有什么理由让它运行得更慢。当您增加输入列表的长度时,它尤其不能缩放。

    i xs = foldr1 combine [[x, x+x ..] |x<- sort xs]
        where
          combine l [] = l
          combine [] r = r
          combine l@(x:xs) r@(y:ys)
            | x < y = (x: combine xs r)
            | x > y = (y: combine l ys)
            | otherwise = (x: combine xs ys)
    

    不再是一个班轮,而是我能想到的最快的班轮。我不是百分百确定为什么如果您向右或向左折叠以及提前对输入列表进行排序,它会在运行时产生如此大的差异。但它不应该对结果产生影响,因为:

    commutative a b = combine [a] [b] == combine [b] [a]
    

    我发现从将递归函数折叠到无穷无尽的输入系数倍数列表的列表中来考虑这个问题是完全疯狂的。

    在我的系统上,它仍然比此处使用 Data.List.Ordered 提供的另一个解决方案慢约 10 倍。

    【讨论】:

    • 您可以将此问题视为“我有一堆递增的流,我必须从中创建一个递增的流”。我的想法是“让我定义一个累加器acc 和一个函数f,它们在每一步决定所有流中的哪个元素进入累加器”。 f 应该是递归的,因为在每一步你都在做同样的事情。从这个角度来看,递归函数上的foldr 看起来是完美的候选者。只是分享我的想法
    • @steve combine = Data.List.Ordered.union。 :) 是否递归,它只是将两个流合二为一。你的意思是很好的“疯狂”,比如惊险刺激,对吧?至于速度差异,您只是暗示,而不是描述,但它们是有道理的。请参阅wiki.haskell.org/Prime_numbers 上关于“左倾”与“右倾”(和树状)折叠的讨论。但前提是您使用超过 2 个除数;如果有 2 应该没有任何区别,因为没有深化结构。
    • 你把它折叠在一个只有 2 个列表的列表上(如果 xs == [2,3])(当然,每个 is 都是无限的)。但也可以折叠 n 个无限列表。上述页面和 Data.List.Ordered 中的示例。有了它,我们甚至可以拨打O.unionAll [[n,n+n..] | n &lt;-[1..]]。 --- 10 倍是因为编译了包函数。
    • 因子 10 确实是因为它没有被编译。编译它给出了一场并驾齐驱的比赛。 @WillNess 当然我的意思是好的“疯子”
    • 正确。 :) 并驾齐驱,因为本质上是一样的。你可以在源代码中看到它。
    【解决方案3】:

    这里的答案只是说明这个想法,它不是一个优化的解决方案,可能存在多种实现方式。

    首先,从输入的列表中计算每个因子的所有值:

    map (\d->[d*x|x<-[1..]]) xs
    

    例如:xs = [3, 5] 给予

    [[3, 6, 9, ...], [5, 10, 15, ...]]
    

    然后,找到每个列表的第一个元素的最小值为:

    findMinValueIndex::[(Int, [Int])]->Int
    findMinValueIndex xss = minimum $ 
                            map fst $ 
                            filter (\p-> (head $ snd p) == minValue) xss
        where minValue = minimum $ map (head . snd) xss
    

    一旦我们发现列表包含最小值,将其返回并从列表中删除最小值:

    sortMulti xss = 
                let idx = findMinValueIndex $ zip [0..] xss
                in  head (xss!!idx):sortMulti (updateList idx (tail $ xss!!idx) xss
    

    因此,例如,在找到结果的第一个值(即3)之后,查找下一个值的列表是:

    [[6, 9, ...], [5, 10, 15, ...]]
    

    重复以上步骤我们就可以构造出想要的列表了。最后,删除重复的值。这是完成的编码:

    import Data.Sequence (update, fromList)
    import Data.Foldable (toList)
    
    function :: [Int] -> [Int]
    function xs = removeDup $ sortMulti $ map (\d->[d*x|x<-[1..]]) xs
        where sortMulti xss = 
                let idx = findMinValueIndex $ zip [0..] xss
                in  head (xss!!idx):sortMulti (updateList idx (tail $ xss!!idx) xss)
    
    removeDup::[Int]->[Int]
    removeDup [] = []
    removeDup [a] = [a]
    removeDup (x:xs) | x == head xs = removeDup xs
                     | otherwise = x:removeDup xs
    
    findMinValueIndex::[(Int, [Int])]->Int
    findMinValueIndex xss = minimum $ 
                            map fst $ 
                            filter (\p-> (head $ snd p) == minValue) xss
        where minValue = minimum $ map (head . snd) xss
    
    updateList::Int->[Int]->[[Int]]->[[Int]]
    updateList n xs xss = toList $ update n xs $ fromList xss
    

    【讨论】:

    • 顺便说一句,我试图简化你的函数并最终得到function = O.nub . unfoldr g . map (iterate =&lt;&lt; (+)) where { g mss = Just (m, s:r) where { ((m:s):r) = sortBy (comparing head) mss }},这在基本函数中看起来确实是O.mergeAll 的一个很好的编码:) 假设输入列表是短 (当然只包含无限列表),所以sort 时间不是问题。 O. 是 Data.List.Ordered 的缩写,来自 data-ordlist 包。 :)
    【解决方案4】:

    有一个很好的递归解决方案

    function' :: Int -> [Int]
    function' d = [d * x | x <- [1..]]
    
    braid :: [Int] -> [Int] -> [Int]
    braid []        bs = bs
    braid as        [] = as
    braid aa@(a:as) bb@(b:bs) 
      | a < b     = a:braid as bb
      | a == b    = a:braid as bs # avoid duplicates
      | otherwise = b:braid aa bs
    
    function :: [Int] -> [Int]
    function ds = foldr braid [] (map function' ds)
    

    braid 函数仅使用输入的头部和惰性“即时”构建所需的列表

    【讨论】:

      【解决方案5】:

      如果你想用模函数来做,你可以定义一个简单的单行

      foo ds = filter (\x -> any (== 0) [mod x d | d <- ds]) [1..]
      

      或者,以更易读的形式,

      foo ds = filter p [1..]
        where
        p x = any id [ mod x d == 0 | d <- ds]
            = any (== 0) [ mod x d | d <- ds]
            = not $ null [ () | d <- ds, mod x d == 0]
            = null [ () | d <- ds, mod x d /= 0]
            = null [ () | d <- ds, rem x d > 0]
      

      这样,我们得到

      > take 20 $ foo [3,5]
      [3,5,6,9,10,12,15,18,20,21,24,25,27,30,33,35,36,39,40,42]
      

      但是,它效率低下:last $ take 20 $ foo [300,500] == 4200,所以要生成这 20 个数字,这段代码测试了 4200。数字越大,情况就越糟。

      我们应该在与n 大致成比例的时间内生成n 数字,而不是。

      为此,首先将每个数字的倍数写在自己的列表中:

      [ [d*x | x <- [1..]] | d <- ds ] ==
      [ [d, d+d ..] | d <- ds ] 
      

      然后,合并这些有序递增的数字列表以一种有序的方式生成一个有序的非递减数字列表。包data-ordlist 有很多functions 来处理这种列表:

      import qualified Data.List.Ordered as O
      import           Data.List               (sort)
      
      bar :: (Ord a, Num a, Enum a) => [a] -> [a]
      bar ds = foldr    O.merge [] [ [d, d+d ..] | d <- ds ]
             = O.foldt' O.merge [] [ [d, d+d ..] | d <- ds ]   -- more efficient, 
             = O.mergeAll [ [d, d+d ..] | d <- sort ds ]       -- tree-shaped folding
      

      如果我们希望生成的列表不包含任何重复项,即创建一个递增列表,我们可以将其更改为

      baz ds = O.nub $ foldr O.merge [] [ [d, d+d ..] | d <- ds ]
             = foldr    O.union [] [ [d, d+d ..] | d <- ds ]
             = O.foldt' O.union [] [ [d, d+d ..] | d <- ds ]
             = O.unionAll [ [d, d+d ..] | d <- sort ds ]
             = (O.unionAll . map (iterate =<< (+)) . sort)  ds
      

      哦,而且,与二次方 Data.List.nub 不同,Data.List.Ordered.nub 是线性的,在输入列表的每个元素上花费 O(1) 时间。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-08-02
        • 1970-01-01
        • 1970-01-01
        • 2018-01-15
        • 1970-01-01
        • 2019-05-09
        • 1970-01-01
        相关资源
        最近更新 更多