В Clojure, как сгруппировать элементы? - PullRequest
10 голосов
/ 22 февраля 2012

В ближайшем будущем я хочу объединить эти данные:

(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)])

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

Ответы [ 3 ]

5 голосов
/ 22 февраля 2012

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

(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)

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

4 голосов
/ 22 февраля 2012

Не спешите отклонять 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 голосов
/ 23 февраля 2012

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

(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)}
...