【问题标题】:F# : Writing a function that builds a list of tuples recursively, a syntax errorF#:编写递归构建元组列表的函数,语法错误
【发布时间】:2012-01-15 10:50:20
【问题描述】:

我正在尝试编写一个返回 List 的递归函数,但我在语法上遇到了一些问题。

当递归结束时,函数应该返回一个空列表,否则一个元组 (int * int) 与递归调用自身返回的 List 合并:

let rec foobar () : List<int * int> =
    if (recursionIsEnded) then
        []
    else 
        foobar () :: (10, 10) // this is wrong
        // (10,10) works, but I need to concatenate it with the elements returned by foobar recursive call

有人可以向我解释我做错了什么吗?

编辑:

我会尽量提供更多细节。 其实我的功能有点复杂。我正在遍历一个二维数组并构建一个包含满足特定条件的数组索引元素的元组列表。其实这是我的代码:

let rec GetSameColorNeighs(grid : Option<Ball> [,], row : int , col : int, color : Microsoft.Xna.Framework.Color) : List<int * int> =
    if (row < 0 || col < 0 || row > MaxLineNumber - 1 || col > BallsPerLine - 1) then
        []
    else
        let ball = grid.[row,col]
        match ball with 
            |Some(ball) -> 

                if (!ball.visited = false || not <| ball.color.Equals(color)) then
                    [row , col]
                else
                     ball.visited := true
                     (row,col) ::GetSameColorNeighs(grid, row + 1, col + 1, color) ::  GetSameColorNeighs(grid, row - 1, col - 1, color) 

            |None  -> []

所以这里还有 2 个问题:):

  1. 如何修改下面一行才能编译?

    (row,col) ::GetSameColorNeighs(grid, row + 1, col + 1, color) ::  GetSameColorNeighs(grid, row - 1, col - 1, color) 
    
  2. 有没有更好的方法来做到这一点?

我不关心列表的元素顺序。

【问题讨论】:

    标签: list syntax recursion f# tuples


    【解决方案1】:

    Daniel 的解决方案在我看来不错,但您不需要使用可变状态 (ResizeArray)。相反,您可以将loop 函数编写为使用yield 生成结果并使用yield! 进行递归调用的序列表达式:

    let GetSameColorNeighs (grid:Option<Ball>[,], row, col, color:Color) =
      let rec loop (row, col) = seq {
        if not (row < 0 || col < 0 || row > MaxLineNumber - 1 
                        || col > BallsPerLine - 1) then
            let ball = grid.[row,col]
            match ball with 
            | Some(ball) -> 
              if (!ball.visited = false || not <| ball.color.Equals(color)) then
                // Not sure what you want here - yield items using 'yield'?
                // [row , col] 
              else
                ball.visited := true
                yield row, col                 // Add single item to results
                yield! loop(row + 1, col + 1)  // Add all generated to results
                yield! loop(row - 1, col - 1)  //        -- || --
            | None  -> () }
      loop(row, col) |> Seq.toList
    

    【讨论】:

    • @Heisenbug 序列表达式的另一个不错的功能是,您可以从for 循环中产生东西(而不是使用递归进行迭代)。但是,我不确定这在这种情况下是否有用(取决于您如何处理 2D 数组)。
    • 实际上,我正在构建一个具有相同颜色的 adiacent 球列表,从数组的给定元素开始。构建列表后,我将返回没有在列表中索引的元素的数组副本(因为它们是 Option)。
    • 当 (!ball.visited = false || not ... 时您没有使用 Some(ball) 的任何特殊原因?而不是匹配然后 if?
    • @Heisenbug 这主要是向 Tomas 提出的问题,他必须说他很有经验 :) 我想知道他是否有理由选择 if 而不是 when 构造。跨度>
    • @RuneFS - 不是真的,没有理由。我只是从 Daniel 那里获取了原始代码,并且只做了最小的更改。在when 中指定条件对我来说听起来非常好(只要条件不太复杂以至于难以阅读)。
    【解决方案2】:

    使用带有累加器的内部函数可能是最简单的方法(作为奖励,它是尾递归)。

    let foobar() =
      let rec foobar acc =
        if (recursionIsEnded) then
            List.rev acc
        else 
            foobar ((10, 10)::acc)
      foobar []
    

    根据您的更新,我更喜欢可变的解决方案。类似的东西

    let GetSameColorNeighs(grid : Option<Ball> [,], row : int , col : int, color : Microsoft.Xna.Framework.Color) : List<int * int> =
        let results = ResizeArray()
        let rec loop (row, col) =
          if (row < 0 || col < 0 || row > MaxLineNumber - 1 || col > BallsPerLine - 1) then ()
          else
              let ball = grid.[row,col]
              match ball with 
                  |Some(ball) -> 
    
                      if (!ball.visited = false || not <| ball.color.Equals(color)) then
                          [row , col] //doesn't compile, not sure what to do here
                      else
                           ball.visited := true
                           results.Add(row, col)
                           loop(row + 1, col + 1) 
                           loop(row - 1, col - 1)
    
                  |None  -> ()
        loop(row, col)
        results |> Seq.toList
    

    【讨论】:

    • 我需要澄清一下。如果在 else 分支中我需要连接更多调用 foobar 的结果怎么办? (就像我编辑的示例中的 GetSameColorNeighs 一样)使用累加器的正确语法是什么?我该如何修改你的代码是 foobar 有一些参数?
    【解决方案3】:

    由于 foobar 返回一个列表,您要么必须附加列表,要么连接然后反转

    (foobar())@(10,10) 但我通常会这样做

    反过来

       (10,10)::(foobar()))
    

    然后在您返回时反转。上面的尾递归有问题,所以到 tweek 你最终会得到类似的东西:

    let foobar () : List<int * int> =
      let rec inner acc =
        if (recursionIsEnded) then
            list.Rev acc
        else 
            inner ((10, 10)::acc)
      inner []
    

    与将列表附加到列表相比,最终反转通常会导致更少的操作

    【讨论】:

      【解决方案4】:

      虽然它是一个更简单的函数,但我希望它有助于说明这一点,因为您通过两个列表进行递归以构建元组。该函数以 ([],[]) 形式将两个列表作为输入,并将每个元素作为一个元组配对到一个新列表中。

      let rec pairlists twolists = 
        match twolists  with
          | ([],[]) -> []
          | (x::xs, y::ys) -> 
                  (x,y)::pairlists(xs,ys)
      ;;
      

      给定两个长度相同的列表(如果它们的长度不同,你会得到一个错误),每次递归都会将前两个元素配对成一个元组,如 (x,y)。语法:

      (x,y)::pairlists(xs,ys)
      

      实际上只是说“用我们当前的元素 (x,y) 构建一个元组,然后继续用我们的其余列表 (xs,ys) 构建一个元组。

      运行:

      pairlists ([1;2;3], 1;2;3])
      

      你会得到输出:

      [(1,1);(2,2);(3;3)]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-02-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-22
        • 2020-02-16
        • 2011-10-26
        相关资源
        最近更新 更多