Is this possible somehow? I want to use tryPick and then continue with the "rest" of the collection.
I can only think of ugly ways to do this:
let sequence = Seq.init 5 (fun (i) -> i)
let pickedMember =
sequence
|> Seq.tryPick (fun (i) ->
if i = 2 then
Some i
else
None
);;
let sequenceWithoutPickedMember = sequence |> Seq.filter (fun (i) -> not(i=pickedMember.Value));;
You could do both at the same time using a fold
returning you a tuple with the lone option and the rest.
As long as the lone is None
you check your predicate (here equality with 2)
when it's true (if any) you update the lone one to be that item and skip that item from the sequence
otherwise you just add that item to the resulting "rest sequence"
I made it a more general function with a predicate parameter :
let firstAndRest predicate source =
source
|> Seq.fold (fun (lone, rest) item -> if Option.isNone lone && predicate item
then Some item, rest
else lone, seq { yield! rest; yield item })
(None, Seq.empty)
// usage with example given
let sequence = Seq.init 5 id
let pickedMember, sequenceWithoutPickedMember = firstAndRest ((=) 2) sequence
// pickedMember : Some 2
// sequenceWithoutPickedMember : seq [0; 1; 3; 4]
Edit
As nesting sequence expression concerns me (it can keep alive a lot of enumerator for long sequence) here's an alternative using indexes.
The idea is to find the index of that item (if any) rather than the item itself.
That done we get the item at that index and split the sequence in two part.
The after
part will contain the found item so we need to discard it (by skipping one more item)
If that item was not found we just give back the original source.
let firstAndRest predicate source =
let source = Seq.cache source // to prevent yielding different values for side-effect sequences
match Seq.tryFindIndex predicate source with
Some index -> let picked = source |> Seq.item index |> Some
let before, after = Seq.take index source, Seq.skip (index + 1) source
picked, Seq.append before after
| None -> None, source
Based on your comment, you say:
I want to pick a member from a collection but then also apply a function to each member that were not picked
I would approach this by creating a new function tryPickOrApplyFunc
that folds over the collection, either returning the appropriate value or applying the supplied function.
/// Applies the supplied choosing function to successive elements, returning the first result where the function returns `Some (x)` and applies the supplied function `f` to all other elements.
let tryPickOrApplyFunc chooser f sequ =
let folder acc v =
match acc, chooser v with
|Some x, _ -> // We have already picked an element - apply the function to v and return the already picked element
f v
Some x
|None, Some y -> // We haven't picked a value and the chooser function returns `Some (x)` - Pick the value
Some v
|None, None -> // We haven't picked a value and the chooser function returns `None` - apply the function and return `None`
f v
None
Seq.fold (folder) None sequ
Example usage - picking a value or printing the non-matching results:
tryPickOrApplyFunc (fun x -> if x = 3 then Some x else None) (printfn "%d") [1; 2; 3; 4; 5; 6;];; 1 2 4 5 6 val it : int option = Some 3
Or, if we go outside the range of the values:
tryPickOrApplyFunc (fun x -> if x = 7 then Some x else None) (printfn "%d") [1; 2; 3; 4; 5; 6;];; 1 2 3 4 5 6 val it : int option = None
If you want to return a collection of non-picked values instead, I'd build it as a list and would recommend switching to foldBack
so that you get the values in the original order:
/// Applies the supplied choosing function to successive elements, returning the first result where the function returns `Some (x)` and a sequence of all the other elements.
let tryPickWithRemaining chooser sequ =
let folder v acc =
match acc, chooser v with
|(Some x, lst), _ -> // We have already picked an element - return the already picked element and append v to the list of remaining values
Some x, v::lst
|(None, lst), Some y -> // We haven't picked a value and the chooser function returns `Some (x)` - Pick the value and keep the list of remaining values as it is
Some v, lst
|(None, lst), None -> // We haven't picked a value and the chooser function returns `None` - return `None` append v to the list of remaining values
None, v::lst
let pick, lst = Seq.foldBack (folder) sequ (None, [])
pick, Seq.ofList lst
It is not clear from your example what you want do when Seq.tryPick
returns None
. I'll assume that the whole expression shall return 'None' and that the function signature chooser:('a -> 'b option) -> source:seq<'a> -> ('b * 'a list) option
is acceptable for your use case.
You'll get the option of a tuple back, containing the picked value and the rest as a list. It does not need to be a sequence because you need to consume the whole input sequence eagerly to return the result. The laziness gone, we can as well use a list.
let tryPickWithRest chooser source =
source
|> Seq.fold (fun (picked, rest) x ->
match picked, chooser x with
| None, Some newlyPicked -> Some newlyPicked, rest
| _ -> picked, x::rest ) (None, [])
|> function
| Some picked, rest -> Some(picked, List.rev rest)
| _ -> None
[0..4]
|> tryPickWithRest (fun i -> if i = 2 then Some "found 2" else None)
// val it : (string * int list) option = Some ("found 2", [0; 1; 3; 4])
let tryPickWithRest' chooser source = Seq.foldBack (fun x (picked, rest) -> match picked, chooser x with | None, Some newlyPicked -> Some newlyPicked, rest | _ -> picked, x::rest ) source (None, []) [-4..0] |> tryPickWithRest' (fun i -> if i >= 2 then Some "found 2" else None) // val it : string option * int list = (null, [-4; -3; -2; -1; 0])
Does List.partition
or Array.partition
do what you want?
> [1..5] |> List.partition ((=) 2);;
val it : int list * int list = ([2], [1; 3; 4; 5])
You can convert all finite Sequences to lists or arrays, using Seq.toList
or Set.toArray
, and then use the appropriate partition
function. There is, however, no Seq.partition
function .
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.