【问题标题】:List comprehension in Haskell of any order or dimensionHaskell中任何顺序或维度的列表理解
【发布时间】:2019-12-24 15:11:20
【问题描述】:

考虑以下程序。

chars = [" "] ++ ["A"] ++ ["B"] ++ (repeat "ABCD")

f :: Int -> [(Char,Int)]
f n = (,) <$> (chars !! n) <*> [1..3]

g :: Int -> [[(Char,Int)]]
g 1 = (\a     -> [a    ]) <$> (f 1)
g 2 = (\a b   -> [a,b  ]) <$> (f 1) <*> (f 2)
g 3 = (\a b c -> [a,b,c]) <$> (f 1) <*> (f 2) <*> (f 3)
-- g n = (\x1 x2 ... xn -> [x1,x2,...,xn]) <$> (f 1) <*> (f 2) <*> ... (f n)

我们怎样才能写出g n,一般对于所有n > 0,而不像上面那样显式地输入展开式,理想情况下只使用Prelude(和Control.Applicative,如果需要的话)?请注意,对于所有 n>3,f n = f (n-1),因此可以递归地定义 g

输出是这样的(忽略漂亮的打印):

> g 1
[ [ ( 'A' , 1 ) ] , [ ( 'A' , 2 ) ] , [ ( 'A' , 3 ) ] ]

> g 2
[ [ ( 'A' , 1 ) , ( 'B' , 1 ) ]
, [ ( 'A' , 1 ) , ( 'B' , 2 ) ]
, [ ( 'A' , 1 ) , ( 'B' , 3 ) ]
, [ ( 'A' , 2 ) , ( 'B' , 1 ) ]
, [ ( 'A' , 2 ) , ( 'B' , 2 ) ]
, [ ( 'A' , 2 ) , ( 'B' , 3 ) ]
, [ ( 'A' , 3 ) , ( 'B' , 1 ) ]
, [ ( 'A' , 3 ) , ( 'B' , 2 ) ]
, [ ( 'A' , 3 ) , ( 'B' , 3 ) ]
]

> g 3
[ [ ( 'A' , 1 ) , ( 'B' , 1 ) , ( 'A' , 1 ) ]
, [ ( 'A' , 1 ) , ( 'B' , 1 ) , ( 'A' , 2 ) ]
...
, [ ( 'A' , 3 ) , ( 'B' , 3 ) , ( 'D' , 3 ) ]
]

【问题讨论】:

  • 我不知道这是否可能,但实际上,您真的需要 处理所有n &gt; 0 吗?在n = 10,该列表中将有近 40 亿个元素。在n = 15,将有将近 1E+15 个元素。
  • 如所写,f i 为所有i &gt;= 3 返回相同的值。
  • 我正在用这个为 2D“魔方”立方体游戏编写求解器。应该可以解决它/使用群论策略性地简化解决过程。在这里,我强制一个将 1 与 2 交换的组。希望它不需要超过 n&gt;8。游戏在这里:chiark.greenend.org.uk/~sgtatham/puzzles/js/twiddle.html.

标签: list haskell list-comprehension monads functor


【解决方案1】:
g n = traverse f [1 .. n]

traverse 在前奏曲中(至少在过去几年中)。

因为这有点不明显,所以我是这样到达那里的:

  1. 我注意到您将 f 应用于从 1 到 n 的数字,所以我从 map f [1 .. n] 开始。

  2. 这产生了一个[[(Char, Int)]],它是所需的结果类型,但它需要有点……转向侧面并相乘。您想要来自内部列表中值的非确定性选择的所有列表。非确定性选择是[]Applicative 实例的本质,事实证明sequence[[a]] 类型上的操作正是“生成您从组合组合元素中获得的所有列表”内部列表”。这让我去了sequence $ map f [1 .. n]

  3. 但是sequencemap 这对很常见,以至于有一个操作可以同时执行这两个操作。 sequence . map f === traverse f。因此,应用该规则简化了结果。 traverse f [1 .. n]

