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)

下面是按第一项排序的元组列表。我想把时间按顺序排列

  • 如果元组的第二项大于50,则它将位于自己的集群中
  • 否则,对总和小于50的项进行聚类
  • 订单无法更改
  • 代码:

    预期值将为

    [["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"]