19

述語を使用してシーケンスをフィルタリングする関数を作成したいのですが、結果には、述語が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が入力されたとき。

私がやりたいのは、 「q」を含む「q 」までのすべてのコマンドを処理するように、関数processUntilQuitを実装することです。

4

11 に答える 11

18

計算式でのサポートの欠如は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 }
于 2012-09-24T12:17:50.890 に答える
5

ソリューションの問題点を実際に把握しないでください。

2つの小さな修正:

(1)読みやすさのためにシーケンス式を使用します。

(2)入力シーケンスが空の場合のSeq.truncate代わりに使用します。Seq.take

let myFilter predicate s = 
    seq { yield! Seq.takeWhile predicate s
          yield! s |> Seq.skipWhile predicate |> Seq.truncate 1 }
于 2012-09-24T11:30:11.060 に答える
2
let duplicateHead xs = seq { yield Seq.head xs; yield! xs }
let filter predicate xs =
    xs
    |> duplicateHead
    |> Seq.pairwise
    |> Seq.takeWhile (fst >> predicate)
    |> Seq.map snd

duplicateHeadここで計算式が気に入らない場合の代替バージョン:

let duplicateHead' xs =
    Seq.append 
        (Seq.head xs)
        xs

このアプローチは、現在および次の要素のタプルを構築することに基づいています。はpredicate現在の要素に適用されていますが、次の要素が返されます。

:最初の要素で失敗した場合は安全ではありません。正常に機能させるには、を確実に通過する要素を追加しpredicateて再作業する必要があります。duplicateHeadpredicate

于 2012-09-24T11:03:51.510 に答える
1

もう1つの遅い答えですが、これは「機能的」でシンプルであり、結果シーケンスの最後の要素を超える要素を読み取りません。

let myFilter predicate =
    Seq.collect (fun x -> [Choice1Of2 x; Choice2Of2 (predicate x)])
    >> Seq.takeWhile (function | Choice1Of2 _ -> true | Choice2Of2 p -> p)
    >> Seq.choose (function | Choice1Of2 x -> Some x | Choice2Of2 _ -> None)
于 2019-07-03T20:44:17.903 に答える
0

醜い非機能的なソリューション

let myfilter f s =
    let failed = ref false
    let newf = fun elem -> match !failed with 
                           |true -> 
                               failed := f elem
                               true
                           |false->false
    Seq.takeWhile newf s
于 2012-09-24T09:55:06.413 に答える
0

醜い機能的な解決策:):

let rec myFilter predicate =
        Seq.fold (fun acc s ->
            match acc with
                | (Some x, fs) -> 
                    match predicate s with
                        | true -> (Some x, fs @ [s])
                        | false -> (Some x, fs)
                | (_, fs) ->
                    match predicate s with
                        | true -> (None, fs @ [s])
                        | false -> (Some s, fs))
            (None, [])

最終的にタプルが作成されます。最初の要素には、ソースリストの最初の一致しない要素を含むオプションが含まれ、2番目の要素にはフィルター処理されたリストが含まれます。

醜い機能的な怠惰な解決策(申し訳ありませんが、私はあなたの投稿を初めて正しく読みませんでした):

let myFilterLazy predicate s =
        let rec inner x =
            seq {
                match x with
                    | (true, ss) when ss |> Seq.isEmpty = false ->
                        let y = ss |> Seq.head
                        if predicate y = true then yield y
                        yield! inner (true, ss |> Seq.skip 1)
                    | (_, ss) when ss |> Seq.isEmpty = false ->
                        let y = ss |> Seq.head
                        if predicate y = true then
                            yield y
                            yield! inner (false, ss |> Seq.skip 1)
                        else
                            yield y
                            yield! inner (true, ss |> Seq.skip 1)
                    | _ -> 0.0 |> ignore
            }

        inner (false, s)

私はF#に精通していないため、試合での終了ケースの見栄えを良くすることができません。おそらく、F#の達人の一部が役立つでしょう。

編集: Tomas Petricekの回答に触発されたそれほど醜くない純粋なF#ソリューション:

