如何以惯用的方式基于另一个序列在F#中拆分序列

时间:2017-03-29 18:22:41

标签: f# sequences

我在F#中有2个序列,每个序列都包含不同的整数,严格按升序排列:listMaxesnumbers

如果not Seq.isEmpty numbers,则保证not Seq.isEmpty listMaxesSeq.last listMaxes >= Seq.last numbers

我想在F#中实现一个函数,该函数返回一个整数列表的列表,其List.length等于Seq.length listMaxes,包含numbers的元素除以列表,其中元素listMaxes限制每组。

例如:使用参数

调用
listMaxes = seq [ 25; 56; 65; 75; 88 ]
numbers = seq [ 10; 11; 13; 16; 20; 25; 31; 38; 46; 55; 65; 76; 88 ]

此功能应该返回

[ [10; 11; 13; 16; 20; 25]; [31; 38; 46; 55]; [65]; List.empty; [76; 88] ]

我可以实现这个功能,只在numbers上迭代一次:

let groupByListMaxes listMaxes numbers = 
    if Seq.isEmpty numbers then
        List.replicate (Seq.length listMaxes) List.empty
    else
        List.ofSeq (seq {
            use nbe = numbers.GetEnumerator ()
            ignore (nbe.MoveNext ())
            for lmax in listMaxes do
                yield List.ofSeq (seq {
                    if nbe.Current <= lmax then
                        yield nbe.Current
                    while nbe.MoveNext () && nbe.Current <= lmax do
                        yield nbe.Current
                })
        })

但是这段代码感觉不洁,丑陋,势在必行,而且非常不F#-y。

是否有任何功能性/ F#-idiomatic方法来实现这一目标?

4 个答案:

答案 0 :(得分:3)

这是一个基于列表解释的版本,其风格非常实用。只要您想处理它,就可以使用'array'在它们之间进行转换。如果您只想使用库函数,也可以将Seq.toListSeq.scan结合使用,但要注意,在执行此操作时,在计算或内存中引入二次复杂度非常容易

这两者都是线性的:

Seq.partition ((>=) max)

答案 1 :(得分:2)

可以使用模式匹配和尾递归这样做:

let groupByListMaxes listMaxes numbers =
    let rec inner acc numbers =
        function
        | [] -> acc |> List.rev
        | max::tail ->
            let taken = numbers |> Seq.takeWhile ((>=) max) |> List.ofSeq
            let n = taken |> List.length
            inner (taken::acc) (numbers |> Seq.skip n) tail

    inner [] numbers (listMaxes |> List.ofSeq)

更新:我也受到了fold的启发,并提出了以下解决方案,严格禁止转换输入序列。

let groupByListMaxes maxes numbers =
    let rec inner (acc, (cur, numbers)) max = 
        match numbers |> Seq.tryHead with
        // Add n to the current list of n's less
        // than the local max
        | Some n when n <= max ->
            let remaining = numbers |> Seq.tail  
            inner (acc, (n::cur, remaining)) max
        // Complete the current list by adding it
        // to the accumulated result and prepare
        // the next list for fold.
        | _ ->
            (List.rev cur)::acc, ([], numbers)

    maxes |> Seq.fold inner ([], ([], numbers)) |> fst |> List.rev

答案 2 :(得分:1)

我自己找到了更好的实施方案。仍然欢迎改进提示。

处理2个序列真的很痛苦。而且我确实只希望迭代numbers一次而不将该序列转换为列表。但后来我意识到转listMaxes(通常是较短的序列)成本较低。这样只剩下1个序列,我可以Seq.fold使用numbers

在使用Seq.fold而不是numbers进行迭代时,我们希望保留和更改的状态是什么?首先,它应该包括listMaxes的剩余部分,但我们已经超越的先前的最大值不再是感兴趣的。第二,到目前为止累积的清单,尽管与其他答案一样,这些可以保持相反的顺序。更重要的是:州是一对夫妇,其中第二个元素是反向列表的反向列表。

let groupByListMaxes listMaxes numbers =
    let rec folder state number =
        match state with
            | m :: maxes, _ when number > m ->
                folder (maxes, List.empty :: snd state) number
            | m :: maxes, [] ->
                fst state, List.singleton (List.singleton number)
            | m :: maxes, h :: t ->
                fst state, (number :: h) :: t
            | [], _ ->
                failwith "Guaranteed not to happen"
    let listMaxesList = List.ofSeq listMaxes
    let initialState = listMaxesList, List.empty
    let reversed = snd (Seq.fold folder initialState numbers)
    let temp = List.rev (List.map List.rev reversed)
    let extraLength = List.length listMaxesList - List.length temp
    let extra = List.replicate extraLength List.empty
    List.concat [temp; extra]

答案 3 :(得分:0)

我知道这是一个老问题,但是我有一个非常相似的问题,我认为这是一个简单的解决方案:

let groupByListMaxes cs xs =
    List.scan (fun (_, xs) c -> List.partition (fun x -> x <= c) xs)
        ([], xs)
        cs
    |> List.skip 1
    |> List.map fst