在列表理解中使用许多相同生成器的惯用方法

时间:2017-10-18 16:08:47

标签: haskell list-comprehension idiomatic

在Statistics课程中,我的老师向我们展示了两个骰子的所有可能卷的概率模型,增加到4.记住Haskell列表推导非常棒,我决定将它带到下一步并编写此代码找到添加到10的所有可能的4个骰子

[(d1,d2,d3,d4) | d1 <- [1..6], d2 <- [1..6], d3 <- [1..6], d4 <- [1..6], (d1 + d2 + d3 + d4) == 10]

这按预期工作,给我输出

  

[(1,1,2,6),(1,1,3,5),(1,1,4,4),(1,1,5,3),(1,1,6 ,2),(1,2,1,6),(1,2,2,5),(1,2,3,4),(1,2,4,3),(1,2,5 ,2),(1,2,6,1),(1,3,1,5),(1,3,2,4),(1,3,3,3-),(1,3,4 ,2),(1,3,5,1),(1,4,1,4),(1,4,2,3),(1,4,3,2),(1,4,4 ,1),(1,5,1,3),(1,5,2,2),(1,5,3,1),(1,6,1,2),(1,6,2 ,1),(2,1,1,6),(2,1,2,5),(2,1,3,4),(2,1,4,3),(2,1,5 ,2),(2,1,6,1),(2,2,1,5),(2,2,2,4),(2,2,3,3),(2,2,4 ,2),(2,2,5,1),(2,3,1,4),(2,3,2,3),(2,3,3,2),(2,3,4 ,1),(2,4,1,3),(2,4,2,2),(2,4,3,1),(2,5,1,2),(2,5,2 ,1),(2,6,1,1),(3,1,1,5),(3,1,2,4),(3,1,3,3),(3,1,4- ,2),(3,1,5,1),(3,2,1,4),(3,2,2,3),(3,2,3,2),(3,2,4 ,1),(3,3,1,3),(3,3,2,2),(3,3,3,1),(3,4,1,2),(3,4,2 ,1),(3,5,1,1),(4,1,1,4),(4,1,2,3),(4,1,3,2),(4,1,4 ,1),(4,2,1,3),(4,2,2,2),(4,2,3,1),(4,3,1,2),(4,3,2 ,1),(4,4,1,1),(5,1,1,3),(5,1,2,2),(5,1,3,1),(5,2,1 ,2),(5,2,2,1),(5,3,1,1),(6,1,1,2),(6,1,2,1),(6,2,1 ,1)]

这就是我的问题所在.Ruby是我背景的重要组成部分,因此我对DRY原则非常重视。在代码中使用d1 <- [1..6], d2 <- [1..6], d3 <- [1..6], d4 <- [1..6]似乎是不必要的,我想相信有更好的方法可以做到这一点。

据我所知,我当前的方法通过在幕后运行4个嵌套循环来实现 - 每个生成器一个。有没有办法让一个<- [1..6]生成器适用于所有变量,有效地创建4个嵌套循环?如果没有,是否有更少冗余或惯用的方式编写此代码以实现相同的结果?

注意:我对这门语言很陌生,所以如果这是显而易见的话我会道歉。如果您使用任何新手命令式/面向对象程序员不熟悉的词/概念,请尝试为我解释。

4 个答案:

答案 0 :(得分:12)

如果你关心的是[1..6]重复(范围独立变化的能力),你可以使用:

let die = [1..6] in [ (d1,d2,d3,d4) | d1 <- die, d2 <- die
                                    , d3 <- die, d4 <- die
                                    , (d1 + d2 + d3 + d4) == 10 ]

总的来说,要删除显式模具命名,虽然这不完全相同,因为它将是列表而不是元组:

let die = [1..6] in [dice | dice <- sequence (replicate 4 die), sum dice == 10]

要恢复元组,你可以模式匹配,但是如果输入表达式因模式匹配失败而改变,则可能会引入难以跟踪的错误:

let die = [1..6] in
  [ (d1,d2,d3,d4) | dice@[d1,d2,d3,d4] <- sequence (replicate 4 die)
                  , sum dice == 10 ]

答案 1 :(得分:9)

如果您想坚持4-Tuples,即(1,1,3,5),这不是很优雅,但如果您愿意使用列表,那么您可以非常优雅地管理

import Control.Monad
listSum10 = filter ((==10) . sum) $ replicateM 4 [1..6]
          = [dice | dice <- replicateM 4 [1..6], sum dice == 10]

或使用do - 注释

listSum10 = do x <- replicateM 4 [1..6]
               guard $ sum x == 10
               return x

答案 2 :(得分:2)

edit2: 惯用在执行搜索时约束搜索的方法是尽早测试, - 尽早 - 以减少搜索尽可能搜索空间:

  let die = [1..6] in [ (d1,d2,d3,d4) | d1 <- die, d2 <- die, 
                         s2 <- [d1 + d2], s2 <= 8, d3 <- die, 
                         s3 <- [s2 + d3], s3 <= 9, s3 >= 4, d4 <- [10 - s3]]

