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

时间:2015-12-28 00:35:55

标签: f#

我需要打包这样的数据:

let data = [1; 2; 2; 3; 2; 2; 2; 4]
let packed = [(1, 1); (2, 2); (3, 1); (2, 3); (4, 1)]

每个项目表示下一个项目存在多少次。但是,它必须与不相邻的副本一起使用。

我可以使用经典的命令式代码来解决这个问题,但是想知道这是怎么做的。

此外,Seq.countBy无效,因为它会考虑所有值

3 个答案:

答案 0 :(得分:6)

如果您已经拥有命令式版本,则可以follow a set of small steps to refector to a recursive implementation

递归

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

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

此函数的类型为'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)]

折叠

一旦你有一个递归版本,你经常会得到一个使用折叠的版本的配方。在这种情况下,由于上述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 []

在这种情况下,传递给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)]

答案 1 :(得分:1)

我的解决方案假定data集合是一个列表。如果将它作为一个元组(根据你的例子)是故意的,那么为了我的解决方案工作,元组必须转换为一个列表(一个例子,如何找到它here)。

let groupFunc list = 
    let rec groupFuncRec acc lst init count =
        match lst with
        | [] -> List.rev acc
        | head::[] when head = init
            -> groupFuncRec ((init, count)::acc) [] 0 0
        | head::[] when head <> init
            -> groupFuncRec ((head, 1)::acc) [] 0 0
        | head::tail when head = init 
            -> groupFuncRec acc tail head (count+1)
        | head::tail when head <> init
            -> groupFuncRec ((init, count)::acc) tail head 1
    let t = List.tail list
    let h = List.head list
    groupFuncRec [] t h 1

当我对您的样本数据运行该函数时,我得到了预期的结果:

 list = [(1, 1); (2, 2); (3, 1); (4, 1)]

答案 2 :(得分:1)

您可以通过在其参数中包含一些位置信息来使Seq.countBy工作。当然,您需要映射回原始数据。

[1; 2; 2; 3; 2; 2; 2; 4]
|> Seq.scan (fun (s, i) x ->
    match s with
    | Some p when p = x -> Some x, i
    | _ -> Some x, i + 1 ) (None, 0)
|> Seq.countBy id
|> Seq.choose (function 
| (Some t, _), n -> Some(t, n)
| _ -> None )
|> Seq.toList
// val it : (int * int) list = [(1, 1); (2, 2); (3, 1); (2, 3); (4, 1)]