let myFilterLazy2 predicate s =
        let rec inner ss = seq {
            if Seq.isEmpty ss = false then
                yield ss |> Seq.head
                if ss |> Seq.head |> predicate then
                    yield! ss |> Seq.skip 1 |> inner
        }

        inner s
于 2012-09-24T10:39:23.820 に答える
0

もう少し良いもの。:)

let padWithTrue n xs = seq { for _ in 1..n do yield true; done; yield! xs }
let filter predicate n xs =
    let ys = xs |> Seq.map predicate |> padWithTrue n
    Seq.zip xs ys
    |> Seq.takeWhile snd
    |> Seq.map fst

nこれは、追加する要素の数を定義する追加のパラメーターを取ります。

:1行padWithTruedoneキーワード)に注意してください

于 2012-09-24T11:49:34.623 に答える
0

私はあなたがそれが取るものを推測します

let takeUntil pred s =
  let state = ref true
  Seq.takeWhile (fun el ->
    let ret= !state
    state := not <| pred el
    ret
    ) s
于 2014-07-26T05:04:47.740 に答える
0

これは非常に古いですが、他のソリューションがこれを示唆していなかったので、私が貢献すると思いました...

Seq.scan述語の結果の2つの要素のスタックを確立し、前の要素の述語の結果を表すそのスタックの一番下が単純である間に使用するのはtrueどうですか?(注、このコードはテストしていません)

Seq.scan (fun (a,b,v) e -> (pred e, a, Some e)) (true, true, None )
>> Seq.takeWhile (fun (_,b,_) -> b)
>> Seq.map (fun (_,_,c) -> c)
于 2017-11-14T17:03:14.740 に答える
0

これは古い質問だと思います。しかし、もっと機能的な解決策があります。

とはいえ、正直なところ、この質問については、TomasPetricekによるより必須の解決策の方が好きです。

let takeWhileAndNext predicate mySequence =
    let folder pred state element =
        match state with
            | Some (_, false) ->
                None
            | _ ->
                Some (Some element, pred element)
    let initialState = Some (None, true)
    Seq.scan (folder predicate) initialState mySequence |> Seq.takeWhile Option.isSome
                                                        |> Seq.map Option.get
                                                        |> Seq.map fst
                                                        |> Seq.filter Option.isSome
                                                        |> Seq.map Option.get

最後から2番目の行では、 match以外の状態がないため、を|> Seq.filter Option.isSomeに置き換えることができます。|> Seq.tailinitialStateSome (None, _)

于 2017-12-18T17:33:59.133 に答える
0

質問されてからその年齢はわかっていますが、似ているがより一般的な問題に対処する必要があり、誰かが私の解決策を役立つと思ってくれることを願っています。

アイデアは、クロージャで列挙子をキャッチしてから、元のシーケンスの残りの部分を反復処理する関数を返すことです。この関数には、現在の要素を含めるかどうかという1つのブーレンパラメーターがあります(OPの場合)

/// allows fetching elements from same sequence
type ContinueSequence<'a> (xs: 'a seq) =

    let en = xs.GetEnumerator()

    member _.Continue (includeCurrent: bool) =
        let s = seq { while en.MoveNext() do yield en.Current }
        let c = seq { en.Current }
        if includeCurrent then
            Seq.append c s
        else
            s

    interface IDisposable with 
        member _.Dispose() =
            en.Dispose()

質問に対する実際の答えは次のようになります。

use seq = new ContinueSequence a 
let result = Seq.append
   seq.Continue(false) |> Seq.takeWhile(predicate) 
   seq.Continue(true) |> Seq.take(1)  //include element which breaks predicate

より一般的な例

/// usage example:
let a = seq [1; 2; 3; 4; 5; 6; 7]

use seq = new ContinueSequence<_>(a)
let s1 = seq.Continue(false) |> Seq.takeWhile((>) 3) // take 1 and 2, 3 is current
let s2 = seq.Continue(true) |> Seq.take(2)    // take 3 and 4
let s3 = seq.Continue(false) |> Seq.skip(1)   // skip 5

let s = 
    s1 
    |> Seq.append <| s2 
    |> Seq.append <| s3 
    |> Seq.toList

// s = [1; 2; 3; 4; 6; 7]
于 2020-04-26T07:25:25.407 に答える