【问题标题】:Recursively iterating over an array in F#递归迭代 F# 中的数组
【发布时间】:2014-10-18 02:04:12
【问题描述】:

这是一个相当简单的请求,但我在使用 F# 的语法时遇到了问题。我需要创建一个函数来递归地迭代二维数组,每次满足条件时增加一个计数器。在这种情况下,该函数将 ID 作为参数,然后检查该 ID 在内部数组中出现的次数。我在想这样的事情:

let runner array count =  
    match count with  
    |0 -> (exit loop)  
    |n -> (perform function)  
        runner array count-1

对于递归迭代函数的一般结构。但是,有几件事我不确定。在 F# 中退出递归循环(即基本情况)的条件是什么?如何构造它以便遍历主数组和子数组以检查 ID?假设我不能使用可变函数,我该如何使计数器随着递归循环的每次运行而增加?任何帮助将不胜感激。

【问题讨论】:

  • “退出递归循环的条件是什么”。当您进入一个无法调用递归函数并返回一个值的分支时,这就是您的递归所能达到的程度。此时,如果函数是尾递归的,则您要么返回该值,要么有一个充满递归调用的堆栈,这些调用继续执行直到它们“折叠”成单个值。
  • “我怎样才能使计数器增加......”。这就是递归的全部意义所在。您可以构造函数,以便在下一个递归调用的位置有一个表达式,如1 + runner array count-1,并且该表达式在您完成递归时计算,或者您将计数器作为函数的参数并将递增的计数器传递给下一个递归调用(这是使函数尾递归的必要步骤)。

标签: arrays recursion f# iteration


【解决方案1】:

我发现 F# 中的 2D 数组有点尴尬,但你可以这样做:

let recursivelyCountID ID (matr : 'T[,]) =
    let rec dim1 acc index0 index1 =
        if index1 > matr.GetUpperBound 1 then
            acc
        else
            let newAcc = if matr.[index0, index1] = ID then acc+1 else acc
            dim1 newAcc index0 (index1+1)

    let rec dim0 acc index0 =
        if index0 > matr.GetUpperBound 0
        then acc
        else dim0 (acc + (dim1 0 index0 0)) (index0+1)

    dim0 (matr.GetLowerBound 0) (matr.GetLowerBound 1)

如果您要使用数组数组,则可以使用 F# 标准 Array 模块中的函数:

let IDsInInnerArray ID xs = xs |> Array.sumBy (fun i -> if i = ID then 1 else 0)
let totalIDs ID xss = xss |> Array.map (fun xs -> IDsInInnerArray ID xs) |> Array.sum

编辑:我错过了 2D 数组实现 IEnumerable 的事实让我自己很尴尬,所以之前的代码不是使用数组的数组,而是 2D 数组的直接函数:

let totalIDs ID (matr : 'T[,]) = 
    matr 
    |> Seq.cast<'T> 
    |> Seq.sumBy (fun i -> if i = ID then 1 else 0)

...使用 Seq.cast 函数作为 scrwtp 在他的回答中显示。

【讨论】:

    【解决方案2】:

    有一种更通用的方法可以做到这一点。你想折叠一个二维数组。它们没有在 Array2D 模块中定义折叠,但您可以编写一个。

    let fold f state (arr: 'a [,]) =
        Seq.cast<'a> arr
        |> Seq.fold f state
    

    我们在这里“作弊”了一点,cast 为我们提供了数组中元素的扁平序列,然后我们将其传递给常规折叠。所以现在你可以这样做了:

     array |> fold (fun counter e -> if cond e then counter+1 else counter) 0
    

    其中 cond 是您要检查的条件。

    【讨论】:

    • 所以,如果我理解正确的话,这会将主数组变成一个序列,对吧?但是 state 参数有什么作用呢?抱歉,我对 F# 的掌握仍然很薄弱。
    • 第一部分(演员表)。然后在现在平坦的序列上调用 Seq.fold。 Fold 对序列的每个元素 (e) 和一个累加器 (counter) 调用函数 f,该累加器返回一个新的累加器值,以便在下一次调用 f 时使用。 state 是累加器的初始值。因此,在您的情况下,初始状态为 0,然后在满足条件时向累加器添加 1(或不添加)。累加器的最终值是折叠的输出。
    • state参数是Linqs Aggregate: System.Linq.Enumerable.Aggregate(array |> Seq.cast, 0, fun counter e - > if cond e then counter+1 else counter)`
    【解决方案3】:

    有点笨拙:

     let count (x : 'a [,]) (e : 'a) =
        seq { for x in arr do yield x :?> 'a } |> Seq.where ((=) e) |> Seq.length
    

    数组实现了 GetEnumerator,因此这也适用于 Array3D 和 4D。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-20
      • 2020-03-21
      • 2017-02-02
      • 1970-01-01
      • 2013-11-11
      • 1970-01-01
      • 2012-10-17
      • 2019-12-10
      相关资源
      最近更新 更多