Clojure конвертировать вектор карт в карту векторных значений - PullRequest
0 голосов
/ 25 января 2019

Есть несколько SO сообщений, связанных с этой темой, однако я не смог найти ничего, что работает для того, чего я хочу достичь.

У меня есть вектор карт.Я собираюсь использовать пример из другого связанного сообщения SO:

(def data
   [{:id 1 :first-name "John1" :last-name "Dow1" :age "14"}
    {:id 2 :first-name "John2" :last-name "Dow2" :age "54"}
    {:id 3 :first-name "John3" :last-name "Dow3" :age "34"}
    {:id 4 :first-name "John4" :last-name "Dow4" :age "12"}
    {:id 5 :first-name "John5" :last-name "Dow5" :age "24"}]))

Я хотел бы преобразовать это в карту, где значения каждой записи будут являться вектором соответствующих значений (поддерживая порядок data).

Вот что я хотел бы получить в качестве вывода:

{:id [1 2 3 4 5]
 :first-name ["John1" "John2" "John3" "John4" "John5"]
 :last-name ["Dow1" "Dow2" "Dow3" "Dow4" "Dow5"]
 :age ["14" "54" "34" "12" "24"]}

Есть ли элегантный и эффективный способ сделать это в Clojure?

Ответы [ 5 ]

0 голосов
/ 26 января 2019

Если мы объединяем карты в одну последовательность пар, у нас есть представление графа в виде списка ребер. Нам нужно преобразовать его в список смежности (здесь - вектор, а не список).

(defn collect [maps]
  (reduce
    (fn [acc [k v]] (assoc acc k (conj (get acc k []) v)))
    {}
    (apply concat maps)))

Например,

=> (collect data)
{:id [1 2 3 4 5]
 :first-name ["John1" "John2" "John3" "John4" "John5"]
 :last-name ["Dow1" "Dow2" "Dow3" "Dow4" "Dow5"]
 :age ["14" "54" "34" "12" "24"]}

Преимущество этого метода перед некоторыми другими состоит в том, что карты в данной последовательности могут иметь разные формы.

0 голосов
/ 26 января 2019

Для меня самый ясный подход заключается в следующем:

(defn ->coll [x]
  (cond-> x (not (coll? x)) vector))

(apply merge-with #(conj (->coll %1) %2) data)

По сути, задача здесь состоит в том, чтобы объединить все карты (merge-with) и собрать все значения в одном ключе с помощьюсоединение (conj) с вектором в ключе - обеспечение того, чтобы значения действительно соединились с вектором (->coll).

0 голосов
/ 25 января 2019

Ну, я разработал решение, а затем, прежде чем я смог опубликовать, Мичиэль опубликовал более краткое решение, но я все равно опубликую его =).

(defn map-coll->key-vector-map
  [coll]
  (reduce (fn [new-map key] 
            (assoc new-map key (vec (map key coll))))
          {}
          (keys (first coll))))
0 голосов
/ 25 января 2019

Пожалуйста, учитывайте читателя при написании кода! Нет призов за игру в «код-гольф». Однако, если вы заставляете их расшифровывать чрезмерно сжатый код, это приводит к значительным затратам.

Я всегда стараюсь четко указывать, что делает код. Это проще всего, если разбить проблему на простые шаги и использовать добрые имена. В частности, это почти невозможно сделать, используя juxt или любую другую загадочную функцию.

Вот как я бы реализовал решение:

(def data
  [{:id 1 :first-name "John1" :last-name "Dow1" :age "14"}
   {:id 2 :first-name "John2" :last-name "Dow2" :age "54"}
   {:id 3 :first-name "John3" :last-name "Dow3" :age "34"}
   {:id 4 :first-name "John4" :last-name "Dow4" :age "12"}
   {:id 5 :first-name "John5" :last-name "Dow5" :age "24"}])

(def data-keys (keys (first data)))

(defn create-empty-result
  "init result map with an empty vec for each key"
  [data]
  (zipmap data-keys (repeat [])))

(defn append-map-to-result
  [cum-map item-map]
  (reduce (fn [result map-entry]
            (let [[curr-key curr-val] map-entry]
              (update-in result [curr-key] conj curr-val)))
    cum-map
    item-map))

(defn transform-data
  [data]
  (reduce
    (fn [cum-result curr-map]
      (append-map-to-result cum-result curr-map))
    (create-empty-result data)
    data))

с результатами:

(dotest
  (is= (create-empty-result data)
    {:id [], :first-name [], :last-name [], :age []})

  (is= (append-map-to-result (create-empty-result data)
         {:id 1 :first-name "John1" :last-name "Dow1" :age "14"})
    {:id [1], :first-name ["John1"], :last-name ["Dow1"], :age ["14"]})

  (is= (transform-data data)
    {:id         [1 2 3 4 5],
     :first-name ["John1" "John2" "John3" "John4" "John5"],
     :last-name  ["Dow1" "Dow2" "Dow3" "Dow4" "Dow5"],
     :age        ["14" "54" "34" "12" "24"]}))

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

0 голосов
/ 25 января 2019

Можно сделать более эффективным, но это хорошее начало:

(def ks (keys (first data)))
(zipmap ks (apply map vector (map (apply juxt ks) data))) ;;=> 

{:id [1 2 3 4 5]
 :first-name ["John1" "John2" "John3" "John4" "John5"]
 :last-name ["Dow1" "Dow2" "Dow3" "Dow4" "Dow5"]
 :age ["14" "54" "34" "12" "24"]}

Еще одно, что близко:

(group-by key (into [] cat data))

;;=> 
{:id [[:id 1] [:id 2] [:id 3] [:id 4] [:id 5]],
 :first-name [[:first-name "John1"] [:first-name "John2"] [:first-name "John3"] [:first-name "John4"] [:first-name "John5"]],
 :last-name [[:last-name "Dow1"] [:last-name "Dow2"] [:last-name "Dow3"] [:last-name "Dow4"] [:last-name "Dow5"]],
 :age [[:age "14"] [:age "54"] [:age "34"] [:age "12"] [:age "24"]]}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...