我需要打包这样的数据:
let data = [1; 2; 2; 3; 2; 2; 2; 4]
let packed = [(1, 1); (2, 2); (3, 1); (2, 3); (4, 1)]
每个项目表示下一个项目存在多少次。但是,它必须与不相邻的副本一起使用。
我可以使用经典的命令式代码来解决这个问题,但是想知道这是怎么做的。
此外,Seq.countBy
无效,因为它会考虑所有值
答案 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)]