大文字と小文字を区別しない一致がある場合、文字列の配列から文字列を削除する慣用的な clojure の方法は何ですか?
結果の大文字と小文字を区別する必要があります (私は常に、最初に発生したインセンシティブな一致を維持したいと考えています)。
簡単な例:
(distinct-case-insensitive ["fish" "Dog" "cat"] ["FISH "DOG"])
戻るだろう
["fish" "Dog" "cat"]
これは私が思いついた解決策です。関数を単純化するために、重複するリストを 1 つだけ受け入れるため、以前に vararg リストが必要な場合(apply concat lists)
。
(defn distinct-case-insensitive [xs]
(->> xs
(group-by clojure.string/lower-case)
(vals)
(map first)))
(distinct-case-insensitive ["fish" "Dog" "cat" "Fish" "DOG"]) =>
("fish" "Dog" "cat")
しかし、しし座が述べたように、ハッシュマップのために順序が保持されません。注文したソリューションの使用について
(defn distinct-case-insesitive [xs]
(->> xs
(group-by clojure.string/lower-case)
(#(map % (map clojure.string/lower-case xs)))
(map first)
(distinct)))
明らかに、ここでは build-in distinctを使用できないため、自分で再実装する必要があります。
mishadoff の解決策は本当に美しく、奇妙ですが、HashMap 実装を clojure に染める 8 つを超える一意の要素がある場合、要素の順序が崩れます。
あなたが望むことをする最も安全な方法は、使用することですreduce
:
(defn concat-distinct [& colls]
(first
(reduce (fn [[coll seen] el]
(let [lc-el (string/lower-case el)]
(if (contains? seen lc-el)
[coll seen]
[(conj coll el) (conj seen lc-el)])))
[[] #{}]
(apply concat colls))))
任意の数のコレクションで機能する場合:
user=> (concat-distinct ["fish" "Dog" "cat"] ["FISH" "DOG"] ["snake"] ["CaT" "Camel"])
["fish" "Dog" "cat" "snake" "Camel"]
そして、任意の数の個別の要素に対して(ミシャドフのソリューションとは異なります):
user=> (concat-distinct ["g" "h" "i" "j" "a" "b" "c" "d" "e" "f"])
["g" "h" "i" "j" "a" "b" "c" "d" "e" "f"]
ほとんどの場合、貪欲なソリューションで問題ありません。しかし、怠惰にしたい場合は、再帰を避けることができません。
(defn lazy-concat-distinct [& colls]
((fn step [coll seen]
(lazy-seq
(loop [[el & xs :as s] coll]
(when (seq s)
(let [lc-el (string/lower-case el)]
(if (contains? seen lc-el)
(recur xs)
(cons el (step xs (conj seen lc-el)))))))))
(apply concat colls) #{}))
このソリューションでは、遅延シーケンスを使用します。
user=> (def res (lazy-concat-distinct (lazy-seq (println :boo) ["boo"])))
user=> (count res)
:boo
1
lazy-cat
マクロを使用して、さらに怠惰にすることができます。
(defmacro lazy-concat-distinct* [& colls]
`(lazy-concat-distinct (lazy-cat ~@colls)))
実際に使用されるまで引数を評価しません。
user=> (def res (lazy-concat-distinct* (do (println :boo) ["boo"])))
user=> (count res)
:boo
1
一度にすべてをダウンロードせずに、大規模なデータベースからデータを集約したい場合に便利です。
注:怠惰なソリューションには注意してください。たとえば、このソリューションは貪欲なソリューションよりもほぼ 4 倍遅く動作します。
これは、要件を満たし(最初に一致したアイテムが「勝ち」、順序が保持される)、怠惰で、高次関数であるという利点があるソリューションです。egおよびkeyfn
に対応して、最初の引数としてaを取ります。sort-by
group-by
(defn distinct-by [keyfn coll]
(letfn [(step [xs seen]
(lazy-seq
((fn [xs]
(when-let [[x & more] (seq xs)]
(let [k (keyfn x)]
(if (seen k)
(recur more)
(cons x (step more (conj seen k)))))))
xs)))]
(step coll #{})))
したがって、使用法は次のようになります。
(require '[clojure.string :as str])
(distinct-by str/lower-case ["fish" "Dog" "cat" "Fish" "DOG"])
;=> ("fish" "Dog" "cat")
内部匿名関数の使用recur
は、比較的マイナーな最適化です。clojure.core/distinct
使用しますが、多くの場合は必要ありません。余分なノイズのないバージョンは次のとおりです。
(defn distinct-by [keyfn coll]
(letfn [(step [xs seen]
(lazy-seq
(when-let [[x & more] (seq xs)]
(let [k (keyfn x)]
(if (seen k)
(step more seen)
(cons x (step more (conj seen k))))))))]
(step coll #{})))