列表中的数字乘数

时间:2010-04-22 15:26:53

标签: algorithm math haskell

如何在合并的排序列表中打印给定数字列表的倍数?

take 10 (multiples [4,5])

给出

4,5,8,10,12,15,16,20,24,25

我已经让它适用于大小为2或1的列表,但我需要一个更通用的解决方案:)

9 个答案:

答案 0 :(得分:8)

以下是两个有效的解决方案,可以生成无重复的排序无限列表,您可以take来实现。假设您对multiples的输入有n个元素。

每个元素

O(n)

首先,对于输入中的每个数字,创建其倍数的无限列表。然后,仔细合并这些列表 ,保持它们的排序并避免重复。 (这是问题的难点。)

multiples xs = merge [map (*x) [1..] | x<-xs]

merge xss
    | all null xss = []
    | otherwise    = m : merge (map (avoid m) xss)
    where
      m = minimum [head xs | xs<-xss, xs/=[]]
      avoid m (x:xs) | m==x  = xs
      avoid m   xs           = xs

(由于MtnViewMark的评论,代码从原始版本清理完毕。) 这有效:

*Main> take 20 $ multiples [4,6,9]
[4,6,8,9,12,16,18,20,24,27,28,30,32,36,40,42,44,45,48,52]

merge的这种实现比一次合并两个列表更有效,并且生成每个输出元素只需要O(n)时间。

每个元素的O(log n)

更多(和AFAICT,大多数)高效算法是通过将候选者保存在堆中来生成所需的倍数。每个输出元素只需要O(log n)时间。

import Data.Heap as Heap (MinHeap, insert, empty, view)

multiplesH xs = uniq $ tail $ map fst $ iterate (next . snd) (0, prep xs)
    where
      prep :: Ord a => [a] -> MinHeap (a,a)
      prep = foldr (\x -> insert (x,x)) empty
      next h = case view h of Just ((x,n),hh) -> (x, insert (x+n,n) hh)
      uniq (x:y:ys) | x==y  = uniq (y:ys)
      uniq (x:xs)           = x: (uniq xs)
      uniq []               = []

当你只有几个数字时它们并没有太大的不同,但是对于大数n,堆版本很多更快:

*Main> :set +s
*Main> multiples [1000..2000] !! 10000
20088
(21.70 secs, 2108213464 bytes)
*Main> multiplesH [1000..2000] !! 10000
20088
(0.08 secs, 15348784 bytes)

答案 1 :(得分:4)

参数中的每个数字都成为无数的倍数列表

multiLists :: [Integer] -> [[Integer]]
multiLists = map (\x -> iterate (+x) x)

然后您需要合并结果列表。由于每个列表都保证按升序排列,因此您可以使用类似this page末尾的合并函数。

最后,您可能希望消除重复项。使用排序列表执行此操作的方法是:

sortedNub :: [Integer] -> [Integer]
sortedNub = map head . group

答案 2 :(得分:2)

这是一个总是生成排序结果,删除重复项,生成无限列表(你可以take)并且相对有效(应该是常量内存!)的版本:

multiples :: (Num a, Ord a) => [a] -> [a]
multiples = map (fst.head) . iterate step . prep
    where prep                    = map (\i -> (i,i))
          next (m,i)              = (m+i,i)

          step (p:ps)             = uniq $ insert (next p) ps

          insert q  []            = [q]
          insert q (p:ps) | q > p = p : insert q ps
          insert q  ps            = q : ps

          uniq p@((ma,_):(mb,_):_) | ma == mb = step p
          uniq p                              = p

示例:

> take 20 $ multiples [4,9]
[4,8,9,12,16,18,20,24,27,28,32,36,40,44,45,48,52,54,56,60]

> take 20 $ multiples [4,8,10]
[4,8,10,12,16,20,24,28,30,32,36,40,44,48,50,52,56,60,64,68]

> take 20 $ multiples [4, 9, 20]
[4,8,9,12,16,18,20,24,27,28,32,36,40,44,45,48,52,54,56,60]

注意:假设输入列表已排序。在. sort之后添加. prep以删除此约束。

答案 3 :(得分:1)

multi xs = [x*y | y <- [1..], x <- xs ]

应该这样做。主要问题是控制你应该take的数量有点难。

要避免结果中的多个相等数字,请在结果列表中应用Data.List.nub。这不是非常复杂,可以更快地完成,但可以完成工作。

