【问题标题】:What is the equivalent of Scala's Seq.Span in F#?F# 中的 Scala 的 Seq.Span 等价物是什么?
【发布时间】:2019-06-28 23:37:03
【问题描述】:

引用Scala's documentation

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

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

注意:c span p 等价于(但可能比)(c takeWhile p, c dropWhile p),前提是谓词 p 的评估不会导致任何副作用。

注意:可能会针对不同的运行返回不同的结果,除非对底层集合类型进行了排序。

  • p - 测试谓词
  • 返回 - 由该可迭代集合的最长前缀(其元素都满足 p)和该可迭代集合的其余部分组成的一对。
  • 定义类 - IterableOps → IterableOnceOps
  • 注意 - 重用:调用此方法后,应丢弃调用它的迭代器,并仅使用返回的迭代器。使用旧迭代器是未定义的,可能会发生变化,并且也可能导致对新迭代器的更改。

在查看 Seq 的 F# documentation 时,我没有看到任何等价物。

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])

【问题讨论】:

标签: .net scala f#


【解决方案1】:

您的解释不是 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)

【讨论】:

  • 由于您正在构建 ResizeArrays,您的解决方案似乎不适用于无限序列,它们最终会耗尽内存。
  • 我还在我的帖子中添加了其他信息,以解释 (takeWhile, skipWhile) 如何提供完全相同的扫描结果,但它需要两次而不是一次。
  • 是的,我的实现实现了序列,而不是懒惰地评估它。我应该有准确的。我用惰性换取了单次迭代和副作用安全性(想象一下,如果在此之前的操作之一是关于数据库调用的)。不幸的是,由于序列的惰性,似乎不可能将它们放在一起(?)。
  • 你正在缓冲左右两边,我认为会有一个解决方案只缓冲左边(takWhile)和懒惰(skipWhile),我不喜欢这个但它比缓冲它们,也只能迭代一次。
【解决方案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&lt;seq&lt;int&gt;&gt;

【讨论】:

  • 也许我太密集了,但是我不知道如何使用这个函数来实现扫描。我在帖子中添加了其他信息以进一步解释扫描。
  • 我确实明白你的意思,但故意提供一些与 span 不同的东西。根据我的经验,像 span 这样的东西经常被递归使用。此功能提供了在更高级别上工作的选项,每一步都生成一个集合,这对我来说通常更容易理解。这可能对您有用,具体取决于您尝试使用 span 解决的问题。
【解决方案3】:

这就是我想出的,这不是一个很好的答案,因为它需要你急切地迭代第一个,所以如果你返回 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)

【讨论】:

    【解决方案4】:

    看起来你想要类似下面的东西,你只需将相同的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]
    

    等等

    【讨论】:

    • 这不是我要找的,这看起来更像是分区,但需要多次迭代,我也不是反对你的人。
    猜你喜欢
    • 2018-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-15
    • 2012-11-25
    • 2010-11-13
    • 2014-03-11
    • 1970-01-01
    相关资源
    最近更新 更多