В clojure, как объединить несколько карт, объединяющих сопоставления с одним и тем же ключом в список? - PullRequest
19 голосов
/ 23 февраля 2012

В Clojure я хотел бы объединить несколько карт в одну карту, где сопоставления с одним и тем же ключом объединяются в список.

Например:

{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny}

должно привести к:

{:weather :sunny, :humor (:happy :sad :happy)}

Я думал о:

(merge-with (comp flatten list) data)

Но это неэффективно, потому что flatten имеет сложность O (n) .

Тогда я придумал:

(defn agg[x y] (if (coll? x) (cons y x) (list y x)))
(merge-with agg data)

Но это не идиоматический . Любая другая идея?

Ответы [ 6 ]

12 голосов
/ 23 февраля 2012

Один подход будет

(defn merge-lists [& maps]
  (reduce (fn [m1 m2]
            (reduce (fn [m [k v]]
                      (update-in m [k] (fnil conj []) v))
                    m1, m2))
          {}
          maps))

Это немного уродливо, но это только потому, что ваши значения еще не списки. Это также заставляет все быть списком (так что вы получите :weather [:sunny] вместо :weather :sunny). Честно говоря, вам, вероятно, будет в миллион раз легче работать с вами.

Если бы вы имели каждое значение как вектор, вы могли бы просто сделать (apply merge-with into maps).

2 голосов
/ 23 февраля 2012

@ Ответ Амаллоя можно немного сгладить, используя for.

(reduce (fn [m [k v]] (update-in m [k] (fnil conj []) v)) {} (for [m data entry m] entry))

Источник для этой техники: http://clj -me.cgrand.net / 2010/01/19 /Clojure-рефакторинга-уплощение-уменьшает /

1 голос
/ 14 мая 2012

Как насчет использования группировки?Он не возвращает точно то, что вы просите, но он очень похож:

user=> (group-by first (concat {:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny :humor :whooot}))
{:humor [[:humor :happy] [:humor :sad] [:humor :happy] [:humor :whooot]], :weather [[:weather :sunny]]}

Или с небольшой модификацией функции группировки:

(defn group-by-v2
 [f vf coll]
  (persistent!
    (reduce
     (fn [ret x]
       (let [k (f x)]
         (assoc! ret k (conj (get ret k []) (vf x)))))
     (transient {}) coll)))

становится:

user=> (group-by-v2 key val (concat {:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny :humor :whooot}))
{:humor [:happy :sad :happy :whooot], :weather [:sunny]}
1 голос
/ 21 апреля 2012

Объединить с этой функцией:

(defn acc-list [x y]
  (let [xs (if (seq? x) x (cons x nil))]
    (cons y xs)))
1 голос
/ 23 февраля 2012

Вы можете попробовать следующее, я думаю, это довольно эффективно

(reduce 
  (fn [m pair] (let [[[k v]] (seq pair)]
                 (assoc m k (cons v (m k))))) 
  {} 
  data)

=> {:weather (:sunny), :humor (:happy :sad :happy)}
0 голосов
/ 26 февраля 2012

Вот решение, где каждое значение представлено в виде списков, даже если одиночные:

(->> [{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny}]
     (map first)
     (reduce (fn [m [k v]] (update-in m [k] #(cons v %))) {}))

=> {:weather (:sunny), :humor (:happy :sad :happy)}

Если вы не хотите помещать синглтоны в список, тогда я подумала, что ваше оригинальное решение просто отлично. Единственный способ сделать его более идиоматичным - использовать core.match.

(->> [{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny}]
     (apply merge-with #(match %1
                               [& _] (conj %1 %2)
                               :else [%1 %2])))

=> {:weather :sunny, :humor [:happy :sad :happy]}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...