述語を使用してシーケンスをフィルタリングする関数を記述したいのですが、その結果には述語が 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の間で失われることです。
また、入力シーケンスは遅延であるため、シーケンスを消費して後で決定を下すソリューションは実行できないことに注意してください。
何か案は?
ありがとう!
編集: たくさんの回答をありがとうございました! こんなに早くたくさんの回答が来るとは思っていませんでした。すぐにそれぞれ見ていきます。ここでもう少し背景を説明したいと思います。シェルを実装する次のコーディング カタを考えてみましょう。
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 が入力されると、「Good bye!」と出力されます。
私がやりたいのは、機能を実装することです終了するまで処理「q」まですべてのコマンドを処理するようにする。含む「q」。
ベストアンサー1
計算式でのサポートが不足しているのはbreak
少し困りものです。これは F# で使用されるモデルにはあまり適合しません (これがサポートされていない理由です) が、この場合には非常に役立ちます。
これをシーケンスの1回の反復だけで実装したい場合、最もクリーンな解決策は、シーケンスの基礎となる構造を使用して、再帰ループとして記述することだと思います。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 }