Как сгруппировать вложенные коллекции по заданным критериям? - PullRequest
6 голосов
/ 06 октября 2011

Как я могу сгруппировать вложенные коллекции на основе значений столбцов, которые динамически задаются ?Например, предположим, у нас есть следующие вложенные коллекции;как я могу сгруппировать его по значениям в первом и втором столбцах?

[ ["A" 2011 "Dan"] ["A" 2011 "Jon"] ["A" 2010 "Tim"] ["B" 2009 "Tom"] ]

Желаемая полученная карта:

{ A { 
      2011 [['A', 2011, 'Dan'] ['A', 2011, 'Joe']]
      2010 [['A', 2010, 'Tim']] 
    }
  B { 2009 [['B', 2009, 'Tom']] } 
}

Ниже приводится мое решение, которое почти работает:

(defn nest [data criteria]
  (if (empty? criteria)
    data
    (for [[k v] (group-by #(nth % (-> criteria vals first)) data)]
      (hash-map k (nest v (rest criteria))))))

Ответы [ 5 ]

6 голосов
/ 06 октября 2011

Я придумал следующее:

user=> (def a [["A" 2011 "Dan"] 
               ["A" 2011 "Jon"] 
               ["A" 2010 "Tim"] 
               ["B" 2009 "Tom"] ])

user=> (into {} (for [[k v] (group-by first a)] 
                  [k (group-by second v)]))

{"A" {2011 [["A" 2011 "Dan"] 
            ["A" 2011 "Jon"]], 
      2010 [["A" 2010 "Tim"]]}, 
 "B" {2009 [["B" 2009 "Tom"]]}}
2 голосов
/ 09 августа 2016

Обобщение group-by

Мне нужно было обобщение group-by, которое дало бы более 2-х вложенных карт-карт.Я хотел иметь возможность дать такой функции список произвольных функций для рекурсивного запуска через group-by.Вот что я придумал:

(defn map-function-on-map-vals
  "Take a map and apply a function on its values. From [1].
   [1] http://stackoverflow.com/a/1677069/500207"
  [m f]
  (zipmap (keys m) (map f (vals m))))

(defn nested-group-by
  "Like group-by but instead of a single function, this is given a list or vec
   of functions to apply recursively via group-by. An optional `final` argument
   (defaults to identity) may be given to run on the vector result of the final
   group-by."
  [fs coll & [final-fn]]
  (if (empty? fs)
    ((or final-fn identity) coll)
    (map-function-on-map-vals (group-by (first fs) coll)
                              #(nested-group-by (rest fs) % final-fn))))

Ваш пример

Применительно к вашему набору данных:

cljs.user=> (def foo [ ["A" 2011 "Dan"]
       #_=>            ["A" 2011 "Jon"]
       #_=>            ["A" 2010 "Tim"]
       #_=>            ["B" 2009 "Tom"] ])
cljs.user=> (require '[cljs.pprint :refer [pprint]])
nil
cljs.user=> (pprint (nested-group-by [first second] foo))
{"A"
 {2011 [["A" 2011 "Dan"] ["A" 2011 "Jon"]], 2010 [["A" 2010 "Tim"]]},
 "B" {2009 [["B" 2009 "Tom"]]}}

Создает точно желаемый результат.nested-group-by может принимать три или четыре или более функций и создает такое количество вложений хэш-карт.Возможно, это будет полезно для других.

Удобная функция

nested-group-by также имеет удобную дополнительную функцию: final-fn, которая по умолчанию равна identity, поэтому, если вы ее не предоставитесамое глубокое вложение возвращает вектор значений, но если вы укажете final-fn, он будет выполняться для самых внутренних векторов.Для иллюстрации: если вы просто хотели узнать, сколько строк исходного набора данных появилось в каждой категории и году:

cljs.user=> (nested-group-by [first second] foo count)
                                               #^^^^^ this is final-fn
{"A" {2011 2, 2010 1}, "B" {2009 1}}

Предупреждение

Эта функция не использует recur так глубоко-рекурсивные вызовы могут взорвать стек.Однако для ожидаемого варианта использования с небольшим набором функций это не должно быть проблемой.

1 голос
/ 20 сентября 2018

Я подозреваю, что самая идиоматическая версия этого:

(defn nest-by
  [ks coll]
  (let [keyfn (apply juxt ks)]
    (reduce (fn [m x] (update-in m (keyfn x) (fnil conj []) x)) {} coll)))

Это использует тот факт, что update-in уже делает большую часть того, что вы хотите.В вашем конкретном случае вы бы просто пошли:

(nest-by [first second] [["A" 2011 "Dan"]
                         ["A" 2011 "Jon"]
                         ["A" 2010 "Tim"]
                         ["B" 2009 "Tom"] ])

{"A" {2011 [["A" 2011 "Dan"] ["A" 2011 "Jon"]], 2010 [["A" 2010 "Tim"]]}, "B" {2009 [["B" 2009 "Tom"]]}}
1 голос
/ 11 октября 2011

Вот решение, которое я придумал. Это работает, но я уверен, что это можно улучшить.

(defn nest [data criteria]
  (if (empty? criteria)
    data
    (into {} (for [[k v] (group-by #(nth % (-> criteria vals first)) data)]
      (hash-map k (nest v (rest criteria)))))))
0 голосов
/ 06 октября 2011

Это сближает вас

(defn my-group [coll]                                                                                                                                                                                                                       
  (let [m (group-by                                                                                                                                                                                                                         
           #(-> % val first first)                                                                                                                                                                                                          
           (group-by #(second %) coll))]                                                                                                                                                                                                    
    (into {} (for [[k v] m] [k (#(into {} %) v)]))))                                                                                                                                                                                        

(my-group [["A" 2011 "Dan"] ["A" 2011 "Jon"] ["A" 2010 "Tim"] ["B" 2009 "Tom"]])                                                                                                                                                            

{"A" {                                                                                                                                                                                                                                      
      2011 [["A" 2011 "Dan"] ["A" 2011 "Jon"]],                                                                                                                                                                                             
      2010 [["A" 2010 "Tim"]]                                                                                                                                                                                                               
      },                                                                                                                                                                                                                                    
 "B" {2009 [["B" 2009 "Tom"]]}                                                                                                                                                                                                              
}

Как обычно с Clojure, вы, вероятно, можете найти что-то более менее многословное.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...