简体   繁体   English

F#Seq.tryPick并将函数应用于未被挑选的成员

[英]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. 我想使用tryPick,然后继续使用该集合的“休息”。

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. 你可以同时使用fold返回一个带有单独选项的元组和其余的fold

As long as the lone is None you check your predicate (here equality with 2) 只要孤独是None你检查你的谓词(这里与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) after部分将包含找到的项目,所以我们需要丢弃它(通过跳过一个项目)
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. 我会通过创建一个新的函数tryPickOrApplyFunc来解决这个问题,该函数折叠集合,返回适当的值或应用提供的函数。

/// 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: 如果您想要返回一组未挑选的值,我会将其构建为一个列表,并建议切换到foldBack以便您按原始顺序获取值:

/// 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 . Seq.tryPick返回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. 我假设整个表达式将返回'None'并且函数签名chooser:('a -> 'b option) -> source:seq<'a> -> ('b * 'a list) option是您的用例可以接受。

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. 现在你已经澄清了你想要在任何情况下评估选择选项和过滤列表的元组,我们只是借用了TheInnerLight的概念并将其缩短。 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? List.partitionArray.partition做你想要的吗?

> [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. 您可以使用Seq.toListSet.toArray将所有有限序列转换为列表或数组,然后使用适当的partition函数。 There is, however, no Seq.partition function . 但是, 没有Seq.partition功能

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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