我总是觉得有一个函数或表达式需要在Haskell中使用列表(或数组,只是应用相同的)的值以及索引,这很不方便。
我在试验N-queens问题 here 时写下validQueens
...
validQueens x =
and [abs (x!!i - x!!j) /= j-i | i<-[0..length x - 2], j<-[i+1..length x - 1]]
我不关心使用索引,所有的加号和减号等等。它感觉马虎。我想出了以下内容:
enumerate x = zip [0..length x - 1] x
validQueens' :: [Int] -> Bool
validQueens' x = and [abs (snd j - snd i) /= fst j - fst i | i<-l, j<-l, fst j > fst i]
where l = enumerate x
受到Python enumerate
的启发(不是借用命令式概念必然是一个好主意)。在概念上似乎更好,但snd
和fst
在整个地方都很糟糕。至少乍一看,它在时间和空间上都是昂贵的。我不确定我是否更喜欢它。
简而言之,我对这两者都不满意
有没有人发现他们发现比上述任何一种更优雅的模式?如果没有,是否有任何令人信服的理由,上述方法之一是否优越?
答案 0 :(得分:32)
借用enumerate
很好并且鼓励。但是,拒绝计算其参数的长度可以使它变得有点懒惰:
enumerate = zip [0..]
(事实上,只使用zip [0..]
而不命名enumerate
是很常见的。)我不清楚为什么你认为你的第二个例子在时间或空间上应该更昂贵。请记住:索引是O(n),其中n是索引。您对fst
和snd
的笨拙的投诉是合理的,可以通过模式匹配来解决:
validQueens' xs = and [abs (y - x) /= j - i | (i, x) <- l, (j, y) <- l, i < j]
where l = zip [0..] xs
现在,您可能会对这个双循环的效率感到担忧,因为(j, y) <- l
条将在l
的整个主干上运行,而实际上我们只是想要它从(i, x) <- l
开始我们离开的地方。所以,让我们编写一个实现这个想法的函数:
pairs :: [a] -> [(a, a)]
pairs xs = [(x, y) | x:ys <- tails xs, y <- ys]
完成此功能后,您的功能不太难以适应。将谓词拉出到自己的函数中,我们可以使用all
代替and
:
validSingleQueen ((i, x), (j, y)) = abs (y - x) /= j - i
validQueens' xs = all validSingleQueen (pairs (zip [0..] xs))
或者,如果您更喜欢无点符号:
validQueens' = all validSingleQueen . pairs . zip [0..]
答案 1 :(得分:15)
索引元素元组在Haskell中是很常见的事情。因为zip
在第一个列表停止时停止,您可以将它们写为
enumerate x = zip [0..] x
更优雅,更高效(因为它不预先计算length x
)。事实上,我甚至不打算命名它,因为zip [0..]
太短了。
这肯定比按列表索引迭代更有效,因为!!
在第二个参数中是线性的,因为列表是链接列表。
使节目更优雅的另一种方法是使用模式匹配而不是fst
和snd
:
validQueens' :: [Int] -> Bool
validQueens' x = and [abs (j2 - i2) /= j1 - i1 | (i1, i2) <-l, (j1, j2) <-l, j1 > i1]
where l = zip [0..] x