2012-02-22 7 views
10

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

(def data [[:morning :pear][:morning :mango][:evening :mango][:evening :pear]]) 
(group-by first data) 
;{:morning [[:morning :pear][:morning :mango]],:evening [[:evening :mango][:evening :pear]]} 

Моя проблема заключается в том, что :evening и :morning являются избыточными. Вместо этого, я хотел бы создать следующую коллекцию:

([:morning (:pear :mango)] [:evening (:mango :pear)]) 

я придумал:

(for [[moment moment-fruit-vec] (group-by first data)] [moment (map second moment-fruit-vec)]) 

Есть более идиоматические решения?

+1

имя переменной в вашем предлагаемое решение вводит в заблуждение. Значение, разрушенное как «плод», на самом деле представляет собой последовательность векторов пары момент-плод. –

+0

Спасибо большое! Обновлен вопрос – viebel

ответ

5

Я столкнулся с подобными проблемами группировки. Обычно я в конечном итоге закупорки слияния-с или обновление в в каком-то шаге обработки сл:

(apply merge-with list (map (partial apply hash-map) data)) 

Вы получаете карту, но это лишь след из пар ключ-значение:

user> (apply merge-with list (map (partial apply hash-map) data)) 
{:morning (:pear :mango), :evening (:mango :pear)} 
user> (seq *1) 
([:morning (:pear :mango)] [:evening (:mango :pear)]) 

Это решение только получает то, что вы хотите, если каждый ключ появляется дважды. Это может быть лучше:

(reduce (fn [map [x y]] (update-in map [x] #(cons y %))) {} data) 

Оба они чувствуют себя «более функциональными», но также чувствуют себя немного запутанными. Не будьте слишком быстры, чтобы отклонить свое решение, это легко понять и достаточно функционально.

+1

Что вы думаете о '(применять merge-with (comp flatten list) (map (partial apply hash-map) data))'? – viebel

+1

Это хорошее, лаконичное исправление. Я считаю, что 'flatten' является ** O (n) **, поэтому он может не работать многократно, применяя его в некоторых наборах данных. –

+1

Вы правы. Я нашел лучшее решение, см. Мой ответ. У BTW есть встроенная функция, которая делает то же самое, что и 'agg'? – viebel

4

Не спешите увольнять group-by, он агрегировал ваши данные по желаемой клавише и не изменил данные. Любая другая функция, ожидающая последовательности пар моментально-фруктов, примет любое значение, поднятое на карте, возвращенное group-by.

С точки зрения вычисления сводки, к которой я стремился: merge-with, но для этого мне пришлось преобразовать входные данные в последовательность карт и построить «базовую карту» с необходимыми ключами и пустыми векторами в качестве значений ,

(let [i-maps (for [[moment fruit] data] {moment fruit}) 
     base-map (into {} 
        (for [key (into #{} (map first data))] 
        [key []]))] 
     (apply merge-with conj base-map i-maps)) 

{:morning [:pear :mango], :evening [:mango :pear]} 
2

Размышляя на ответ @mike t «s, я придумал:

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

Это решение работает и тогда, когда ключи появляются более чем в два раза по data:

(apply merge-with agg (map (partial apply hash-map) 
    [[:morning :pear][:morning :mango][:evening :mango] [:evening :pear] [:evening :kiwi]])) 
;{:morning (:mango :pear), :evening (:kiwi :pear :mango)}