Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

fold or choose till None?

Tags:

f#

Is there already a way to do something like a chooseTill or a foldTill, where it will process until a None option is received? Really, any of the higher order functions with a "till" option. Granted, it makes no sense for stuff like map, but I find I need this kind of thing pretty often and I wanted to make sure I wasn't reinventing the wheel.

In general, it'd be pretty easy to write something like this, but I'm curious if there is already a way to do this, or if this exists in some known library?

let chooseTill predicate (sequence:seq<'a>) = 
    seq {
            let finished = ref false                            
            for elem in sequence do
                if not !finished then
                    match predicate elem with
                        | Some(x) -> yield x 
                        | None -> finished := true
    }

let foldTill predicate seed list = 
    let rec foldTill' acc = function
        | [] -> acc
        | (h::t) -> match predicate acc h with 
                        | Some(x) -> foldTill' x t
                        | None -> acc
    foldTill' seed list

let (++) a b = a.ToString() + b.ToString()

let abcdef =  foldTill (fun acc v -> 
                        if Char.IsWhiteSpace v then None 
                        else Some(acc ++ v)) "" ("abcdef ghi"  |> Seq.toList)

// result is "abcdef"
like image 253
devshorts Avatar asked Sep 19 '13 19:09

devshorts


1 Answers

I think you can get that easily by combining Seq.scan and Seq.takeWhile:

open System

"abcdef ghi"
|> Seq.scan (fun (_, state) c -> c, (string c) + state) ('x', "")
|> Seq.takeWhile (fst >> Char.IsWhiteSpace >> not)
|> Seq.last |> snd

The idea is that Seq.scan is doing something like Seq.fold, but instead of waiting for the final result, it yields the intermediate states as it goes. You can then keep taking the intermediate states until you reach the end. In the above example, the state is the current character and the concatenated string (so that we can check if the character was whitespace).

A more general version based on a function that returns option could look like this:

let foldWhile f initial input =
  // Generate sequence of all intermediate states
  input |> Seq.scan (fun stateOpt inp -> 
       // If the current state is not 'None', then calculate a new one
       // if 'f' returns 'None' then the overall result will be 'None'
       stateOpt |> Option.bind (fun state -> f state inp)) (Some initial)
  // Take only 'Some' states and get the last one
  |> Seq.takeWhile Option.isSome
  |> Seq.last |> Option.get
like image 110
Tomas Petricek Avatar answered Nov 06 '22 20:11

Tomas Petricek