【问题标题】:Counters in functional programming函数式编程中的计数器
【发布时间】:2017-05-17 10:31:13
【问题描述】:

我构建了一个简单的函数,给定一个列表,返回一个列表列表。每个列表都必须排序。 例如:

 subOrd [4;4;10;20;5;30;6;10]      --> [[4;4;10;20];[5;30];[6;10]]
 subOrd [5;6;4;3;2;1]              --> [[5;6];[4];[3];[2];[1]]

这是我目前的解决方案,效果很好,除了一个细节:

let rec subOrd (l1: int list) :int list list =
    let rec aux2 (l2: int list) (l3: int list) :int list=
        match l2 with
        | []                                ->  []
        | [x]                               ->  [x]
        | x0::(x1::_ as xs) when x0 > x1    ->  x0::l3
        | x0::(x1::_ as xs) when x0 <= x1   ->  (x0::l3)@(aux xs l3)
    match l1 with
    | []                ->  []
    | x::xs             ->  (aux2 l1 [])::subOrd xs

它对最后一场比赛中的每个xs 重复该操作。通过将列表a4 提供给函数,我得到:

let a4 = [1; 3; 4; 7; 5; 6]
val it : int list list = [[1; 3; 4; 7]; [3; 4; 7]; [4; 7]; [7]; [5; 6]; [6]]

使用 C,我想我会用递增的计数器来索引数组。从概念上讲,例如xs.[i]。我找到了有关如何Increment value in F# 的信息,但我不确定从功能上解决这个问题的最佳方法。

非常感谢任何建议。

【问题讨论】:

  • 这里绝对不需要任何计数器或索引。
  • 这在我看来就像一个代码 kata 或课程练习,所以我不确定我应该放弃多少。

标签: list f# pattern-matching


【解决方案1】:

您可以使用List.foldBack 从右到左处理它,而不是使用计数器并从左到右处理列表,例如

let subOrd l =
    let acc x = function
    | [] -> [[x]]
    | (((y::_) as ys) :: ls) -> 
        if x <= y then ((x::ys)::ls)
        else [x]::ys::ls
    | _ -> failwith "Should never happen!"

    List.foldBack acc l []

【讨论】:

  • 我没有想到这样的方法。真的很有趣,谢谢!我要去练习一下。
  • @Worice 这当然是一个很好的解决方案,但它也至少从你开始的概念上跳了两个。第一种方法是使用两个累加器的直接递归,然后是fold,最后是foldback。按顺序查看它们可能更容易理解发生了什么。
【解决方案2】:

几个好处:尾递归、简洁、抽象和通用性。

尾递归

在从重复递归返回后有剩余工作进行处理始终是次优方法。它排除了尾调用编译器优化,从而递归调用实际上被编译成 goto 指令。堆栈将承受过度的压力,所有这些工作的返回地址仍有待完成,最终将溢出。 作为附加参数传递给函数的累加器解决了这个问题,因为它将携带现在在递归调用之前完成的处理结果。在这种情况下,它甚至可以兼作包含前一个元素的状态。是的,结果现在的顺序是错误的,但是再次反转它并没有错。

简洁

尝试以最简单的方式制定逻辑:我想根据应用于成对连续元素的比较函数的结果将列表分成块。这将需要两个匹配表达式,第三个用于“没有更多元素,我们完成了”的情况。

抽象和泛型

不要将自己局限于硬编码的用例,例如。 整数比较大于等于。对于涉及其他条件或其他数据类型的其他场景,您可能需要类似的功能。已经解决了基本功能将实现有利可图的代码重用。

let chunkWhile p xs =
    let rec aux = function
    | (y::_) as ys::yss, x::xs when p x y -> aux ((x::ys)::yss, xs)
    | yss, x::xs -> aux ([x]::yss, xs)
    | yss, [] -> List.rev <| List.map List.rev yss
    aux ([], xs)

[4;4;10;20;5;30;6;10]
|> chunkWhile (>=)
// val it : int list list = [[4; 4; 10; 20]; [5; 30]; [6; 10]]

解构累加器以检索上一次迭代的元素,并通过将当前和上一个元素传递给谓词函数来决定。如果条件成立,则将当前元素添加到当前块,这是累加器的第一个元素。否则,只要有要处理的元素,就创建一个新的单例列表并将其添加到累加器中。如果没有更多元素,则将累加器中的所有子列表反转并以相反的顺序返回。

【讨论】:

  • 非常感谢您的详细解释!
猜你喜欢
  • 1970-01-01
  • 2021-02-26
  • 2019-11-02
  • 2010-09-06
  • 1970-01-01
  • 1970-01-01
  • 2011-01-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多