怎么办Seq.takeWhile + F#中的一个项目

vid*_*idi 14 f# functional-programming lazy-sequences

我想编写一个使用谓词过滤序列的函数,但结果也应该包含谓词返回false的第一个项目.

如果F#中有break关键字,那么逻辑将是这样的

let myFilter predicate s =
    seq {
        for item in s do
            yield item
            if predicate item then
                break
    }
Run Code Online (Sandbox Code Playgroud)

我尝试了Seq.takeWhile和Seq.skipWhile的组合,如下所示:

Seq.append 
    (Seq.takeWhile predicate s) 
    (Seq.skipWhile predicate s |> Seq.take 1)
Run Code Online (Sandbox Code Playgroud)

...但问题是在takeWhile和skipWhile之间丢失了与谓词匹配的第一个项目

还要注意输入序列是惰性的,因此任何消耗序列并在之后做出决定的解决方案都是不可行的.

有任何想法吗?

谢谢!

编辑:非常感谢所有的答案!我没想到会有如此之快的反应.我很快就会看看他们每个人.现在我只想提供更多背景信息.考虑以下实现shell的编码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
Run Code Online (Sandbox Code Playgroud)

这个实现有一个麻烦,它打印"再见!" 何时输入命令q.

我想要做的是实现函数processUntilQuit,使其处理所有命令,直到"q",包括 "q".

Tom*_*cek 15

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 }
Run Code Online (Sandbox Code Playgroud)

  • 我没有这个声明的"证据",但我认为它不能用"很好的功能方式"编写,因为底层数据结构 - "seq <'T>`并不是真正的功能.如果你使用惰性列表,你可以用函数式编写它,但是F#中的序列更加惯用(即使它们有时会强迫你使用命令式样式来实现某些更高级别的声明函数). (2认同)

pad*_*pad 5

不太明白你的解决方案有什么问题。

两个小修正:

(1) 使用序列表达式以提高可读性。

(2)如果输入序列为空,则使用Seq.truncatenot 。Seq.take

let myFilter predicate s = 
    seq { yield! Seq.takeWhile predicate s
          yield! s |> Seq.skipWhile predicate |> Seq.truncate 1 }
Run Code Online (Sandbox Code Playgroud)