简体   繁体   中英

F# Seq.tryPick and apply function to members that were not picked

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])

Edit

Now that you have clarified that you want to evaluate to a tuple of the pick option and the filtered list in any case, we just borrow the idea of TheInnerLight and make it shorter. Of course, so the last will be first.

 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM