重写 Haskell 中嵌套 for 循环的好技巧是什么?
What are good techniques for rewriting nested for loops in Haskell?
我正在学习 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] ]
正如您已经发现的,“嵌套循环”部分在很大程度上可以通过列表理解来完成。
但是您原始代码中的所有其他内容也可以通过列表理解来完成!首先对外循环使用一个列表理解:
Prelude> [ "foo" | i <- [0..3] ]
["foo","foo","foo","foo"]
您真的不需要处理换行符,只需使用标准 unlines
函数
Prelude> unlines [ "foo" | i <- [0..3] ]
"foo\nfoo\nfoo\nfoo\n"
现在进入内循环。这应该会产生一个 string/list,因此您可以 concat
各个列表,但同样还有一个标准函数,它还添加了您始终放在两个字符之间的 space:
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]
]
从某种意义上说,嵌套循环就是 monads/do 表示法/列表理解。
您的任务可以直接编码为一个列表理解,它将处理所有测试、换行插入和连接,几乎直接对应于您的“命令式”代码,如果您看起来不是那么命令式眯眯眼:
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 并且目前正在尝试重写此代码:
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] ]
正如您已经发现的,“嵌套循环”部分在很大程度上可以通过列表理解来完成。
但是您原始代码中的所有其他内容也可以通过列表理解来完成!首先对外循环使用一个列表理解:
Prelude> [ "foo" | i <- [0..3] ]
["foo","foo","foo","foo"]
您真的不需要处理换行符,只需使用标准 unlines
函数
Prelude> unlines [ "foo" | i <- [0..3] ]
"foo\nfoo\nfoo\nfoo\n"
现在进入内循环。这应该会产生一个 string/list,因此您可以 concat
各个列表,但同样还有一个标准函数,它还添加了您始终放在两个字符之间的 space:
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]
]
从某种意义上说,嵌套循环就是 monads/do 表示法/列表理解。
您的任务可以直接编码为一个列表理解,它将处理所有测试、换行插入和连接,几乎直接对应于您的“命令式”代码,如果您看起来不是那么命令式眯眯眼:
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"
等语句一样。