2010-04-24 51 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