答案 4 :(得分:1)

我将此视为整数列表中的过滤器。

您需要的只是一个谓词,用于确定整数是否是列表中项目的倍数。

然后用该谓词过滤[1 ..]。

multiples xs = filter (isDividedByAny xs) [1..]
       where isDividedByAny xs int =  any (divides int) xs
                      where divides int elem  = int `mod` elem == 0

答案 5 :(得分:1)

我很惊讶地发现没有提到“汉明问题”:汉明问题是大卫特纳为他的FP提出的懒惰函数式编程的经典例子之一,这是第一个类似Haskell的语言Miranda。

汉明问题与相同,multiples [2,3,5]类似,而特纳的解决方案是(见下面的评论):

ham = 1 : foldr1 merge [mult 2 ham, mult 3 ham, mult 5 ham]
      where
      mult n x = [n*a|a<-x]
      merge (a:x) (b:y) = a : merge x y,     if a=b
                        = a : merge x (b:y), if a<b
                        = b : merge (a:x) y, if a>b

(来自特纳的Example Miranda scripts

这直接推广到(假设传递给倍数的所有元素都大于1,并且与问题相反,参数列表正在增加):

multiples ms = drop 1 mms
      where mms = 1: foldr1 merge (map (mult mms) ms))
            mult x n = [n*a|a<-x]
            merge (a:x) (b:y) = a : merge x y,     if a=b
                              = a : merge x (b:y), if a<b
                              = b : merge (a:x) y, if a>b

讨论了关于LtU的汉明问题的四种解决方案:expressivity of "idiomatic C++"

答案 6 :(得分:1)

另一个答案?那么,解决这个问题的一种方法就是广义合并。我变得有点痴迷于找到一种相对干净和有效的多向合并方法。

此合并函数将任意有限数量的任意列表作为输入并生成它们的合并。唯一的前提条件是列表已排序。列表可以是空的或无限的:

merge :: (Ord a) => [[a]] -> [a]
merge rs =
    case foldr minToFront [] rs of
        []          -> []
        ([]:rs)     ->     merge rs
        ((a:as):rs) -> a : merge (as:rs)
    where
        minToFront a (b:rs) | a `after` b = b:a:rs
        minToFront a  qs                  = a:qs

        []    `after` _     = False
        _     `after` []    = True
        (a:_) `after` (b:_) = a > b

这段代码只为生成的每个元素输入一个输入列表的头部。

一旦你有了这个,定义原始功能很容易:

multiples :: (Num a, Ord a) => [a] -> [a]
multiples = uniq . merge . map (\n -> iterate (+n) n)

你需要另一个很好的通用效用函数去除重复的答案。以unix实用程序命名,这里是:

uniq :: (Eq a) => [a] -> [a]
uniq :: (Eq a) => [a] -> [a]
uniq []                       = []
uniq (a:bs@(b:_)) | a == b    =     uniq bs
uniq (a:bs)                   = a : uniq bs

你可以用这个简单的代码将那个小小的snippit变成一个完全可行的等效于uniq命令行实用程序(好吧,忽略命令行选项):

main :: IO ()
main = interact (unlines . uniq . lines)

哈斯克尔让我微笑!

答案 7 :(得分:0)

也许:

let howmany = 10 in take howmany (nub (sort [ x * y | x <- [4, 5, 6, 7], y <- [1..howmany] ]))

给出:

[4,5,6,7,8,10,12,14,15,16]

Haskell不是我的强项,对于更大的列表来说效率相当低,但是它有用(我想!)。

您需要hugs / ghci中的列表模块:l List

答案 8 :(得分:0)

这确实有效,但不适用于无限列表:

sort [ x * y | x <- [4, 5], y <- [1..10] ]

因此,您必须在[1..10]部分中指定所需的倍数。 不好的是,它不会尊重[5,4],例如,只是把它分类到同一个东西。


好的,更好的一个:

multiples :: (Num a, Ord a) => [a] -> [a] 
multiples nums = sort $ multiply 1
   where multiply n = map (*n) nums ++ (multiply $ n + 1)
  

需要10 $倍数[4,5]

     

[4,5,8,10,12,15,16,20,20,25]

你可能想在那里添加“nub”,以删除双号

multiples :: (Num a, Ord a) => [a] -> [a] 
multiples nums = sort . nub $ multiply 1
   where multiply n = map (*n) nums ++ (multiply $ n + 1)