【问题标题】:Filtering a custom tuple in haskell在haskell中过滤自定义元组
【发布时间】:2020-07-29 21:03:20
【问题描述】:

大家好,我正在开发一个函数,它将棋盘游戏的棋盘输出为字符串。 目前我有它的工作,所以我可以输出没有任何碎片的板。

我正在尝试添加一个过滤器,以便如果当前 (x, y) 坐标在元组列表中 [Position, Col] 其中Position(Int, Int)

如果(x, y) 在此列表中,那么我想检查颜色Col,然后相应地输出。

drawBoardCell :: (Int, Int) ->[(Position, Col)] -> String
drawBoardCell (x, y) pieces = do
      let test = filter (\((a,b),_) -> a == x && b == y) pieces
      if snd(test) == Black
            then " b "
      else if snd(test) == White
            then " w "
      else " . "

这是我到目前为止所尝试的并遇到错误:

Display.hs:47:14: error:
    • Couldn't match expected type ‘(a0, Col)’
                  with actual type ‘[((Int, Int), Col)]’
    • In the first argument of ‘snd’, namely ‘(test)’
      In the first argument of ‘(==)’, namely ‘snd (test)’
      In the expression: snd (test) == Black

Display.hs:49:19: error:
    • Couldn't match expected type ‘(a1, Col)’
                  with actual type ‘[((Int, Int), Col)]’
    • In the first argument of ‘snd’, namely ‘(test)’
      In the first argument of ‘(==)’, namely ‘snd (test)’
      In the expression: snd (test) == White

【问题讨论】:

  • filter 的返回值是一个列表 ([a]) 所以test 是一个列表 - 错误消息中的实际类型。另一方面,snd 需要一个元组 - 错误消息中的 预期类型。这两种类型不匹配。

标签: list haskell filter tuples


【解决方案1】:

filter 返回一个列表,但 snd 需要一个元组。这些是不同的类型。

你可以调整你的条件为

      if snd(head test) == Black
            then " b "
      else if snd(head test) == White
            then " w "
      else " . "

但是如果test 是一个 列表,[] 呢?

你可以这样编码

      if not (null test) && snd(head test) == Black
            then " b "
      else not (null test) && if snd(head test) == White
            then " w "
      else " . "

但这不是一个很好的 Haskell。相反,我们可以通过模式匹配获得更好的代码:

      case  test  of
        ((_,Black):_) -> " b "
        ((_,White):_) -> " w "
        _             -> " . "

或者重组你的代码来使用

find :: (a -> Bool) -> [a] -> Maybe a

而不是

filter :: (a -> Bool) -> [a] -> [a]

您可以使用Hoogle了解更多信息。

【讨论】:

    【解决方案2】:

    问题是test 仍然是一个列表[(Position, Col)],所以它不能传递给snd,它需要一个元组(a, b)。如果你可以保证你的pieces数组中会有匹配,那么你应该将你的body改为:

    drawBoardCell (x, y) pieces =
      let [match] = filter (\((a,b),_) -> a == x && b == y) pieces
      case match of
        (_, Black) -> " b "
        (_, White) -> " w "
        _          -> " . "
    

    注意这里使用case 而不是if。这不是绝对必要的,但在将一个值与多个选项进行比较时以这种方式匹配时会更简洁。

    但是,如果我怀疑是这种情况,您可能有 0 或 1 个匹配项,则需要使用类似以下的内容:

    drawBoardCell (x, y) pieces = do
      case filter (\((a,b),_) -> a == x && b == y) pieces of
        [(_, Black)] -> " b "
        [(_, White)] -> " w "
        _            -> " . "
    

    如果过滤列表中不存在该值,如果该值多次出现,或者如果单个匹配项的Col 既不是White 也不是Black,这将打印" . "

    在 cmets 中指出,您可能希望以不同的方式处理不匹配和多个匹配。在这种情况下:

    drawBoardCell (x, y) pieces = do
      case filter (\((a,b),_) -> a == x && b == y) pieces of
        [(_, Black)] -> " b "
        [(_, White)] -> " w "
        []           -> " . "  -- Case of no matches
        _            -> error "Multiple pieces cannot occupy the same position " ++ show (x, y)
    

    如果列表有多个匹配项,此示例将导致运行时错误(这对于调试很有用)。

    【讨论】:

    • 您的代码表面上是错误的。您可能具有领域知识,即该列表中永远不会有任何重复项(按位置),但这在代码中并不是不言而喻的。这就是为什么我使用我在回答中所做的那些模式。如果有任何重复,您的第二个代码将不会使用它们中的任何一个。第一个会出错。
    • @WillNess 我解释了我在答案中所做的假设。很明显,我认为第二种解决方案仅适用于“0 或 1 匹配”。对棋盘游戏有些熟悉,我认为多个棋子出现在棋盘上的同一位置是一种错误情况。我考虑将这些案例设为[] -> " . "; _ -> error "Multiple pieces cannot occupy the same position",但我知道error 不受欢迎,所以我决定不这样做。
    • 这与我所说的并不矛盾。 :) 你很可能绝对肯定坏情况永远不会发生,相反,它在代码中并不是不言而喻的。显式错误比未处理错误的可能性要好得多。您知道它永远不会发生,因此拥有该错误消息是无害的,可用作(附加)文档。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-15
    • 1970-01-01
    相关资源
    最近更新 更多