F#构建一个列表/值数组+连续重复

mam*_*mcx 5 f#

我需要打包这样的数据:

let data = [1; 2; 2; 3; 2; 2; 2; 4]
let packed = [(1, 1); (2, 2); (3, 1); (2, 3); (4, 1)]
Run Code Online (Sandbox Code Playgroud)

每个项目说明下一个项目存在多少次.但是,它必须与不相邻的重复一起使用.

我可以用经典的命令式代码来解决这个问题,但是想知道这个功能是如何做到的.

此外,Seq.countBy不工作,因为它考虑了所有的价值观

Mar*_*ann 6

如果您已经拥有命令式版本,则可以按照一组小步骤来转换为递归实现.

递归

虽然我不知道您的命令式版本是什么样的,但这是一个递归版本:

let pack xs =
    let rec imp acc = function
    | [] -> acc
    | h::t ->
        match acc with
        | [] -> imp [(h, 1)] t
        | (i, count) :: ta ->
            if h = i
            then imp ((i, count + 1) :: ta) t
            else imp ((h, 1) :: (i, count) :: ta) t
    xs |> imp [] |> List.rev
Run Code Online (Sandbox Code Playgroud)

此功能具有类型'a list -> ('a * int) list when 'a : equality.它使用一个私有的"实现函数" imp来完成工作.这个函数是递归的,并且acc贯穿整个函数(称为).这个累加器是具有类型的结果列表('a * int) list.

如果累加器列表为空,则将原始列表(h)的头部以及计数1作为元组创建为更新累加器的唯一元素,并imp使用该更新的累加器递归调用该函数.

如果累加器已经包含至少一个元素,则通过模式匹配提取元素,并将该元组(i)中的元素进行比较h.如果h = i,累加器更新; 否则,会产生一个新的元组acc.但是,在这两种情况下,imp使用新的累加器递归调用.

您可以使用与原始元组相同的列表来调用它,如下所示:

> pack [1; 2; 2; 3; 2; 2; 2; 4];;
val it : (int * int) list = [(1, 1); (2, 2); (3, 1); (2, 3); (4, 1)]
Run Code Online (Sandbox Code Playgroud)

一旦你有一个递归版本,你经常有一个使用折叠版本的配方.在这种情况下,由于上述pack功能必须最终反转累加器(使用List.rev),因此右折叠是最合适的.在F#中,这是通过内置List.foldBack函数完成的:

let pack' xs =
    let imp x = function
        | (i, count) :: ta when i = x -> (i, count + 1) :: ta
        | ta -> (x, 1) :: ta
    List.foldBack imp xs []
Run Code Online (Sandbox Code Playgroud)

在这种情况下,传递给List.foldBack它的函数有点过于复杂而无法作为匿名函数传递,因此我选择将其定义为私有内部函数.它等同于imp上面函数使用的递归函数pack,但是你会注意到它不必递归地调用它自己.相反,它只需返回累加器的新值.

结果是一样的:

> pack' [1; 2; 2; 3; 2; 2; 2; 4];;
val it : (int * int) list = [(1, 1); (2, 2); (3, 1); (2, 3); (4, 1)]
Run Code Online (Sandbox Code Playgroud)

  • 很好的答案.在我看来,一些微小的变化可以使它更加清晰.你可以用`let imp x = function ...`替换`let imp x acc = match acc ...`,以避免命名你刚刚解构的标识符.更重要的是,您可以切换模式匹配情况的顺序,并使用`when`子句来统一`[]`和`x <> i`情况:`| (i,count):: ta当i = x - >(i,count + 1):: ta | ta - >(x,1):: ta`. (2认同)