2

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

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

简单的例子:

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

会回来

["fish" "Dog" "cat"]
4

4 回答 4

5

这是我想出的解决方案。为了简化功能,它只接受一个带有重复项的列表,因此如果您(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)))
于 2013-11-01T13:32:04.297 回答
3

贪心解决方案

显然,你不能在这里使用内置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 倍。

于 2013-11-01T13:57:03.100 回答
1

这是一个满足您要求的解决方案(第一个匹配项“获胜”并保留顺序),是惰性的,并且具有更高阶函数的好处。它以 akeyfn作为其第一个参数,对应于 egsort-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 #{})))
于 2013-11-04T18:18:16.177 回答
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") 
于 2017-03-05T20:29:08.200 回答