F#通过运行总计列出组?
下面是按第一项排序的元组列表。我想把时间按顺序排列F#通过运行总计列出组?,f#,F#,下面是按第一项排序的元组列表。我想把时间按顺序排列 如果元组的第二项大于50,则它将位于自己的集群中 否则,对总和小于50的项进行聚类 订单无法更改 代码: 预期值将为 [["ACE"] // 78 ["AMR"; "Aam"; "Acc"; "Adj"; "Aga"; "All"] // 47 ["Ame"] // 4 ["Amo"] // 60 ....] 理想情况下,我希望平均分配第二组([“AMR”;“Aam”;“Acc”;“Adj”;“Aga”;“All”],它们的总和为47)
[["ACE"] // 78
["AMR"; "Aam"; "Acc"; "Adj"; "Aga"; "All"] // 47
["Ame"] // 4
["Amo"] // 60
....]
理想情况下,我希望平均分配第二组([“AMR”;“Aam”;“Acc”;“Adj”;“Aga”;“All”]
,它们的总和为47)和第三组([“Ame”]
,它们只有4个)
如何在F#中实现它
我有以下的解决方案。它使用可变变量。这不是F#惯用语?是为。。。F#中的祈使句是还是某种函数结构的语法糖
seq {
let mutable c = []
for v in values |> Seq.sortBy(fun (k, _) -> k) do
let sum = c |> Seq.map(fun (_, v) -> v) |> Seq.sum
if not(c = []) && sum + (snd v) > 50
then
yield c
c <- [v]
else
c <- List.append c [v]
}
seq{
设可变c=[]
对于v值|>顺序排序(fun(k,|)->k)do
设sum=c |>Seq.map(fun(u,v)->v)|>Seq.sum
如果不是(c=[])和&sum+(snd v)>50
然后
产量c
我想我明白了。这不是最好的代码,但它是有效的,是不变的
let foldFn (acc:(string list * int) list) (name, value) =
let addToLast last =
let withoutLast = acc |> List.filter ((<>) last)
let newLast = [((fst last) @ [name]), (snd last) + value]
newLast |> List.append withoutLast
match acc |> List.tryLast with
| None -> [[name],value]
| Some l ->
if (snd l) + value <= 50 then addToLast l
else [[name], value] |> List.append acc
values |> List.fold foldFn [] |> List.map fst
let foldFn(acc:(字符串列表*int)列表)(名称、值)=
让addToLast最后=
let withoutLast=acc |>List.filter(()last)
让newLast=[((fst last)@[name]),(snd last)+value]
newLast |>List.append withoutLast
将acc |>List.tryLast与
|无->[[名称],值]
|一些l->
如果(snd l)+值列表。附加acc
值|>List.foldFn[]|>List.map fst
更新:由于追加可能是非常昂贵的操作,我添加了prepend-only版本(仍然满足维持订单的原始要求)
let foldFn(acc:(字符串列表*int)列表)(名称、值)=
让addToLast最后=
让withoutLast=acc |>List.filter(()last)|>List.rev
设newLast=((fst last)@[name]),(snd last)+值
(newLast::withoutLast)|>List.rev
将acc |>List.tryLast与
|无->[[名称],值]
|一些l->
如果(snd l)+值列表.rev
注意:第4行仍然有@
运算符(在集群中创建新名称列表时),但由于集群中的理论最大名称数量为50(如果所有名称都等于1),因此此处的性能可以忽略不计
如果删除最后一行中的List.map fst
,您将获得列表中每个集群的总和值。附加操作的成本很高。即使在处理后需要反转列表,带有预加中间结果的直接折叠也更便宜
["ACE", 78; "AMR", 3; "Aam", 6; "Acc", 1; "Adj", 23; "Aga", 12; "All", 2; "Ame", 4; "Amd", 6; "Amo", 60]
|> List.fold (fun (r, s1, s2) (t1, t2) ->
if t2 > 50 then [t1]::s1::r, [], 0
elif s2 + t2 > 50 then s1::r, [t1], t2
else r, t1::s1, s2 + t2 ) ([], [], 0)
|> fun (r, s1, _) -> s1::r
|> List.filter (not << List.isEmpty)
|> List.map List.rev
|> List.rev
// val it : string list list =
// [["ACE"]; ["AMR"; "Aam"; "Acc"; "Adj"; "Aga"; "All"]; ["Ame"; "Amd"];
// ["Amo"]]
[“ACE”,78;“AMR”,3;“Aam”,6;“Acc”,1;“Adj”,23;“Aga”,12;“All”,2;“Ame”,4;“Amd”,6;“Amo”,60]
|>列表。折叠(乐趣(r,s1,s2)(t1,t2)->
如果t2>50,则[t1]::s1::r,[],0
如果s2+t2>50,则s1::r,[t1],t2
else r,t1::s1,s2+t2)([],[],0)
|>乐趣(r,s1,)->s1::r
|>List.filter(非List.map List.rev
|>List.rev
//val it:字符串列表=
//[“ACE”];[“AMR”;“Aam”;“Acc”;“Adj”;“Aga”;“All”];[“Ame”;“Amd”];
//[“Amo”]]
这是一个递归版本-工作方式与折叠版本基本相同:
let groupBySums data =
let rec group cur sum acc lst =
match lst with
| [] -> acc |> List.where (not << List.isEmpty) |> List.rev
| (name, value)::tail when value > 50 -> group [] 0 ([(name, value)]::(cur |> List.rev)::acc) tail
| (name, value)::tail ->
match sum + value with
| x when x > 50 -> group [(name, value)] 0 ((cur |> List.rev)::acc) tail
| _ -> group ((name, value)::cur) (sum + value) acc tail
(data |> List.sortBy (fun (name, _) -> name)) |> group [] 0 []
values |> groupBySums |> List.iter (printfn "%A")
让groupBySums数据=
let rec group cur sum acc lst=
匹配lst与
|[]->acc |>List.where(非List.rev
|(名称,值)::值>50时的尾部->组[]0([(名称,值)]::(cur |>List.rev)::acc)尾部
|(名称、值)::tail->
将总和+值与
|当x>50->group[(名称、值)]0((cur |>List.rev)::acc)尾部时的x
|组((名称、值)::cur(总和+值)acc tail
(data |>List.sortBy(fun(name,|)->name))|>group[]0[]
values |>groupBySums |>List.iter(printfn“%A”)
使用Seq.mapFold
和Seq.groupBy
的另一种解决方案:
let group values =
values
|> Seq.mapFold (fun (group, total) (name, count) ->
let newTotal = count + total
let newGroup = group + if newTotal > 50 then 1 else 0
(newGroup, name), (newGroup, if newGroup = group then newTotal else count)
) (0, 0)
|> fst
|> Seq.groupBy fst
|> Seq.map (snd >> Seq.map snd >> Seq.toList)
像这样调用它:
[ "ACE", 78
"AMR", 3
"Aam", 6
"Acc", 1
"Adj", 23
"Aga", 12
"All", 2
"Ame", 4
"Amo", 60
]
|> group
|> Seq.iter (printfn "%A")
// ["ACE"]
// ["AMR"; "Aam"; "Acc"; "Adj"; "Aga"; "All"]
// ["Ame"]
// ["Amo"]
当你说你想“平均分配”第二组时,你能举个例子说明你的意思吗?因为我可以看出你的意思有两件事:1)你希望字符串的数量或多或少是均匀的(例如,第一组中的[“AMR”;“Aam”;“Acc”]
和[“Adj”;“Aga”;“All”;“Ame”]
在第二组中,因为这是一个3-4的分布)。或者2)您希望第一组和第二组之间的总值尽可能接近(这将使[“AMR”;“Aam”;“Acc”;“Adj”]
在第一组中,总计33,而[“Aga”;“All”;“Ame”]
在第二组中,总计18).你想要哪一个?顺便说一句,仅仅因为某些东西在构建结果时在内部使用了可变变量,并不意味着它是非惯用的F#。重要的是,你不会改变其他函数可以看到的任何数据。使用可变变量构建不可变的结果是F#中非常常见的模式。(2)是我想要的。我希望值分布更均匀。要回答您刚才在编辑中提出的问题:for…do
in F#可以是命令式的,也可以是语法糖。当它出现在普通代码中时是命令式的,但当它出现在计算表达式中时(如seq{…}
)然后它是一个函数构造的语法糖。顺便说一句,你写了seq[…]
,但那应该是seq{…}
。通过写seq[…]
你在构建一个列表,然后将其转换为seq,这是低效的。你不能在seq.iter()
中使用yield
,因为seq.iter()
不是计算表达式。你也不能在for…do
中使用yield
,除非for…do
在计算表达式中(在这种情况下,它类似于Haskell的命令式语句:它只是函数构造的语法糖).计算表达式更改了…do
的含义,并允许收益率
。请注意,在Seq.map()
中也不能使用收益率
let group values =
values
|> Seq.mapFold (fun (group, total) (name, count) ->
let newTotal = count + total
let newGroup = group + if newTotal > 50 then 1 else 0
(newGroup, name), (newGroup, if newGroup = group then newTotal else count)
) (0, 0)
|> fst
|> Seq.groupBy fst
|> Seq.map (snd >> Seq.map snd >> Seq.toList)
[ "ACE", 78
"AMR", 3
"Aam", 6
"Acc", 1
"Adj", 23
"Aga", 12
"All", 2
"Ame", 4
"Amo", 60
]
|> group
|> Seq.iter (printfn "%A")
// ["ACE"]
// ["AMR"; "Aam"; "Acc"; "Adj"; "Aga"; "All"]
// ["Ame"]
// ["Amo"]