clojure:删除不区分大小写的字符串重复项



如果存在不区分大小写的匹配,从字符串数组中删除字符串的惯用方法是什么?

我需要保留结果的大小写(我总是想保留第一次出现的不敏感匹配)。

简单的例子:

(distinct-case-insensitive ["fish" "Dog" "cat"] ["FISH "DOG"])

会回来

["fish" "Dog" "cat"]

这是我想出的解决方案。为了简化功能,它只接受一个重复的列表,所以如果你需要 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)))

贪婪的解决方案

显然,您不能在此处使用内置 DISTINCT ,因此您应该自己重新实现它。

Mishadoff 的解决方案非常漂亮和 Clujur's,但是当有超过 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 倍。

这是一个满足您要求的解决方案(第一个匹配项"获胜"并保留顺序),是惰性的,并且具有作为高阶函数的好处。它以keyfn作为其第一个参数,对应于例如 sort-bygroup-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") 

最新更新