以下是解决此问题的不同方法。

这里的想法是创建某种数据处理器和乘法器网络以实现目标,同时抓住机会提高效率。使用重复数据来避免重复工作,我们安排处理器链/数据路径图的对数高度,而不是线性高度。

import qualified Data.List.Ordered as O          -- from the data-ordlist package
import Data.Ord 

lim = 10                                                       -- the target score

(⊗) :: [(Int, a)] -> [(Int, b)] -> [(Int, (a, b))]            -- cross-product in
xs ⊗ ys = takeWhile ((<= lim).fst) $                          -- ascending order:
            O.foldt' (O.mergeBy $ comparing fst) []            --   merge all via a
              [ [(p+q, (a,b)) | (q,b) <- ys] | (p,a) <- xs]    --   balanced tree
                                       -- combine the points while tracking the score
g2 ys = ys ⊗ ys                -- the doubling combinator
ys = [ (x,x) | x <- [1..6]]     -- six sides to a die

r2 = g2 ys           -- results from rolling the dice twice,
r3 = ys ⊗ r2        -- three times, 
r4 = g2 r2           -- 4,      /less efficiently: (ys ⊗ (ys ⊗ (ys ⊗ ys)))/
r5 = ys ⊗ r4        -- 5,
r6 = g2 r3           -- 6       /less efficiently: (ys ⊗ (ys ⊗ ... (ys ⊗ ys) ...))/

foo rn = map snd $ dropWhile ((< lim).fst) rn

为了使所有这些零碎的功能得到适当的功能,剩下的就是通过使用其二进制来计算从rn创建n的一般方法。表示,或重复减半或其他东西。

目前,

  

〜&GT; foo r2
  [(4,6),(5,5),(6,4)]
  
  〜&GT;拿10 $ foo r4
  [((1,1),(2,6)),((1,1),(3,5)),((1,1),(4,4)),((1,1), (5,3)),((1,1),(6,2)),((1,2),(1   ,6)),((1,2),(2,5)),((1,2),(3,4)),((1,2),(4,3)),((1 ,2),(5,2))]
   
  〜&GT;拿10 $ foo r6
  [((1,(1,1)),(1,(1,5))),((1,(1,1)),(1,(2,4))),((1,( 1,1)),(1,(3,3))),((1,(1,1)),(1   ,(4,2))),((1,(1,1)),(1,(5,1))),((1,(1,1)),(2,(1,4) )),((1,(1,1)),(2,(2,3))),((1,(   1,1)),(2,(3,2))),((1,(1,1)),(2,(4,1))),((1,(1,1)), (3,(1,3)))]

答案 3 :(得分:2)

我相信在Haskell中,可以通过为这个骰子滚动过程找到最合适的数据类型来实现惯用方式。

那是什么..?我们将n次掷骰m次。该算法需要玫瑰树(N-ary tree)数据类型。如

data Rolls = Dice :< [Rolls] deriving Show

嗯,你已经在Data.Tree包中有了这个,但是为了用更简单的术语来表达逻辑,我将尝试使用上面非常简化的N-ary树类型的数据。通过展开,使用标准的Data.Tree包来实现它是微不足道的。

这也是非常有效的,因为我们在它们发生之前消除了不可能的路线。所以没有低效的组合和过滤。

type Count = Int
type Total = Int
type Value = Int
type Dice  = (Count, Total, Value)
data Rolls = Dice :< [Rolls] deriving Show

rollDices :: Dice -> Int -> Int -> Rolls
rollDices (c,t,v) tc tt | c <= tc-2 = let bl = (\x -> if x < 1 then 1 else x) ((tt-t)-(tc-c-1)*6)  -- bottom limit
                                          tl = (tt-t)-(tc-c)+1                                     -- top limit
                                          bs = if bl <= 6 then if tl <= 6 then [bl..tl]            -- branches
                                                                          else [bl..6]
                                                          else []
                                      in  (c,t,v) :< map (\n -> rollDices (c+1, t+n, n) tc tt) bs
                        | otherwise = (c,t,v) :< [(c+1,tt, tt-t) :< []]

main :: IO String
main = do
  putStr "How many dice are to be rolled..?   :"
  tc <- (read :: String -> Int) <$> getLine
  putStr "To what sum do you want to reach..? :"
  tt <- (read :: String -> Int) <$> getLine
  return . show $ rollDices (0,0,0) tc tt

这将计算一个具有m个级别的N元树,每个路径将保存除根(种子)为0之外的骰子值。因此,让我们看到4个骰子,目标总和为10。

