Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to do Seq.takeWhile + one item in F#

I would like to write a function which filters a sequence using a predicate but the result should also INCLUDE the first item for which the predicate returns false.

The logic would be something like this, if there was a break keyword in F#

let myFilter predicate s =
    seq {
        for item in s do
            yield item
            if predicate item then
                break
    }

I tried combinations of Seq.takeWhile and Seq.skipWhile, something like this:

Seq.append 
    (Seq.takeWhile predicate s) 
    (Seq.skipWhile predicate s |> Seq.take 1)

...but the problem is that the first item which matches the predicate is lost between the takeWhile and the skipWhile

Also note that the input sequence is lazy so any solution which consumes the sequence and takes decisions afterwards is not viable.

Any ideas?

Thanks!

EDIT: Thanks a LOT for all the answers! I didn't expect so many responses so fast. I will take a look at each of them soon. Now I just want to give a little more context. Consider the following coding kata which implements a shell:

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

This implementation has the trouble that it doesn't print "Good bye!" when command q is entered.

What I want to do is to implement the function processUntilQuit such that it processes all the commands until "q", including "q".

like image 563
vidi Avatar asked Sep 24 '12 09:09

vidi


People also ask

What is SEQ in F#?

The type seq<'T> is a type abbreviation for IEnumerable<'T> . This means that any type that implements the generic System. Collections. Generic. IEnumerable<'T> , which includes arrays, lists, sets, and maps in F#, and also most .

What is yield in F#?

yield! (pronounced yield bang) inserts all the items of another sequence into this sequence being built. Or, in other words, it appends a sequence.


2 Answers

The lack of support for break in computation expressions is a bit annoying. It does not fit well with the model used by F# (which is why it is not supported), but it would be really useful in this case.

If you want to implement this using just a single iteration over the sequence, then I think the cleanest solution is to just use the underlying structure of sequences and write it as a recursive loop using IEnumerator<'T>

This is fairly short (compared to other solutions here) and it is quite clear code too:

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 }
like image 88
Tomas Petricek Avatar answered Sep 21 '22 14:09

Tomas Petricek


Don't really get what is the problem with your solution.

Two small corrections:

(1) Use sequence expression for readability.

(2) Use Seq.truncate instead of Seq.take in case the input sequence is empty.

let myFilter predicate s = 
    seq { yield! Seq.takeWhile predicate s
          yield! s |> Seq.skipWhile predicate |> Seq.truncate 1 }
like image 39
pad Avatar answered Sep 17 '22 14:09

pad