尝试根据先决条件从序列中获取全部或全部

Try to get all or nothing from a sequence based on a precondition

以下代码无法按预期运行:

// Gets all or nothing. If predicate is false for at least one item 
// in the sequence, returns None. If seq is empty, returns the empty seq.
let tryGetAllOrNone predicate (source: seq<_>) =
    let mutable condition = true
    let resultSeq =
        seq {
            use e = source.GetEnumerator()
            while condition && e.MoveNext() && (condition <- predicate e.Current; condition) do
                yield e.Current
        }
    if condition then Some resultSeq
    else None

原因很清楚:一个序列被延迟求值,这意味着这里 if 语句将首先被求值,return 序列。然后,当我们使用结果序列时,我们将始终得到 Some 结果,直到条件变为 false:

// expect: None (predicate is false for the first item)
> [1;2;3] |> tryGetAllOrNone (fun x -> x = 2);;
val it : seq<int> option = Some (seq [])

// expect None (predicate is false for the second item)
> [1;2;3] |> tryGetAllOrNone (fun x -> x = 1);;
val it : seq<int> option = Some (seq [1])

// correct (predicate is always true)
> [1;2;3] |> tryGetAllOrNone (fun x -> x > 0);;
val it : seq<int> option = Some (seq [1; 2; 3])

我可能只需要先使用序列,即使用 [...yield ...] 而不是 seq { .. yield ...},但也许有一个更简单的解决方案可以保留惰性(只是问这个问题听起来很倒退,所以我的直觉告诉我:先消费,对吧)?

编辑:考虑这个问题的时间有点长,我得出的结论是我所问的是不可能的。您不能先从一个序列中一个接一个地懒惰 return,然后在遇到无效项目时说:"hey, all those items you got thus far, give them back, if one is invalid, all are invalid!".

尽管如此,还是把它留在这里,以防它对别人有帮助,或者万一有人有更好的主意;)。

你是对的,一般来说,你需要遍历整个序列,直到你可以 return NoneSome。如果序列的最后一个元素不满足条件,那么你需要先读取前面所有的元素,直到你知道你失败了。

你唯一能做的优化就是一旦找到第一个不满足条件的元素就可以returnNone。这比构建整个序列然后检查任何元素的条件是否为假要好一些(因为如果一个较早的元素不满足条件,您可以 return None 更快) .下面的实现是这样做的:

// Gets all or nothing. If predicate is false for at least one item 
// in the sequence, returns None. If seq is empty, returns the empty seq.
let tryGetAllOrNone predicate (source: seq<_>) =
    let results = ResizeArray<_>()
    let mutable failed = false
    use e = source.GetEnumerator()
    while not failed && e.MoveNext() do
      if not (predicate e.Current) then failed <- true
      else results.Add(e.Current)
    if failed then None 
    else Some(seq results)