在Haskell中对无限列表进行燕尾迭代

时间:2013-09-19 13:36:22

标签: haskell

我想迭代2(或3)个无限列表,找到满足条件的“最小”对,如下所示:

until pred [(a,b,c) | a<-as, b<-bs, c<-cs]
  where pred (a,b,c) = a*a + b*b == c*c
        as = [1..]
        bs = [1..]
        cs = [1..]

在整个程序运行期间,上面的内容不会很快,a == b == 1。 是否有一个很好的方法来解决问题,例如构建无限序列[(1,1,1),(1,2,1),(2,1,1),(2,1,2),(2,2,1),(2,2,2),(2,2,3),(2,3,2),..]

奖励:是否有可能推广到n元组?

9 个答案:

答案 0 :(得分:8)

有一个monad,Omega

Prelude> let as = each [1..]
Prelude> let x = liftA3 (,,) as as as
Prelude> let x' = mfilter (\(a,b,c) -> a*a + b*b == c*c) x
Prelude> take 10 $ runOmega x'
[(3,4,5),(4,3,5),(6,8,10),(8,6,10),(5,12,13),(12,5,13),(9,12,15),(12,9,15),(8,15,17),(15,8,17)]

使用它的应用功能,您可以推广到任意元组:

quadrupels = (,,,) <$> as <*> as <*> as <*> as   -- or call it liftA4

:当然,仅此一项并不能消除重复。它只给你正确的对角化。也许您可以将monad comprehensions与Thomas的方法一起使用,或者只使用另一个mfilter传递(在这种情况下限制为b /= c)。

答案 1 :(得分:7)

列表理解是解决此类问题的好方法(也是简明扼要的方法)。首先,您知道您希望(a,b,c)的所有组合可能满足a^2 + b^2 = c^2 - 有用的观察结果是(仅考虑正数),a <= c && b <= c始终是这样的。

为了生成我们的候选人列表,我们可以说c范围从1到无穷大,ab范围从1到c。< / p>

[(a,b,c) | c <- [1..], a <- [1..c], b <- [1..c]]

要获得解决方案,我们只需要将您想要的等式添加为警卫:

[(a,b,c) | c <- [1..], a <- [1..c], b <- [1..c], a*a+b*b == c*c]

效率低,但输出正确:

[(3,4,5),(4,3,5),(6,8,10),(8,6,10),(5,12,13),(12,5,13),(9,12,15)...

有比盲测更多的原则方法可以解决这个问题。

答案 2 :(得分:3)

{ - 这取决于什么是“最小的”。但是,如果元组首先按其最大值进行比较,这里有一个“最小”概念的解决方案。数字,然后按他们的总和。 (当我在评论中写下文本时,您可以将我的整个答案复制并粘贴到文件中。)

我们稍后需要nub。 - }

import Data.List (nub)

{ - 仅用于说明:2元组的简单案例。 - }

-- all the two-tuples where 'snd' is 'n'
tuples n = [(i, n) | i <- [1..n]]
-- all the two-tuples where 'snd' is in '1..n'
tuplesUpTo n = concat [tuples i | i <- [1..n]]

{ - 要获得所有结果,您需要将每个元组的翻转插入到流中。但是,让我们稍后再做,然后先概括一下。

构建任意长度的元组有点困难,因此我们将在列表上工作。如果它们的长度为'k',我称它们为'kList'。 - }

-- just copied from the tuples case, only we need a base case for k=1 and 
-- we can combine all results utilizing the list monad.
kLists 1 n = [[n]]
kLists k n = do 
       rest <- kLists (k-1) n
       add <- [1..head rest]
       return (add:rest)

-- same as above. all the klists with length k and max number of n
kListsUpTo k n = concat [kLists k i | i <- [1..n]]

-- we can do that unbounded as well, creating an infinite list.
kListsInf k = concat [kLists k i | i <- [1..]]

{ - 下一步是旋转这些列表,因为到目前为止,最大的数字总是在最后一个位置。因此,我们只需查看所有旋转即可获得所有结果。在这里使用nub无疑是尴尬的,你可以改进它。但如果没有它,所有元素相同的列表将重复k次。 - }

rotate n l = let (init, end) = splitAt n l 
             in end ++ init
rotations k l = nub [rotate i l | i <- [0..k-1]]

rotatedKListsInf k = concatMap (rotations k) $ kListsInf k

{ - 剩下的就是将这些列表转换为元组。这有点尴尬,因为每个n元组都是一个单独的类型。但它当然是直截了当的。 - }

kListToTuple2 [x,y]         = (x,y)
kListToTuple3 [x,y,z]       = (x,y,z)
kListToTuple4 [x,y,z,t]     = (x,y,z,t)
kListToTuple5 [x,y,z,t,u]   = (x,y,z,t,u)
kListToTuple6 [x,y,z,t,u,v] = (x,y,z,t,u,v)

{ - 一些测试:

*Main> take 30 . map kListToTuple2 $ rotatedKListsInf 2
[(1,1),(1,2),(2,1),(2,2),(1,3),(3,1),(2,3),(3,2),(3,3),(1,4),(4,1),(2,4),(4,2),(3,4),
(4,3),(4,4),(1,5),(5,1),(2,5),(5,2),(3,5),(5,3),(4,5),(5,4),(5,5),(1,6),(6,1),
(2,6), (6,2), (3,6)]
*Main> take 30 . map kListToTuple3 $ rotatedKListsInf 3
[(1,1,1),(1,1,2),(1,2,1),(2,1,1),(1,2,2),(2,2,1),(2,1,2),(2,2,2),(1,1,3),(1,3,1),
(3,1,1),(1,2,3),(2,3,1),(3,1,2),(2,2,3),(2,3,2),(3,2,2),(1,3,3),(3,3,1),(3,1,3),
(2,3,3),(3,3,2),(3,2,3),(3,3,3),(1,1,4),(1,4,1),(4,1,1),(1,2,4),(2,4,1),(4,1,2)]

修改 的 我意识到有一个错误:当然,旋转有序列表是不够的。解决方案必须符合

的某些方面
rest <- concat . map (rotations (k-1)) $ kLists (k-1) n
kLists中的

,但随后出现了重复输出的一些问题。我想你可以搞清楚。 ;-)   - }

