#clojure #transformation
#clojure #преобразование
Вопрос:
Получение данных из базы данных в виде списка карт (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}}}
При слиянии я теряю некоторые записи, но я не могу придумать другого способа. Есть ли простой способ? Я даже собирался попробовать использовать spectre.
Любые мысли будут оценены.
Ответ №1:
Если я имею дело с вложенными картами, в первую очередь обычно нужно подумать об обновлении или присоединении — они принимают последовательность вложенных ключей. Для такой проблемы, когда данные очень регулярные, это просто.
(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}}}
Я думаю, это был эффект, который вы искали.
Менее пройденные дороги:
Поскольку ваша последовательность поступает из базы данных, я бы не стал беспокоиться об использовании временной коллекции для ускорения агрегации. Кроме того, теперь я думаю об этом, работа с вложенными переходными картами — это боль в любом случае.
обновление было бы удобно, если бы вы хотели, например, добавить какие-либо значения с одним и тем же ключом, но смысл вашего вопроса заключается в том, что кортежи structure / cat / item уникальны, поэтому вам просто нужна группировка.
juxt может использоваться для генерации ключевой структуры, т. е.
((juxt :structure :cat :item) (first data))
[1 "A" "item1"]
но мне не ясно, есть ли какой-либо способ использовать это, чтобы сделать add-val fn более читаемым.
Ответ №2:
Вы можете продолжать использовать существующий код. Должно измениться только окончательное слияние:
(defn deep-merge [amp; 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 amp; [v amp; 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
оставлено в качестве упражнения для наших читателей 🙂
Ответ №3:
(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)