Как преобразовать список карт во вложенную карту карт? - PullRequest
2 голосов
/ 12 апреля 2019

Получение данных из базы данных в виде списка карт (LazySeq) оставляет меня нуждающимся в преобразовании их в карту карт.

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

Это форма моих данных:

(def data (list {:structure 1 :cat "A" :item "item1" :val 0.1}
                {:structure 1 :cat "A" :item "item2" :val 0.2}
                {:structure 1 :cat "B" :item "item3" :val 0.4}
                {:structure 2 :cat "A" :item "item1" :val 0.3}
                {:structure 2 :cat "B" :item "item3" :val 0.5}))

Я бы хотел получить их в виде

=> {1 {"A" {"item1" 0.1}
            "item2" 0.2}}
      {"B" {"item3" 0.4}}
    2 {"A" {"item1" 0.3}}
      {"B" {"item3" 0.5}}}

Я пытался

(->> data
     (map #(assoc {} (:structure %) {(:cat %) {(:item %) (:val %)}}))
     (apply merge-with into))

Это дает

{1 {"A" {"item2" 0.2}, "B" {"item3" 0.4}},
 2 {"A" {"item1" 0.3}, "B" {"item3" 0.5}}}

При слиянии я теряю некоторые записи, но не могу придумать другого пути.Есть ли простой способ?Я даже собирался использовать призрак.

Будем благодарны за любые мысли.

Ответы [ 3 ]

3 голосов
/ 12 апреля 2019

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

(assoc-in {} [1 "A" "item1"] 0.1)
;; =>
{1 {"A" {"item1" 0.1}}}

Использование последовательности во что-то другое, сокращение - идиоматический выбор. Редукционная функция находится прямо на краю уровня сложности, для которого я считаю анонимным fn, поэтому вместо этого я вытащу ее на место.

(defn- add-val [acc line]
   (assoc-in acc [(:structure line) (:cat line) (:item line)] (:val line)))

(reduce add-val {} data)
;; =>
{1 {"A" {"item1" 0.1, "item2" 0.2}, "B" {"item3" 0.4}},
 2 {"A" {"item1" 0.3}, "B" {"item3" 0.5}}}

Я думаю, это был тот эффект, который вы искали.

Меньше пройденных дорог:

Поскольку ваша последовательность поступает из базы данных, я не буду беспокоиться об использовании временной коллекции для ускорения агрегации. Кроме того, теперь я думаю об этом, так или иначе, работа с вложенными переходными картами - это боль.

update-in был бы полезен, если, например, вы хотите сложить какие-либо значения с одним и тем же ключом, но смысл вашего вопроса в том, что кортежи структуры / cat / item уникальны, и поэтому вам просто нужна группировка.

Juxt может быть использован для генерации структуры ключа - т.е.

((juxt :structure :cat :item) (first data))
[1 "A" "item1"]

но мне не ясно, есть ли способ использовать это, чтобы сделать add-val fn более читабельным.

1 голос
/ 13 апреля 2019

Вы можете продолжать использовать существующий код.Только последнее слияние должно измениться:

(defn deep-merge [& xs]
  (if (every? map? xs)
    (apply merge-with deep-merge xs)
    (apply merge xs)))

(->> data
     (map #(assoc {} (:structure %) {(:cat %) {(:item %) (:val %)}}))
     (apply deep-merge))

;; => 
{1
 {"A" {"item1" 0.1, "item2" 0.2},
  "B" {"item3" 0.4}},
 2
 {"A" {"item1" 0.3},
  "B" {"item3" 0.5}}}

Пояснение : ваш исходный (apply merge-with into) слит только на один уровень вниз.deep-merge сверху вернется на все вложенные карты, чтобы выполнить слияние.

Приложение : @ pete23 - одно использование juxt Я могу придумать, чтобы сделать функцию многократно используемой.Например, мы можем извлечь произвольные поля с помощью juxt, затем преобразовать их во вложенные карты (с помощью еще одной функции ->nested) и, наконец, сделать deep-merge:

(->> data
     (map (juxt :structure :cat :item :val))
     (map ->nested)
     (apply deep-merge))

, где ->nested можетбыть реализовано следующим образом:

(defn ->nested [[k & [v & r :as t]]]
  {k (if (seq r) (->nested t) v)})

(->nested [1 "A" "item1" 0.1])
;; => {1 {"A" {"item1" 0.1}}}

Один пример приложения (сумма по категориям):

(let [ks [:cat :val]]
  (->> data
       (map (apply juxt ks))
       (map ->nested)
       (apply (partial deep-merge-with +))))
;; => {"A" 0.6000000000000001, "B" 0.9}

Примечание deep-merge-with оставлено в качестве упражнения для наших читателей:)

0 голосов
/ 12 апреля 2019
(defn map-values [f m]
  (into {} (map (fn [[k v]] [k (f v)])) m))

(defn- transform-structures [ss]
  (map-values (fn [cs]
                (into {} (map (juxt :item :val) cs))) (group-by :cat ss)))

(defn transform [data]
  (map-values transform-structures (group-by :structure data)))

затем

(transform data)
...