答案 3 :(得分:1)

这个答案是针对未知谓词的更普遍的问题。如果谓词是已知的,那么更有效的解决方案是可能的,就像其他人已经列出了基于知识的解决方案,您不需要针对给定的c迭代所有Int。

在处理无限列表时,您需要执行广度优先搜索解决方案。列表理解仅提供深度优先搜索,这就是您从未在原始代码中找到解决方案的原因。

counters 0 xs = [[]]
counters n xs = concat $ foldr f [] gens where
  gens = [[x:t | t <- counters (n-1) xs] | x <- xs]
  f ys n = cat ys ([]:n)
  cat (y:ys) (x:xs) = (y:x): cat ys xs
  cat [] xs = xs
  cat xs [] = [xs]

main = print $ take 10 $ filter p $ counters 3 [1..] where
  p [a,b,c] = a*a + b*b == c*c

counters为指定数字范围内的值生成所有可能的计数器,包括无限范围。

首先,我们获得有效计数器组合的生成器列表 - 对于每个允许的数字,将其与较小尺寸的计数器的所有允许组合相结合。这可能导致生成器产生无限数量的组合。因此,我们需要均匀地从每台发电机借用。

所以gens是一个生成器列表。可以将此视为以一位数开头的所有计数器的列表:gens !! 0是以1开头的所有计数器的列表,gens !! 1是以{{1}开头的所有计数器的列表等等。

为了均匀地从每个发生器借用,我们可以转置生成器列表 - 这样我们就可以获得生成器的第一个元素列表,然后是生成器的第二个元素列表等。

由于生成器列表可能是无限的,我们不能转置生成器列表,因为我们可能永远不会看到任何生成器的第二个元素(对于无限数量的数字,我们有无穷大的数字)发电机组)。因此,我们从发生器“对角地”枚举元素 - 从第一个生成器获取第一个元素;然后从第一个发生器获取第二个元素,从第二个发生器获取第一个元素;然后从第一个生成器获取第三个元素,从第二个生成器获取第二个元素,从第三个生成器获取第一个元素,等等。这可以通过使用函数2折叠生成器列表来完成,它将两个拉链在一起列表 - 一个列表是生成器,另一个列表是已经压缩的生成器 - 其中一个列表的开头通过向头部添加f而向前偏移一步。这差不多是[]: - 区别在于如果n或ys比另一个短,我们不会删除其他列表的其余部分。请注意,使用zipWith (:) ys ([]:n)进行折叠将是转置。

答案 4 :(得分:1)

这实际上取决于你所说的“最小”的含义,但我假设你想要找到一个关于其最大元素的数字元组 - 所以(2,2)小于(1,3)(而标准Haskell排序是词典的。)

有一个包data-ordlist,其目的正是为了处理有序列表。它的功能mergeAll(和mergeAllBy)允许您将每个方向排序的二维矩阵组合成一个有序列表。

