尝试根据先决条件从序列中获取全部或全部
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 None
或 Some
。如果序列的最后一个元素不满足条件,那么你需要先读取前面所有的元素,直到你知道你失败了。
你唯一能做的优化就是一旦找到第一个不满足条件的元素就可以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)
以下代码无法按预期运行:
// 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 None
或 Some
。如果序列的最后一个元素不满足条件,那么你需要先读取前面所有的元素,直到你知道你失败了。
你唯一能做的优化就是一旦找到第一个不满足条件的元素就可以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)