在 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 循环的好技术是啥?的主要内容,如果未能解决你的问题,请参考以下文章

haskell中的Double for循环

在 React 中管理嵌套状态的好方法

WPF:关于ScrollViewer中嵌套Datagrid的问题

在VB中 for 循环嵌套语句的用法语解释(必须清楚!!)

用嵌套的for循环编写程序,要求通过这个嵌套的循环在屏幕上打印下列图案:

00015_循环嵌套