在 Haskell 中重写嵌套 for 循环的好技术是啥?
Posted
技术标签:
【中文标题】在 Haskell 中重写嵌套 for 循环的好技术是啥?【英文标题】:What are good techniques for rewriting nested for loops in Haskell?在 Haskell 中重写嵌套 for 循环的好技术是什么? 【发布时间】:2021-10-11 18:58:31 【问题描述】:我正在学习 Haskell,目前正在尝试重写此代码:
case class Coord(x: Int, y: Int)
def buildBoard(coords: [Coord]): String =
var str = ""
for (i <- 0 to 30)
for (j <- 0 to 40)
if (coords.exists(c => c.x == i && c.y == j))
str += "x "
else
str += "o "
str += "\n"
str
这为我正在编写的命令行游戏构建了一个简单的板。我可以重写它的最简单方法是什么?我不是在寻找任何高性能的东西
我正在使用元组来实现Coord
类型:
type Coordinate = (Int, Int)
type Coordinates = [Coordinate]
可能的答案
我非常感谢那些提供帮助的人。我设法使用列表理解来完成工作。我的想法是摆脱之前代码的“迭代方法”。事实上,我想将一组“虚拟”坐标转换为字符串,我是这样做的:
createBoard :: Coordinates -> String
createBoard coordinates = concatMap parse board
where
parse coordinate@(_, y)
| coordinate `elem` coordinates = "x "
| y == 41 = "\n"
| otherwise = "o "
board = [ (x, y) | x <- [0..30], y <- [0..41] ]
【问题讨论】:
函数式编程(以及扩展声明式编程)的理念之一是您不应该考虑循环。 您可以尝试使用列表推导和/或concat
来解决这个问题。你不应该试图模仿你的命令式代码,而是以一种功能性的方式来表达结果,例如通过构建一个列表列表,您可以稍后将其连接起来以获得想要的结果。
另外,这个问题不应该被关闭。它对于仍然通过 fp 和 haskell 本身的初始步骤的任何人,或者(正如尊敬的用户 @AntC 甜言蜜语)“试图重写粗略的代码”的人都非常有用。
@WillNess Whos tag wiki 说“请注意,要求翻译您的代码不适合 Stack Overflow。”
@amalloy 它还说 '一个适当形式的问题可能是“我如何翻译这个陈述”',问题的原始标题是 “How can我在 Haskell 中重写了嵌套的 for 循环?”。看起来几乎一样。 :)
【参考方案1】:
正如您已经发现的那样,“嵌套循环”部分大部分可以通过列表解析来完成。
但您原始代码中的其他所有内容也可以通过列表推导来完成!首先对外部循环使用一个列表推导:
Prelude> [ "foo" | i <- [0..3] ]
["foo","foo","foo","foo"]
你真的不需要关心换行符,只需使用标准的unlines
函数
Prelude> unlines [ "foo" | i <- [0..3] ]
"foo\nfoo\nfoo\nfoo\n"
现在是内部循环。这应该会产生一个字符串/列表,因此您可以 concat
单个列表,但同样有一个标准函数也可以添加您总是在两个字符之间放置的空格:
Prelude> putStrLn $ unlines [ unwords ["a" | j <- [0..4]] | i <- [0..3] ]
a a a a a
a a a a a
a a a a a
a a a a a
最后我们可以使用条件来决定使用什么字符:
Prelude> putStrLn $ unlines [ unwords [if i>j then "x" else "o" | j <- [0..4]] | i <- [0..3] ]
o o o o o
x o o o o
x x o o o
x x x o o
现在让我们将所有这些包装在一个函数中:
createBoard :: Coordinates -> String
createBoard cs
= unlines [ unwords [ if (i,j)`elem`cs then "x" else "o"
| j <- [0..40]
]
| i <- [0..30]
]
【讨论】:
【参考方案2】:在某种意义上,嵌套循环是 monads/do notation/list comprehensions。
您的任务可以直接编码为一个列表推导式,它将处理所有测试、换行插入和连接,几乎直接对应于您的“命令式”代码,如果您眯着眼睛看,这不是命令式小:
board :: [(Int, Int)] -> String
board coords =
[ c | i <- [0..30], -- outer loop
j <- [0..40], -- nested loop
c <- if elem (i,j) coords
then "x " -- two chars
else "o " ++ -- spliced in
if j == 40 -- and an optional
then "\n" -- newline
else "" ]
如果您的语言范围不包括上限(如 Haskell 的“范围”,即枚举),则需要在此处进行调整。
这里除了elem
之外,不需要使用任何无关的函数。列表推导非常通用。尤其是连接几乎就是它们最初存在的目的:无论有多少嵌套循环,在 innermost 级别生成的元素都会直接拼接到输出中。就像您的 str += "x"
等声明一样。
【讨论】:
以上是关于在 Haskell 中重写嵌套 for 循环的好技术是啥?的主要内容,如果未能解决你的问题,请参考以下文章
WPF:关于ScrollViewer中嵌套Datagrid的问题