寻找组合

时间:2019-05-12 00:57:29

标签: haskell list-comprehension

我想编写一个函数来计算7个元组中数字1到7的所有组合,但是每个数字在每个元组中只能出现一次。

到目前为止,我已经找到了这种方法,但是它还会返回每个元组中多次出现相同编号的组合。我不太确定如何删除多个元组 出现相同的数字。

  a = [(a,b,c,d,e,f,g) | a <- [1..7], b <- [1..7], c <- [1..7], 
        d <- [1..7], e <- [1..7], f <- [1..7], g <- [1..7]]

目标结果示例(所有有效组合都应在此处):

  [(1,2,3,4,5,6,7),(2,1,3,4,5,6,7),(2,3,1,4,5,6,7),...]

4 个答案:

答案 0 :(得分:9)

您可以使用(\\)Data.List之间的列表差异。

perms = [ (a,b,c,d,e,f,g) | a <- [1..7]
                          , b <- [1..7] \\ [a]
                          , c <- [1..7] \\ [a,b]
                          , d <- [1..7] \\ [a,b,c]
                          , e <- [1..7] \\ [a,b,c,d]
                          , f <- [1..7] \\ [a,b,c,d,e]
                          , g <- [1..7] \\ [a,b,c,d,e,f] ]

通过这种方式,b将被选择为与a不同,c将与ab不同,依此类推。

答案 1 :(得分:8)

我们可以通过answer来优化kuoytfouy中的代码,就像

perms = [(a,b,c,d,e,f,g) | a <- [1..7], let dom6 = [1..7] \\ [a]
                         , b <- dom6,   let dom5 = dom6 \\ [b]
                         , c <- dom5,   let dom4 = dom5 \\ [c]
                         , d <- dom4,   let dom3 = dom4 \\ [d] 
                         , e <- dom3,   let dom2 = dom3 \\ [e] 
                         , f <- dom2,   let dom1 = dom2 \\ [f] 
                         , g <- dom1,   let dom0 = dom1 \\ [g]  ]

并通过减少冗余计算来进一步改进它,

perms = [(a,b,c,d,e,f,g) | a <- [1..7], let dom6 = delete a [1..7]
                         , b <- dom6,   let dom5 = delete b dom6
                         , c <- dom5,   let dom4 = delete c dom5
                         , d <- dom4,   let dom3 = delete d dom4 
                         , e <- dom3,   let dom2 = delete e dom3 
                         , f <- dom2,   let [g]  = delete f dom2 ]

将元素的选择与从当前域中删除的元素组合在一起,可以为我们提供一个同时执行两项工作的功能,通常称为picks。过去曾在SO答案中使用过它,并且可以在那找到它。

另请参阅:

  • picks来自一个pigworker
  • choose在“唯一选择”单例中
  • 我的Common Lisp answer带有 efficiency 代码,该代码实际上是通过对列表结构进行外科手术突变来缩小域列表的,在递归构建的过程中将元素逐个删除嵌套循环;并在返回的途中对其进行修复。
    也就是说,基于choose-(或等效为picks-)的Haskell代码严重怀疑效率极低(对于初学者,在完全强制时inits是二次幂的)。< br /> 每次都重新计算缩小的域,就像在此答案中一样,我们在每个时间点仅得到七个(六个,无论如何)域列表,用- but ,每次delete调用都从头开始搜索 参数(发明了效率低的picks来解决...),再次表明总体计算是二次的,效率低的。值得深思!

答案 2 :(得分:6)

那又怎么样呢?

import Data.List
list = [(a,b,c,d,e,f,g) | a <- [1..7], b <- [1..7], c <- [1..7],
        d <- [1..7], e <- [1..7], f <- [1..7], g <- [1..7], [1,2,3,4,5,6,7]\\[a,b,c,d,e,f,g]==[]]

答案 3 :(得分:5)

我们可以在此处创建一个“辅助函数”,对于给定列表xs生成一个元组列表,其中第一个元素是我们选择的元素,第二个元素是剩余元素的列表,例如:

import Data.List(inits, tails)

pick :: [a] -> [(a, [a])]
pick ls = [(b, as ++ bs) | (as, (b:bs)) <- zip (inits ls) (tails ls)]

例如:

Prelude Data.List> pick [1..5]
[(1,[2,3,4,5]),(2,[1,3,4,5]),(3,[1,2,4,5]),(4,[1,2,3,5]),(5,[1,2,3,4])]

因此,每个项目都会从列表中选择一个元素,并返回一个列表,其中已删除该元素的列表。我们可以使用它来将该列表传递给下一个生成器。

然后我们可以在do块中使用它,例如:

perms :: (Num a, Enum a) => [(a, a, a, a, a, a, a)]
perms = do
    (a, as) <- pick [1..7]
    (b, bs) <- pick as
    (c, cs) <- pick bs
    (d, ds) <- pick cs
    (e, es) <- pick ds
    (f, [g]) <- pick es
    return (a, b, c, d, e, f, g)

产生:

Prelude Data.List> perms
[(1,2,3,4,5,6,7),(1,2,3,4,5,7,6),(1,2,3,4,6,5,7),(1,2,3,4,6,7,5),(1,2,3,4,7,5,6),(1,2,3,4,7,6,5),(1,2,3,5,4,6,7),(1,2,3,5,4,7,6), ...