Remove a single non-unique value from a sequence in F#

巧了我就是萌 提交于 2020-01-04 04:04:07

问题


I have a sequence of integers representing dice in F#.

In the game in question, the player has a pool of dice and can choose to play one (governed by certain rules) and keep the rest.

If, for example, a player rolls a 6, 6 and a 4 and decides to play one the sixes, is there a simple way to return a sequence with only one 6 removed?

Seq.filter (fun x -> x != 6) dice

removes all of the sixes, not just one.


回答1:


the below code will work for a list (so not any seq but it sounds like the sequence your using could be a List)

let rec removeOne value list = 
               match list with
               | head::tail when head = value -> tail
               | head::tail -> head::(removeOne value tail)
               | _ -> [] //you might wanna fail here since it didn't find value in
                                   //the list

EDIT: code updated based on correct comment below. Thanks P

EDIT: After reading a different answer I thought that a warning would be in order. Don't use the above code for infite sequences but since I guess your players don't have infite dice that should not be a problem but for but for completeness here's an implementation that would work for (almost) any finite sequence

 let rec removeOne value seq acc = 
                   match seq.Any() with
                   | true when s.First() = value -> seq.Skip(1)
                   | true -> seq.First()::(removeOne value seq.Skip(1))
                   | _ -> List.rev acc //you might wanna fail here since it didn't find value in
                                       //the list

However I recommend using the first solution which Im confident will perform better than the latter even if you have to turn a sequence into a list first (at least for small sequences or large sequences with the soughtfor value in the end)




回答2:


Non-trivial operations on sequences are painful to work with, since they don't support pattern matching. I think the simplest solution is as follows:

let filterFirst f s =
    seq {
        let filtered = ref false
        for a in s do
            if filtered.Value = false && f a then
                filtered := true
            else yield a
    }

So long as the mutable implementation is hidden from the client, it's still functional style ;)




回答3:


If you're going to store data I would use ResizeArray instead of a Sequence. It has a wealth of functions built in such as the function you asked about. It's simply called Remove. Note: ResizeArray is an abbreviation for the CLI type List.

let test = seq [1; 2; 6; 6; 1; 0]
let a = new ResizeArray<int>(test)
a.Remove 6 |> ignore
Seq.toList a |> printf "%A"

// output
> [1; 2; 6; 1; 0]

Other data type options could be Array

let removeOneFromArray v a =
    let i = Array.findIndex ((=)v) a
    Array.append a.[..(i-1)] a.[(i+1)..]

or List

let removeOneFromList v l = 
    let rec remove acc = function
        | x::xs when x = v -> List.rev acc @ xs
        | x::xs -> remove (x::acc) xs
        | [] -> acc
    remove [] l



回答4:


I don't think there is any function that would allow you to directly represent the idea that you want to remove just the first element matching the specified criteria from the list (e.g. something like Seq.removeOne).

You can implement the function in a relatively readable way using Seq.fold (if the sequence of numbers is finite):

let removeOne f l = 
  Seq.fold (fun (removed, res) v ->
    if removed then true, v::res
    elif f v then true, res
    else false, v::res) (false, []) l 
  |> snd |> List.rev

> removeOne (fun x -> x = 6) [ 1; 2; 6; 6; 1 ];
val it : int list = [1; 2; 6; 1]

The fold function keeps some state - in this case of type bool * list<'a>. The Boolean flag represents whether we already removed some element and the list is used to accumulate the result (which has to be reversed at the end of processing).

If you need to do this for (possibly) infinite seq<int>, then you'll need to use GetEnumerator directly and implement the code as a recursive sequence expression. This is a bit uglier and it would look like this:

let removeOne f (s:seq<_>) = 
  // Get enumerator of the input sequence
  let en = s.GetEnumerator()

  let rec loop() = seq {
    // Move to the next element
    if en.MoveNext() then
      // Is this the element to skip?
      if f en.Current then
        // Yes - return all remaining elements without filtering
        while en.MoveNext() do
          yield en.Current
      else
        // No - return this element and continue looping
        yield en.Current
        yield! loop() }
  loop()



回答5:


You can try this:

let rec removeFirstOccurrence item screened items =

    items |> function
    | h::tail -> if h = item
                 then screened @ tail
                 else tail |> removeFirstOccurrence item (screened @ [h])
    | _ -> []

Usage:

let updated = products |> removeFirstOccurrence product []


来源:https://stackoverflow.com/questions/2705972/remove-a-single-non-unique-value-from-a-sequence-in-f

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!