我想编写一个函数,该函数使用谓词过滤序列,但结果还应包括该谓词返回false的第一项。
如果F#中有一个break关键字,那么逻辑将是这样的
let myFilter predicate s =
seq {
for item in s do
yield item
if predicate item then
break
}
我尝试了Seq.takeWhile和Seq.skipWhile的组合,如下所示:
Seq.append
(Seq.takeWhile predicate s)
(Seq.skipWhile predicate s |> Seq.take 1)
...但是问题是,与谓词匹配的第一个项目在takeWhile和skipWhile之间丢失
还要注意,输入序列是惰性的,因此任何消耗序列并随后做出决定的解决方案都不可行。
有任何想法吗?
谢谢!
编辑:非常感谢所有的答案!我没想到会有这么快的回复。我将很快查看它们中的每一个。现在,我只想提供更多背景信息。考虑以下实现外壳的编码kata:
let cmdProcessor state = function
| "q" -> "Good bye!"
| "h" -> "Help content"
| c -> sprintf "Bad command: '%s'" c
let processUntilQuit =
Seq.takeWhile (fun cmd -> cmd <> "q")
let processor =
processUntilQuit
>> Seq.scan cmdProcessor "Welcome!"
module io =
let consoleLines = seq { while true do yield System.Console.ReadLine () }
let display : string seq -> unit = Seq.iter <| printfn "%s"
io.consoleLines |> processor|> io.display
printf "Press any key to continue..."
System.Console.ReadKey ()|> ignore
此实现的麻烦在于,它不会打印“再见!”输入命令q时。
我要做的是实现processUntilQuit函数,以便处理所有命令,直到“ q”,包括“ q”。
最佳答案
在计算表达式中缺少对break
的支持有点令人讨厌。它与F#使用的模型不太吻合(这就是为什么不支持它的原因),但是在这种情况下,它确实很有用。
如果您想仅使用序列上的单个迭代来实现此目的,那么我认为最干净的解决方案是仅使用序列的基础结构并将其作为使用IEnumerator<'T>
的递归循环编写
这相当短(与此处的其他解决方案相比),代码也非常清晰:
let myFilter predicate (s:seq<_>) =
/// Iterates over the enumerator, yielding elements and
/// stops after an element for which the predicate does not hold
let rec loop (en:IEnumerator<_>) = seq {
if en.MoveNext() then
// Always yield the current, stop if predicate does not hold
yield en.Current
if predicate en.Current then
yield! loop en }
// Get enumerator of the sequence and yield all results
// (making sure that the enumerator gets disposed)
seq { use en = s.GetEnumerator()
yield! loop en }
关于f# - 如何做Seq.takeWhile + F#中的一项,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12562327/