*Main> main
How many dice are to be rolled..?   :4
To what sum do you want to reach..? :10
"(0,0,0) :< [(1,1,1) :< [(2,2,1) :< [(3,4,2) :< [(4,10,6) :< []],(3,5,3) :< [(4,10,5) :< []],(3,6,4) :< [(4,10,4) :< []],(3,7,5) :< [(4,10,3) :< []],(3,8,6) :< [(4,10,2) :< []]],(2,3,2) :< [(3,4,1) :< [(4,10,6) :< []],(3,5,2) :< [(4,10,5) :< []],(3,6,3) :< [(4,10,4) :< []],(3,7,4) :< [(4,10,3) :< []],(3,8,5) :< [(4,10,2) :< []],(3,9,6) :< [(4,10,1) :< []]],(2,4,3) :< [(3,5,1) :< [(4,10,5) :< []],(3,6,2) :< [(4,10,4) :< []],(3,7,3) :< [(4,10,3) :< []],(3,8,4) :< [(4,10,2) :< []],(3,9,5) :< [(4,10,1) :< []]],(2,5,4) :< [(3,6,1) :< [(4,10,4) :< []],(3,7,2) :< [(4,10,3) :< []],(3,8,3) :< [(4,10,2) :< []],(3,9,4) :< [(4,10,1) :< []]],(2,6,5) :< [(3,7,1) :< [(4,10,3) :< []],(3,8,2) :< [(4,10,2) :< []],(3,9,3) :< [(4,10,1) :< []]],(2,7,6) :< [(3,8,1) :< [(4,10,2) :< []],(3,9,2) :< [(4,10,1) :< []]]],(1,2,2) :< [(2,3,1) :< [(3,4,1) :< [(4,10,6) :< []],(3,5,2) :< [(4,10,5) :< []],(3,6,3) :< [(4,10,4) :< []],(3,7,4) :< [(4,10,3) :< []],(3,8,5) :< [(4,10,2) :< []],(3,9,6) :< [(4,10,1) :< []]],(2,4,2) :< [(3,5,1) :< [(4,10,5) :< []],(3,6,2) :< [(4,10,4) :< []],(3,7,3) :< [(4,10,3) :< []],(3,8,4) :< [(4,10,2) :< []],(3,9,5) :< [(4,10,1) :< []]],(2,5,3) :< [(3,6,1) :< [(4,10,4) :< []],(3,7,2) :< [(4,10,3) :< []],(3,8,3) :< [(4,10,2) :< []],(3,9,4) :< [(4,10,1) :< []]],(2,6,4) :< [(3,7,1) :< [(4,10,3) :< []],(3,8,2) :< [(4,10,2) :< []],(3,9,3) :< [(4,10,1) :< []]],(2,7,5) :< [(3,8,1) :< [(4,10,2) :< []],(3,9,2) :< [(4,10,1) :< []]],(2,8,6) :< [(3,9,1) :< [(4,10,1) :< []]]],(1,3,3) :< [(2,4,1) :< [(3,5,1) :< [(4,10,5) :< []],(3,6,2) :< [(4,10,4) :< []],(3,7,3) :< [(4,10,3) :< []],(3,8,4) :< [(4,10,2) :< []],(3,9,5) :< [(4,10,1) :< []]],(2,5,2) :< [(3,6,1) :< [(4,10,4) :< []],(3,7,2) :< [(4,10,3) :< []],(3,8,3) :< [(4,10,2) :< []],(3,9,4) :< [(4,10,1) :< []]],(2,6,3) :< [(3,7,1) :< [(4,10,3) :< []],(3,8,2) :< [(4,10,2) :< []],(3,9,3) :< [(4,10,1) :< []]],(2,7,4) :< [(3,8,1) :< [(4,10,2) :< []],(3,9,2) :< [(4,10,1) :< []]],(2,8,5) :< [(3,9,1) :< [(4,10,1) :< []]]],(1,4,4) :< [(2,5,1) :< [(3,6,1) :< [(4,10,4) :< []],(3,7,2) :< [(4,10,3) :< []],(3,8,3) :< [(4,10,2) :< []],(3,9,4) :< [(4,10,1) :< []]],(2,6,2) :< [(3,7,1) :< [(4,10,3) :< []],(3,8,2) :< [(4,10,2) :< []],(3,9,3) :< [(4,10,1) :< []]],(2,7,3) :< [(3,8,1) :< [(4,10,2) :< []],(3,9,2) :< [(4,10,1) :< []]],(2,8,4) :< [(3,9,1) :< [(4,10,1) :< []]]],(1,5,5) :< [(2,6,1) :< [(3,7,1) :< [(4,10,3) :< []],(3,8,2) :< [(4,10,2) :< []],(3,9,3) :< [(4,10,1) :< []]],(2,7,2) :< [(3,8,1) :< [(4,10,2) :< []],(3,9,2) :< [(4,10,1) :< []]],(2,8,3) :< [(3,9,1) :< [(4,10,1) :< []]]],(1,6,6) :< [(2,7,1) :< [(3,8,1) :< [(4,10,2) :< []],(3,9,2) :< [(4,10,1) :< []]],(2,8,2) :< [(3,9,1) :< [(4,10,1) :< []]]]]"

所以上面我们有树表示。一旦我有时间,我会写一个toList函数。