2011-10-05 62 views
15

我有一個列表,其中可能包含比較相等的元素。我想要一個類似的列表,但刪除了一個元素。所以從(:a:b:c:b:d)我想能夠「刪除」一個:b得到(:a:c:b:d)。什麼是慣用的Clojure從列表中的多個「刪除」單個實例?

上下文是一張紙牌遊戲中的牌,其中有兩張標準牌正在玩牌,所以可能有重複牌但仍然一次玩一張。

我有工作代碼,見下文。 Clojure有更多的慣用方法嗎?

(defn remove-one [c left right] 
    (if (= right()) 
    left 
    (if (= c (first right)) 
     (concat (reverse left) (rest right)) 
     (remove-one c (cons (first right) left) (rest right))))) 

(defn remove-card [c cards] 
    (remove-one c() cards)) 

這裏是斯卡拉答案我前一陣子:What is an idiomatic Scala way to "remove" one element from an immutable List?

+0

作爲一個腳註,我查看了這個問題的Scala版本的首選答案的源代碼。事實證明,Scala中的diff函數使用可變哈希來計算要從多集中移除的出現次數。 –

+0

只是爲了澄清:它是重要的:b在這裏算作重複?從示例輸出中,您'丟棄'了第一個:b,但它是否等同於丟棄了第二個:b? [abcbd - > acdb,但是abcd也可以接受?] – monojohnny

+0

@monojohnny,abcd對我的用例來說同樣適用。 –

回答

23

如何:

(let [[n m] (split-with (partial not= :b) [:a :b :c :b :d])] (concat n (rest m))) 

其中在拆分列表:B,然後刪除:B和concats兩名單。

+0

打我吧!當元素在列表中沒有找到時,'split-with'確實做到了正確的事情,這樣這個解決方案很容易編寫。 – mquander

+1

非常優雅。我認爲值得注意的是'split-with'是建立在'take-while'和'drop-while'之上的,它們是解決類似問題的更基本的構建塊。 –

+0

+1 - 非常優雅!還值得一提的是,這不會消除所有出現的':b',而只是第一個(實際上我認爲是前者)。 快速鏈接到doc:http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/split-with – kgadek

9

我通常用像split-with這樣的高階函數解決這些問題,但有人已經這樣做了。有時在更原始的層次上工作更具可讀性或更高的效率,所以這裏有一個更好的原始循環代碼版本,使用惰性序列並通用化以取代謂詞去除,而不是被限制爲相等性檢查:

(defn remove-once [pred coll] 
    ((fn inner [coll] 
    (lazy-seq 
     (when-let [[x & xs] (seq coll)] 
     (if (pred x) 
      xs 
      (cons x (inner xs)))))) 
    coll)) 


user> (remove-once #{:b} [:a :b :c :b :d]) 
(:a :c :b :d) 
+2

注意我並不是聲稱這更具可讀性,但效率更高如果你熟悉像'split-with'這樣的HOF並且需要一些練習和「好的」遞歸代碼,這可能是一個好習慣。 – amalloy

+0

還要感謝一個使用lazy-seq的好例子! [[懶惰]的「規範」解釋](http://clojure.org/lazy)有點混亂。 –

1

令人驚訝的是沒有一個高級API來做這樣的事情。這裏是另一個類似於@amalloy和@James的版本,它使用recur來避免堆棧溢出。

(defn remove-once [x c]                                                      
    (letfn [(rmv [x c1 c2 b]                                                     
      (if-let [[v & c] (seq c1)]                                                  
       (if (and (= x v) b)                                                   
       (recur x c c2 false)                                                   
       (recur x c (cons v c2) b))                                                 
       c2))]                                                       
    (lazy-seq (reverse (rmv x c '() true)))))                                                

(remove-once :b [:a :b :c :b :d]) 
;; (:a :c :b :d) 
+2

對不起,但我討厭這個實現。你正在解決一個沒有問題的問題 - 我的版本也沒有吹出堆棧,我也不會浪費一大堆能量來調用「reverse」 - 我一開始就以正確的方式構建它。 – amalloy

相關問題