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

时间:2013-11-01 12:52:33

标签: string clojure matching

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

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

简单示例:

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

将返回

["fish" "Dog" "cat"]

4 个答案:

答案 0 :(得分:4)

这是我提出的解决方案。为简化功能,它只接受一个带有重复项的列表,因此如果您之前需要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")

但是,正如Leonid所说,由于hashmap,它不会保留顺序。对于有序的解决方案使用

(defn distinct-case-insesitive [xs]
    (->> xs
         (group-by clojure.string/lower-case)
         (#(map % (map clojure.string/lower-case xs)))
         (map first)
         (distinct)))

答案 1 :(得分:3)

贪心解决方案

显然,你不能在这里使用内置distinct,所以你应该自己重新实现它。

mishadoff's solution非常漂亮且非常棒,但当有超过8个独特元素染色以隐藏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

当您想要从某个大型数据库聚合数据而不立即下载所有数据时,它非常有用。

N.B。小心懒惰的解决方案。例如,这个解决方案比贪婪的解决方案慢了近4倍。

答案 2 :(得分:1)

这是一个满足您要求的解决方案(第一个匹配项“获胜”并保留订单),是懒惰的,并且具有更高阶函数的好处。它需要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 #{})))

答案 3 :(得分:0)

解决方案是实现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")