如何中止扫描但保留序列的标志元素:skipToOrDefault
How do I abort a scan but retain the flag element of the sequence: skipToOrDefault
我有一个 Result
序列,我想累积所有 Error
值但中止处理和 return 找到的第一个 Ok
值。具体来说,我想中止处理列表的其余部分。不幸的是,我采用的方法保留了找到的第一个 Ok
,但不会中止处理列表的其余部分。
let process : Result<'t, string list> -> Result<'t, string list> =
let st0 = Error []
let acc st e =
match st, e with
| Ok _ , _ -> st
| _ , Ok _ -> e
| Error v, Error vs -> Error (v ++ vs)
Seq.scan acc st0
|> Seq.last
理想情况下,Seq.skipToOrDefault
和 Seq.takeToOrDefault
方法会很好。
从你的评论中,很明显你想做的是避免迭代整个序列,一旦遇到第一个Ok
.
好吧,默认情况下序列已经这样做了(它们是 惰性 ),并且 scan
函数保留了 属性。让我们检查一下:
let mySeq = seq {
for i in 0..3 do
printfn "Returning %d" i
yield i
}
mySeq |> Seq.toList |> ignore
> Returning 0
> Returning 1
> Returning 2
> Returning 3
mySeq |> Seq.take 2 |> Seq.toList |> ignore
> Returning 0
> Returning 1
mySeq
|> Seq.scan (fun _ x -> printfn "Scanning %d" x) ()
|> Seq.take 3
|> Seq.toList |> ignore
> Returning 0
> Scanning 0
> Returning 1
> Scanning 1
看:在scan
之后我们再也看不到“回归2”和“回归3”了。那是因为我们没有迭代整个序列,只有我们需要的部分,由 Seq.take 3
.
确定
但是 强制代码中的完整迭代的是 Seq.last
。毕竟,为了得到最后一个元素,你需要遍历整个序列,没有别的办法。
但是您可以做的是在需要时通过 Seq.takeWhile
停止迭代。此函数接受一个谓词,并且 return 仅包含谓词为 true
的元素,不包括产生 false
:
的第一个元素
mySeq |> Seq.takeWhile (fun x -> x < 2) |> Seq.toList |> ignore
> Returning 0
> Returning 1
> Returning 2
> val it : int list = [0; 1]
你的情况的困难在于你还需要 return 破坏谓词的元素。为了做到这一点,你可以部署一个小技巧:在折叠状态下保留一个特殊标志 stop: bool
,最初将其设置为 false
,然后立即切换到元素上的 true
在您需要停止的地方之后。为了保持这种状态,我将使用一条记录:
let st0 = {| prev = Error []; stop = false |}
let acc (s: {| prev: Result<_,string>; stop: bool |}) x =
match s.prev, x with
| Ok _, _ -> {| s with stop = true |} // Previous result was Ok => stop now
| _, Ok _ -> {| s with prev = x |} // Don't stop, but remember the previous result
| Error a, Error b -> {| s with prev = Error (a @ b) |}
sourceSequence
|> Seq.scan acc st0
|> Seq.takeWhile (fun s -> not s.stop)
|> Seq.last
|> (fun s -> s.prev)
P.S。另请注意,在 F# 中,列表串联是 @
,而不是 ++
。您来自 Haskell 吗?
我认为这是一个更好的解决方案。然而,对于 Seq.tryPick
是否总是无副作用而不管底层顺序如何,存在一些混淆。对于list
,这里需要Seq.tail
才能前进...
let rec scanTo (pred:'u -> bool) (acc:'u -> 'a -> 'u) (st0:'u) (ss:'a seq) = seq {
let q =
ss
|> Seq.tryPick Some
|> Option.bind (acc st0 >> Some)
match q with
| None -> yield! Seq.empty
| Some v when pred v -> yield v
| Some v -> yield v; yield! (scanTo pred acc v (Seq.tail ss))
}
例如...
let process : Result<'v, string list> seq -> Result<'v, string list> seq = fun aa ->
let mergeErrors acc e =
match acc, e with
| Error ms, Error m -> Error (m @ ms)
| _, Ok v -> Ok v
| _, Error m -> Error m
let st0 = Error []
let isOk = function
| Ok _ -> true
| _ -> false
scanTo isOk mergeErrors st0 aa
我有一个 Result
序列,我想累积所有 Error
值但中止处理和 return 找到的第一个 Ok
值。具体来说,我想中止处理列表的其余部分。不幸的是,我采用的方法保留了找到的第一个 Ok
,但不会中止处理列表的其余部分。
let process : Result<'t, string list> -> Result<'t, string list> =
let st0 = Error []
let acc st e =
match st, e with
| Ok _ , _ -> st
| _ , Ok _ -> e
| Error v, Error vs -> Error (v ++ vs)
Seq.scan acc st0
|> Seq.last
理想情况下,Seq.skipToOrDefault
和 Seq.takeToOrDefault
方法会很好。
从你的评论中,很明显你想做的是避免迭代整个序列,一旦遇到第一个Ok
.
好吧,默认情况下序列已经这样做了(它们是 惰性 ),并且 scan
函数保留了 属性。让我们检查一下:
let mySeq = seq {
for i in 0..3 do
printfn "Returning %d" i
yield i
}
mySeq |> Seq.toList |> ignore
> Returning 0
> Returning 1
> Returning 2
> Returning 3
mySeq |> Seq.take 2 |> Seq.toList |> ignore
> Returning 0
> Returning 1
mySeq
|> Seq.scan (fun _ x -> printfn "Scanning %d" x) ()
|> Seq.take 3
|> Seq.toList |> ignore
> Returning 0
> Scanning 0
> Returning 1
> Scanning 1
看:在scan
之后我们再也看不到“回归2”和“回归3”了。那是因为我们没有迭代整个序列,只有我们需要的部分,由 Seq.take 3
.
但是 强制代码中的完整迭代的是 Seq.last
。毕竟,为了得到最后一个元素,你需要遍历整个序列,没有别的办法。
但是您可以做的是在需要时通过 Seq.takeWhile
停止迭代。此函数接受一个谓词,并且 return 仅包含谓词为 true
的元素,不包括产生 false
:
mySeq |> Seq.takeWhile (fun x -> x < 2) |> Seq.toList |> ignore
> Returning 0
> Returning 1
> Returning 2
> val it : int list = [0; 1]
你的情况的困难在于你还需要 return 破坏谓词的元素。为了做到这一点,你可以部署一个小技巧:在折叠状态下保留一个特殊标志 stop: bool
,最初将其设置为 false
,然后立即切换到元素上的 true
在您需要停止的地方之后。为了保持这种状态,我将使用一条记录:
let st0 = {| prev = Error []; stop = false |}
let acc (s: {| prev: Result<_,string>; stop: bool |}) x =
match s.prev, x with
| Ok _, _ -> {| s with stop = true |} // Previous result was Ok => stop now
| _, Ok _ -> {| s with prev = x |} // Don't stop, but remember the previous result
| Error a, Error b -> {| s with prev = Error (a @ b) |}
sourceSequence
|> Seq.scan acc st0
|> Seq.takeWhile (fun s -> not s.stop)
|> Seq.last
|> (fun s -> s.prev)
P.S。另请注意,在 F# 中,列表串联是 @
,而不是 ++
。您来自 Haskell 吗?
我认为这是一个更好的解决方案。然而,对于 Seq.tryPick
是否总是无副作用而不管底层顺序如何,存在一些混淆。对于list
,这里需要Seq.tail
才能前进...
let rec scanTo (pred:'u -> bool) (acc:'u -> 'a -> 'u) (st0:'u) (ss:'a seq) = seq {
let q =
ss
|> Seq.tryPick Some
|> Option.bind (acc st0 >> Some)
match q with
| None -> yield! Seq.empty
| Some v when pred v -> yield v
| Some v -> yield v; yield! (scanTo pred acc v (Seq.tail ss))
}
例如...
let process : Result<'v, string list> seq -> Result<'v, string list> seq = fun aa ->
let mergeErrors acc e =
match acc, e with
| Error ms, Error m -> Error (m @ ms)
| _, Ok v -> Ok v
| _, Error m -> Error m
let st0 = Error []
let isOk = function
| Ok _ -> true
| _ -> false
scanTo isOk mergeErrors st0 aa