F#中Scala的Seq.Span等于多少?

时间:2019-06-28 23:37:03

标签: .net scala f#

引用Scala's documentation

def span(p: (A) => Boolean): (Seq[A], Seq[A])

根据谓词将此可迭代集合拆分为前缀/后缀对。

请注意:只要对谓词p的求值没有引起任何副作用,c span p等效于(但可能比c跨度更有效),但对谓词p的评估不会造成任何副作用。

注意:对于不同的运行,可能会返回不同的结果,除非已订购基础集合类型。

  • p-测试谓词
  • 返回-一对,其元素均满足p的此可迭代集合的最长前缀,以及此可迭代集合的其余前缀。
  • 定义类-IterableOps→IterableOnceOps
  • 注意-重用:调用此方法后,应丢弃被调用的迭代器,而仅使用返回的迭代器。使用旧的迭代器是不确定的,可能会发生更改,并且可能还会导致新的迭代器发生更改。

查看F# documentation的Seq时,看不到任何等效内容。

groupBy,partition,splitAt,它们都不符合我要执行的操作。就像同时执行一个takeWhile和skipWhile一样,但是不需要两个迭代,您只需要一个迭代即可使函数返回一个元组(takeWhile,skipWhile)。

输出应符合以下功能

module List
let span (predicate: 'a -> bool) (list: 'a list): ('a list * 'a list) =
    (list |> List.takeWhile predicate, list |> List.skipWhile predicate)

但是只需要进行一次迭代,因为我的序列可能是无限的。

[1;2;3;4] |> List.span (fun i -> i % 2 = 1) => ([1], [2;3;4])

4 个答案:

答案 0 :(得分:3)

您的解释不是Seq.span在Scala中的作用:它将一个序列一分为二,只要谓词函数返回true,就将所有输入元素放入第一个元组值。一旦函数返回false,所有剩余元素将被推入第二个元组值。

F#中的示例如下:

[1;2;3;4] |> Seq.span (fun i -> i % 2 = 1) => ([1], [2;3;4])

使用相互递归函数可以很容易地实现这一点:

 
module Seq

open System.Collections.Generic

let span (predicate: 'a -> bool) (seq: 'a seq): ('a seq * 'a seq) =
    let rec insertLeft predicate (e: IEnumerator<'a>) (left: ResizeArray<'a>) (right: ResizeArray<'a>) =
        if e.MoveNext() then
            if predicate e.Current then
                left.Add e.Current
                insertLeft predicate e left right
            else
                // once predicate returned false, all consecutive elements land in right list
                right.Add e.Current 
                insertRight e right
    and insertRight (e: IEnumerator<'a>) (right: ResizeArray<'a>) =
        if e.MoveNext() then 
            right.Add e.Current
            insertRight e right
    let left = ResizeArray<_>()
    let right = ResizeArray<_>()
    use enumerator = seq.GetEnumerator()
    insertLeft predicate enumerator left right
    (upcast left, upcast right)

答案 1 :(得分:2)

我有一个辅助函数,对于这种事情我觉得非常有用:

module Seq =
    let groupAdjacentBy f xs =
        let mutable prevKey, i = None, 0
        xs
        |> Seq.groupBy (fun x ->
            let key = f x
            if prevKey <> Some key then
                i <- i + 1
                prevKey <- Some key
            (i, key))
        |> Seq.map (fun ((_, k), v) -> (k, v))

请注意,它在实现中使用局部包含的突变,因为这是重用现有Seq.groupBy的最简单方法。

这实际上是一个分组功能,但仅在项目彼此相邻时才将它们放在同一组中。在我看来,这是解决需要takeWhileskipWhile多次使用的问题的一种非常通用的方法,但更简单,因为它们都是一次性完成的。分组功能返回任何类型的组密钥,而不仅仅是布尔值,从而增加了灵活性。

这是一个示例函数,该函数使用返回布尔值的分组函数:

[ 1; 2; -1; -2; 3; 4; -5 ]
|> Seq.groupAdjacentBy (fun x -> x > 0) // positive?
|> Seq.map snd
// seq [seq [1; 2]; seq [-1; -2]; seq [3; 4]; seq [-5]]

在此示例中,前两行返回具有其键的组(分别为truefalsetruefalse)。然后,可以在逻辑中使用这些键,但是如果您不关心它们,那么Seq.map snd将丢弃它们。上面显示的是seq<seq<int>>

答案 2 :(得分:1)

这是我想出的,这不是一个很好的答案,因为它要求您急切地迭代第一个,所以如果您返回true足够长的时间,您将失去记忆。我将让问题再开放几天,如果没有更好的答案,我会标记为这个问题。再一次,我真的希望有一个更好的答案,它在任何情况下都可以与无限序列完全兼容。

module Seq

let span (predicate: 'a -> bool) (sequence: 'a seq): ('a seq * 'a seq) =
    let enumerator = sequence.GetEnumerator()
    let isNotDone = ref (enumerator.MoveNext())
    let first = seq {
        let e = enumerator
        if !isNotDone then
            while (!isNotDone && predicate e.Current) do
                yield enumerator.Current
                isNotDone := e.MoveNext() }
    let second = seq {
        use e = enumerator
        if !isNotDone then
            yield e.Current
            while e.MoveNext() do
                yield e.Current }
    let eagerFirst = List.toSeq (Seq.toList first)
    (eagerFirst, second)

答案 3 :(得分:0)

看起来您想要以下内容,只需将相同的Seq(或List或其他任何东西)两次传递给filter,如下所示:

//Returns a tuple of seq<'a> * seq<'a> (which looks like what you want).
let scalaSpanTuple test sq = (Seq.filter test sq, Seq.filter (test>>not) sq)

或:

//Returns a list of 2 lists of 'a
let scalaSpanList test ls = [List.filter test ls; List.filter (test>>not) ls]