从F#中的序列中删除单个非唯一值

时间:2010-04-24 20:15:39

标签: f#

我在F#中有一个代表骰子的整数序列。

在有问题的游戏中,玩家有一个骰子池,可以选择玩一个(由某些规则管理)并保留其余部分。

例如,如果一个玩家掷出一个6,6和一个4并且决定玩一个六个,那么有一个简单的方法来返回一个仅删除了一个6的序列吗?

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

删除所有六个,而不只是一个。

5 个答案:

答案 0 :(得分:4)

序列上的非平凡操作很难处理,因为它们不支持模式匹配。我认为最简单的解决方案如下:

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
    }

只要从客户端隐藏了可变实现,它仍然是功能样式;)

答案 1 :(得分:2)

如果您要存储数据,我会使用ResizeArray而不是Sequence。它具有丰富的功能,例如您询问的功能。它简称为删除。 注意 ResizeArray是CLI类型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]

其他数据类型选项可以是Array

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

或列表

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

答案 2 :(得分:1)

以下代码适用于列表(因此不是任何seq,但听起来像您使用的序列可能是列表)

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

编辑:根据以下正确评论更新代码。谢谢P

编辑:在读完不同的答案之后,我认为会有一个警告。不要将上面的代码用于infite序列,但是因为我猜你的玩家没有不应该成为问题的骰子,但是为了完整性,这里的实现适用于(几乎)任何 有限序列

 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

然而,我推荐使用第一种解决方案,即使您必须先将序列转换为列表(至少对于小序列或最终具有所需值的大序列),我相信它会比后者表现更好 p>

答案 3 :(得分:1)

我认为没有任何函数可以让你直接表示你想要从列表中删除匹配指定条件的第一个元素(例如Seq.removeOne)。

您可以使用Seq.fold以相对可读的方式实现该功能(如果数字序列是有限的):

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]

fold函数保留一些状态 - 在本例中为bool * list<'a>类型。 Boolean标志表示我们是否已经删除了一些元素,并且该列表用于累积结果(在处理结束时必须反转)。

如果您需要为(可能)无限seq<int>执行此操作,则需要直接使用GetEnumerator并将代码实现为递归序列表达式。这有点丑陋,看起来像这样:

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

答案 4 :(得分:1)

您可以尝试以下方法:

let rec removeFirstOccurrence item screened items =

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

用法:

let updated = products |> removeFirstOccurrence product []