【问题标题】:Is there a standard F# function for separating a sequence of Choices?是否有用于分隔选择序列的标准 F# 函数?
【发布时间】:2021-03-22 04:14:32
【问题描述】:

我正在寻找一个标准的 F# 函数,它接受一个二选一序列并返回一对序列:

let separate (choices : seq<Choice<'T1, 'T2>>) : seq<'T1> * seq<'T2> = ...

一个简单的实现非常简单:

let separate choices =
    let ones =
        choices
            |> Seq.choose (function
                | Choice1Of2 one -> Some one
                | _ -> None)
    let twos =
        choices
            |> Seq.choose (function
                | Choice2Of2 two -> Some two
                | _ -> None)
    ones, twos

这工作正常,但迭代序列两次,这不太理想。此函数是否在半标准库之一中定义?我环顾四周,但找不到。 (如果它存在,我相信它会使用其他名称。)

对于奖励积分,适用于 3 选择、4 选择等的版本也很好,ListArray 等的版本也很好。谢谢。

【问题讨论】:

  • 在 Haskell 中是 partitionEithersunzipEithers (hoogle),但我在 FSharpx 或 FSharpPlus 中找不到任何具有此类签名的东西
  • 啊,我认为你是对的。看起来partitionMap id 将匹配我的要求,(尽管它不支持序列)。感谢您找到它!

标签: f#


【解决方案1】:

我找不到内置实现,但可以自己编写。 它使用基于IEnumerator&lt;&gt; 的方法,因此它适用于任何集合类型,但不是最佳的(例如,数组的工作速度会比可能的慢)。顺序颠倒了(使用ResizeArray 很容易修复,但代码更多)。这个版本也不是懒惰的,但可以很容易地适应Choice&lt;'a, 'b, 'c&gt;和其他人的工作

let splitChoices2 (choices: Choice<'a, 'b> seq) =
    let rec inner (it: IEnumerator<_>) acc1 acc2 =
        if it.MoveNext() then
            match it.Current with
            | Choice1Of2 c1 -> inner it (c1 :: acc1) acc2
            | Choice2Of2 c2 -> inner it acc1 (c2 :: acc2)
        else
            acc1, acc2

    inner (choices.GetEnumerator()) [] []

let choices = [
        Choice1Of2 11
        Choice2Of2 "12"
        Choice1Of2 21
        Choice2Of2 "22"
    ]
choices |> splitChoices2 |> printfn "%A"

更新:基于ResizeArray 的方法没有颠倒的顺序和可能更便宜的枚举

let splitChoices2 (choices: Choice<'a, 'b> seq) =
    let acc1 = ResizeArray()
    let acc2 = ResizeArray()
    
    for el in choices do
        match el with
        | Choice1Of2 c1 -> acc1.Add c1
        | Choice2Of2 c2 -> acc2.Add c2

    acc1, acc2

【讨论】:

  • 谢谢。我喜欢第二个版本,但会保持开放一段时间,看看其他人是否有想法。
【解决方案2】:

这有点受 TraverseA 的启发,但结果却大不相同。这是一个单遍解决方案(更新:但是,虽然核心算法可能是从 List 到 List 的单遍,但让它与您的类型签名相匹配,并以相同的方式对结果进行排序使其成为 3*O(n),这取决于排序和类型签名对您有多重要)

let choices = seq {Choice1Of2(1) ; Choice2Of2(2) ; Choice2Of2(3) ; Choice1Of2(4)}
let seperate' choices  = 
    let rec traverse2ChoicesA tupleSeq choices = 
        match choices with
        | [] -> fst tupleSeq |> List.rev |>Seq.ofList , snd tupleSeq |> List.rev |> Seq.ofList  
        | (Choice1Of2 f)::tl -> traverse2ChoicesA (f::fst tupleSeq, snd tupleSeq) tl
        | (Choice2Of2 s)::tl -> traverse2ChoicesA (fst tupleSeq, s::snd tupleSeq) tl
    traverse2ChoicesA ([],[]) <| List.ofSeq choices
 
seperate' choices;;

val seperate' : choices:seq<Choice<'a,'b>> -> seq<'a> * seq<'b>
val it : seq<int> * seq<int> = ([1; 4], [2; 3])

更新:需要明确的是,如果 ordering 和 List 而不是 Seq 是可以的,那么这是一次通过:

let choices = [Choice1Of2(1) ; Choice2Of2(2) ; Choice2Of2(3) ; Choice1Of2(4)]

let seperate' choices  = 
    let rec traverse2ChoicesA (tupleSeq) choices = 
        match choices with
        | [] -> tupleSeq
        | (Choice1Of2 f)::tl -> traverse2ChoicesA (f :: fst tupleSeq, snd tupleSeq) tl
        | (Choice2Of2 s)::tl -> traverse2ChoicesA (fst tupleSeq, s:: snd tupleSeq) tl
    traverse2ChoicesA ([],[]) choices

seperate' choices;;

val choices : Choice<int,int> list =
  [Choice1Of2 1; Choice2Of2 2; Choice2Of2 3; Choice1Of2 4]
val seperate' : choices:Choice<'a,'b> list -> 'a list * 'b list
val it : int list * int list = ([4; 1], [3; 2])

您可能会在使用 TraverseA 的 FSharpPlus“半标准”库中找到更通用、更高效且具有适当类型签名的东西?

【讨论】:

  • 实际上这不是单通解决方案。时间复杂度是 O(3*n),即使科学上它仍然是 O(n)。一次将 seq 转换为列表,一次迭代结果列表,两次将一半集合转换为反向顺序。
  • 我这样做是为了匹配 OP 的签名。但是,可能不需要列表到 Seq 的转换和反转。这就是我的意思,我会更新这个答案,谢谢
猜你喜欢
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 2023-03-22
  • 1970-01-01
  • 1970-01-01
  • 2013-02-07
  • 2010-09-27
  • 2022-11-23
相关资源
最近更新 更多