如何将列表中的每个元素与haskell中另一个列表中的每个元素分开

时间:2017-08-04 10:43:02

标签: list haskell

假设我有一个无限列表A = [1..]我想将A中的每个元素与列表B = [1..10]中的所有元素分开如果列表A中的任何元素都可被所有元素整除B中的元素我需要打印它。 我需要继续这个,直到我得到10个这样的数字。

以下尝试不起作用:

print(minimum([x | x <- [1..], y <- [1..10], rem x y == 0]))

2 个答案:

答案 0 :(得分:8)

您的尝试

您写道:

print(minimum([x | x <- [1..], y <- [1..10], rem x y == 0]))

现在由于以下几个原因,这不会起作用:

  1. 如果x中的{em>任何元素y分为[1..10],它会将x添加到列表推导列表中。此外,对于任何此类y,我们会一次添加x。因此,x6,它会将其添加三次,因为6可以分为1,2,3和6;
  2. 结果将是无限列表,因此无法计算该列表的最小值。这是因为Haskell不知道列表已经被排序,因此最小值只是第一个元素。您可以使用head;
  3. 获取第一个元素
  4. minimum只会生成一个元素,但您需要前10个元素。
  5. 蛮力逼近

    首先,我们可以使用列表推导来生成所有这些数字(对于任意列表asbs):

    divide_all as bs = [a | a <- as, all ((0 ==) . mod a) bs]
    

    所以这里列表推导迭代as并将每个元素分配给a。接下来我们有一个过滤器all ((0 ==) . mod a) bs,它是all (\b -> mod a b == 0) bs的紧凑形式。因此,它会检查b中的所有成员bsmod a b == 0a是否可以b分割)。如果过滤器满足,那么我们将a(在列表理解的头部)添加到结果中。请注意,这些列表是懒惰构建的,因此as具有无限数量的元素这一事实不是问题。

    现在我们可以使用take :: Int -> [a] -> [a]取这些数字的前10个,然后打印出来:

    mapM_ print (take 10 $ divide_all [1..] [1..10])
    

    打印:

    Prelude> mapM_ print (take 10 $ divide_all [1..] [1..10])
    2520
    5040
    7560
    10080
    12600
    15120
    17640
    20160
    22680
    25200
    

    最少公倍数

    上述方法效率不高:对于a的每个元素,我们需要检查它是否可以与b的每个元素分开。我的机器花了2.16秒来计算这个列表的第1000个元素,用10.21秒来计算第5000个元素。

    我们可以通过计算b中所有元素的最小公倍数(lcm)来加速最后一个,并检查一个数字是否可以被lcm分割:< / p>

    divide_all as bs = [a | a <- as, mod a lcmb == 0] -- optimized version
        where lcmb = foldr1 lcm bs
    

    所以现在我们只需要进行一次检查。计算第1000个元素现在需要0.95秒,并且计算第5000个元素需要4.54秒。

    如果as = [1..]

    如果已知as[1..],我们可以大幅提升此代码,因为我们知道a的元素都是lcmb的倍数。因此,我们可以删除as参数,并使用:

    divide_all bs = [lcmb*a | a <- [1..]] -- optimized version
        where lcmb = foldr1 lcm bs
    

    现在计算第1000个元素需要0.01秒,并计算第5000个元素0.03秒。但当然这只有假设才有效。

答案 1 :(得分:1)

let divcheck = (take 10 .) . filter . flip (all . ((0 ==) .) . mod)

divcheck [1..10] [1..]
-- [2520,5040,7560,10080,12600,15120,17640,20160,22680,25200]

divcheck [1,2,3] [1..]
-- [6,12,18,24,30,36,42,48,54,60]