2010-04-24 47 views
3

删除单个非唯一值I具有表示在F#骰子整数的序列。从序列中F#

在有问题的游戏中,玩家拥有骰子池,可以选择打一个(按照一定的规则管辖),并保持休息。

例如,如果玩家掷出6,6和4并决定玩六分之一,是否有一种简单的方法可以返回一个只有一个6的序列?

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

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

回答

1

下面的代码将工作的列表(因此不会受到任何序列,但它听起来像你的使用可能是一个列表的顺序)

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序列,但是因为我猜你的玩家没有infite dice,这不应该是一个问题,但是为了完整性,这里的一个实现可以用于(几乎)任何 有限序列

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 

但是,我建议使用第一种解决方案,即使您必须首先将序列转换为列表(至少对于小序列或最后寻找值的大序列),我相信后者的表现会比后者更好。

+0

我不明白“以避免串联......”的一部分,特别是因为有一个'@'在你的函数中。是否((List.rev a)@ b'被F#编译成revappend?即便如此,这仍然是一个串联,你可以通过简单地将遍历的值存储在堆栈中来避免:'| heat :: tail当head <> value - > head::(removeOne value tail)时'' – 2010-04-24 20:56:51

+0

另外,对'seq.Skip(1)'使用嵌套调用会导致非常低效的代码(事实上,_O(n) _访问时间,因为每次调用'Skip'都会创建一个间接访问)。使用F#列表时使用的模式根本不适用于序列。 – 2010-04-24 21:33:36

+0

@Tomas是的,当我指出在手边创建列表时,我的观点可能会很好地表现得更好 – 2010-04-25 00:47:29

1

我不觉得有什么,它会让你直接表示要删除刚刚的第一个元素匹配指定CR想法的任何功能列表中的(例如)像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>类型。布尔标志表示我们是否已经移除了某个元素,并且该列表用于累加结果(在处理结束时必须反转)。

如果您需要为(可能)无限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() 
3

序列上不平凡的行动是痛苦的工作,因为他们不支持模式匹配。我认为,最简单的解决办法如下:

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 
    } 

只要可变实现从客户端隐藏,它仍然是实用的风格;)

+0

+1,为简单起见。 – gradbot 2010-04-25 17:52:52

+1

我喜欢这个答案,但我认为使用'not filtered.Value'比'false'更可取。 – kvb 2010-04-26 14:03:41

2

如果你要存储数据,我会用ResizeArray而不是序列。它具有丰富的功能,如您询问的功能。它简称为Remove。注意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] 

其他数据类型的选择可能是阵列

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 
+0

我想这是违反FP原则,因为a.Remove变异列表 – knocte 2014-07-29 22:23:21

+0

@knocte当然,有些情况下你想要在一个函数或对象中封装突变,以便以不可变的方式暴露它。许多F#运行时函数使用下面的可变数据。 – gradbot 2014-08-01 04:52:54