首先让我们在元组上创建一个所需的比较函数:

import Data.List (find)
import Data.List.Ordered

compare2 :: (Ord a) => (a, a) -> (a, a) -> Ordering
compare2 x y = compare (max2 x, x) (max2 y, y)
  where
    max2 :: Ord a => (a, a) -> a
    max2 (x, y) = max x y

然后使用mergeAll创建一个函数,该函数采用比较器,组合函数(两个参数中必须为monotonic)和两个排序列表。它使用函数组合来自两个列表的所有可能元素,并生成结果排序列表:

mergeWith :: (b -> b -> Ordering) -> (a -> a -> b) -> [a] -> [a] -> [b]
mergeWith cmp f xs ys = mergeAllBy cmp $ map (\x -> map (f x) xs) ys

使用此功能,根据最大值生成元组非常简单:

incPairs :: [(Int,Int)]
incPairs = mergeWith compare2 (,) [1..] [1..]

它的前10个元素是:

> take 10 incPairs 
[(1,1),(1,2),(2,1),(2,2),(1,3),(2,3),(3,1),(3,2),(3,3),(1,4)]

当我们(例如)寻找其平方和等于65的第一对时:

find (\(x,y) -> x^2+y^2 == 65) incPairs

我们得到正确的结果(4,7)(与(1,8)相反,如果使用了词典排序)。

答案 5 :(得分:0)

对于这个答案,我会用“最小”来表示元组中数字的总和。

要按顺序列出所有可能的对,您可以先列出总和为2的所有对,然后列出总和为3的所有对,依此类推。在代码中

pairsWithSum n = [(i, n-i) | i <- [1..n-1]]
xs = concatMap pairsWithSum [2..]

Haskell没有使用模板Haskell处理n元组的工具,所以为了概括这一点,你必须切换到列表。

ntuplesWithSum 1 s = [[s]]
ntuplesWithSum n s = concatMap (\i -> map (i:) (ntuplesWithSum (n-1) (s-i))) [1..s-n+1]
nums n = concatMap (ntuplesWithSum n) [n..]

答案 6 :(得分:0)

这是另一种解决方案,可能是另一种略微不同的“最小”概念。我的命令只是“所有具有最大元素N的元组在所有具有最大元素N + 1的元组之前出现”。我写了对和三元组的版本:

gen2_step :: Int -> [(Int, Int)]
gen2_step s = [(x, y) | x <- [1..s], y <- [1..s], (x == s || y == s)]

gen2 :: Int -> [(Int, Int)]
gen2 n = concatMap gen2_step [1..n]

gen2inf :: [(Int, Int)]
gen2inf = concatMap gen2_step [1..]

gen3_step :: Int -> [(Int, Int, Int)]
gen3_step s = [(x, y, z) | x <- [1..s], y <- [1..s], z <- [1..s], (x == s || y == s || z == s)]

gen3 :: Int -> [(Int, Int, Int)]
gen3 n = concatMap gen3_step [1..n]

gen3inf :: [(Int, Int, Int)]
gen3inf = concatMap gen3_step [1..]

你不能把它归结为N元组,但只要你保持同质,你可以在使用数组时将其概括为一般。但我不想把我的大脑绑在那个结上。

答案 7 :(得分:0)

我认为这是最简单的解决方案,如果&#34;最小的&#34;定义为x + y + z,因为在积分值为毕达哥拉斯三角形的空间中找到第一个解后,无限列表中的下一个解更大。

take 1 [(x,y,z) | y <- [1..], x <- [1..y], z <- [1..x], z*z + x*x == y*y] - &GT; [(4,5,3)]

它有一个很好的属性,它只返回一次对称唯一解决方案一次。 x和z也是无限的,因为y是无穷大的。

这不起作用,因为x的序列永远不会结束,因此你永远不会得到y的值,更不用说z了。最右边的生成器是最里面的循环。

take 1 [(z,y,x)|z <- [1..],y <- [1..],x <- [1..],x*x + y*y == z*z]

答案 8 :(得分:-1)

Sry,自从我做了haskell以来已经有一段时间了,所以我将用文字来形容它。

正如我在评论中指出的那样。无法在无限列表中找到最小的任何东西,因为总会有一个较小的东西。

你可以做的是,有一个基于流的方法,它采用列表并返回一个只有'有效'元素的列表,i。即满足条件的地方。让我们调用这个函数triangle

然后,您可以使用take n (triangle ...)在某种程度上计算三角形列表,并从此n元素中找到minium。