【讨论】:

  • 不知道我是怎么错过的。这比我的答案好得多。
  • 感谢详细的解释!我玩过Prelude 函数列表,包括sequencetraverse(以及其他一些函数),但没有洞察力看到这么简单的东西!
  • (sequence .) . map 实际上是 mapM,这是一个更清晰的名称 (IMO:YMMV),打字更短,而 [] 无论如何都是 Monad。 :)
  • (我忘了补充的是 sequenceAtraverse ——你无疑知道,但我为了一个休闲的旅行者而注意到这一点)。
【解决方案2】:
g n = mapM f [1..n]

如何到达那里。

您的示例等效地使用列表理解语法编写为

g :: Int -> [[(Char,Int)]]
g 1 = [[a    ] | a <- (f 1)]
g 2 = [[a,b  ] | a <- (f 1), b <- (f 2)]
g 3 = [[a,b,c] | a <- (f 1), b <- (f 2), c <- (f 3)]
-- g n = [[a,b,c, ... , z] | a <- (f 1), b <- (f 2), ... , z <- (f n)]

这正是 sequence 在 Monad Comprehension 语法中的定义方式:

sequence [as,bs,cs, ... , zs] = [[a,b,c, ... , z] | a <- as, b <- bs, ... , z <- zs]

因此

g n = sequence [(f 1), (f 2), ... , (f n)]
    = sequence $ map f [1..n]

这又是mapM 的定义(或其等价物)。

(mapM 现在也被称为 traverse,对于也是 Applicatives 和 Monads 的类型。Haskell 列表 [] 是两者,无论如何,我发现名称 mapM 更清晰,更短类型)。


这是一个支持未被充分认识的列表理解和 Monad 理解语法的公共服务公告。

我实际上对 g n = sequence $ map f [1..n] 很好,并且没有任何特别想进一步缩短它的冲动,因为我发现它比这两种选择都更清晰。

【讨论】:

  • 我确实从你的列表理解语法开始,然后慢慢进入&lt;*&gt; 语法,并且正在考虑类似fmap (.) fmap 的组合来变出a &lt;*&gt; b &lt;*&gt; ... 但不能让它工作。无论如何,谢谢你的解释!
  • 不客气。 :) 应用语法 ...&lt;$&gt;...&lt;*&gt;...&lt;*&gt;... 替代了缺少的通用 liftAn,而 MonadComprehensions 可以是另一种替代方法(brackety 语法;如原始论文中所示)。缺少的是在可能的情况下将 MonadComprehension 翻译为基于 Applicative 的代码的能力。我有 several posts 关于什么使计算本质上 Monadic。
【解决方案3】:

像这样:

g 0 = [[]]
g n = (\xs x -> xs ++ [x]) <$> g (n - 1) <*> f n

或者更复杂,但也更高效:

g = map ($ []) . go
  where
    go :: Int -> [[(Char,Int)] -> [(Char,Int)]]
    go 0 = [id]
    go n = (\xs x -> xs . (x:)) <$> go (n - 1) <*> f n

这使用 Hughes 列表/差异列表来避免 the quadratic slowdown\xs x -&gt; xs ++ [x]

【讨论】:

  • 我确实尝试在 lambda 中使用 ++,但无法完全获得 g 的循环定义。谢谢!
  • 还有更简单的g = map reverse . g' where { g' 0 = [[]] ; g' n = [ (x:xs) | xs &lt;- g' (n-1), x &lt;- f n ] } 选项,如果后者不与(\xs x ys -&gt; xs $! (x:ys)) 一起使用,而不是(相当于)@987654328 @ -- 除非 GHC 足够聪明,可以自己做这件事。 (或者也许我在这里偏离了目标;实际上,这只是~ 2n 时间与~ 3n 时间的对比,所以,无论如何都不是那么重要:))
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-01
  • 2022-01-08
  • 1970-01-01
  • 2018-06-07
相关资源
最近更新 更多