如果存在不区分大小写的匹配,从字符串数组中删除字符串的惯用 clojure 方法是什么?
我需要保留结果的大小写(我总是想保留第一次出现的不敏感匹配)。
简单的例子:
(distinct-case-insensitive ["fish" "Dog" "cat"] ["FISH "DOG"])
会回来
["fish" "Dog" "cat"]
这是我想出的解决方案。为了简化功能,它只接受一个带有重复项的列表,因此如果您(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")
但是,正如 Leonid 提到的,由于 hashmap,它不会保留顺序。对于订购的解决方案使用
(defn distinct-case-insesitive [xs]
(->> xs
(group-by clojure.string/lower-case)
(#(map % (map clojure.string/lower-case xs)))
(map first)
(distinct)))
显然,你不能在这里使用内置distinct,所以你应该自己重新实现它。
mishadoff 的解决方案非常漂亮和clujur'ish,但是当有超过 8 个独特的元素染色到 clojure HashMap 实现时,它会破坏元素的顺序。
做你想做的最安全的方法是使用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"]
对于任意数量的不同元素(与 mishadoff 的解决方案不同):
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 倍。
这是一个满足您要求的解决方案(第一个匹配项“获胜”并保留顺序),是惰性的,并且具有更高阶函数的好处。它以 akeyfn
作为其第一个参数,对应于 egsort-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 #{})))
一个解决方案是实现一个distinct-by
允许在检查重复项之前指定一个应用于每个元素的函数
(defn distinct-by [f coll]
(let [groups (group-by f coll)]
(map #(first (groups %)) (distinct (map f coll)))))
对于示例情况,这可以像这样使用
(distinct-by clojure.string/lower-case
(concat ["fish" "Dog" "cat"] ["FISH" "DOG"]))
; => ("fish" "Dog" "cat")