#clojure
#clojure
Вопрос:
Итак, я только что поиграл с Clojure сегодня.
Используя эти данные,
(def test-data
[{:id 35462, :status "COMPLETED", :p 2640000, :i 261600}
{:id 35462, :status "CREATED", :p 240000, :i 3200}
{:id 57217, :status "COMPLETED", :p 470001, :i 48043}
{:id 57217, :status "CREATED", :p 1409999, :i 120105}])
Затем преобразуйте приведенные выше данные с помощью,
(as-> test-data input
(group-by :id input)
(map (fn [x] {:id (key x)
:p {:a (as-> (filter #(= (:status %) "COMPLETED") (val x)) tmp
(into {} tmp)
(get tmp :p))
:b (as-> (filter #(= (:status %) "CREATED") (val x)) tmp
(into {} tmp)
(get tmp :p))}
:i {:a (as-> (filter #(= (:status %) "COMPLETED") (val x)) tmp
(into {} tmp)
(get tmp :i))
:b (as-> (filter #(= (:status %) "CREATED") (val x)) tmp
(into {} tmp)
(get tmp :i))}})
input)
(into [] input))
Для получения,
[{:id 35462, :p {:a 2640000, :b 240000}, :i {:a 261600, :b 3200}}
{:id 57217, :p {:a 470001, :b 1409999}, :i {:a 48043, :b 120105}}]
Но у меня такое чувство, что мой код не является «способом Clojure». Итак, мой вопрос в том, что такое «способ Clojure» для достижения того, что я создал?
Ответ №1:
Единственные вещи, которые выделяются для меня, — это использование as->
when ->>
будет работать так же хорошо, и некоторая работа выполняется избыточно, а также некоторые возможности деструктурирования:
(defn aggregate [[id values]]
(let [completed (->> (filter #(= (:status %) "COMPLETED") values)
(into {}))
created (->> (filter #(= (:status %) "CREATED") values)
(into {}))]
{:id id
:p {:a (:p completed)
:b (:p created)}
:i {:a (:i completed)
:b (:i created)}}))
(->> test-data
(group-by :id)
(map aggregate))
=>
({:id 35462, :p {:a 2640000, :b 240000}, :i {:a 261600, :b 3200}}
{:id 57217, :p {:a 470001, :b 1409999}, :i {:a 48043, :b 120105}})
Однако заливка этих filter
значений ed (которые сами являются картами) в карту кажется мне подозрительной. Это создает сценарий с последним выигрышем, в котором порядок ваших тестовых данных влияет на результат. Попробуйте это, чтобы увидеть, как разные порядки test-data
влияют на вывод:
(into {} (filter #(= (:status %) "COMPLETED") (shuffle test-data)))
Комментарии:
1. Спасибо! Мне действительно любопытно, как вы решаете подобную проблему. Хотите поделиться?
Ответ №2:
Это довольно странное преобразование, ключи кажутся немного произвольными, и трудно сделать вывод из n = 2 (или даже узнать, было ли n когда-либо> 2).
Я бы использовал функциональную декомпозицию, чтобы выделить некоторую общность и получить некоторую тягу. Прежде всего, давайте преобразуем статусы в наши ключи…
(def status->ab {"COMPLETED" :a "CREATED" :b})
Затем, имея это в виду, я хотел бы получить простой способ извлечь «мясо» из подструктуры. Здесь для данного ключа в данные я предоставляю содержимое прилагаемой карты для этого ключа и заданного группового результата.
(defn subgroup->subresult [k subgroup]
(apply array-map (mapcat #(vector (status->ab (:status %)) (k %)) subgroup)))
Благодаря этому основной преобразователь становится намного более сговорчивым:
(defn group->result [group]
{
:id (key group)
:p (subgroup->subresult :p (val group))
:i (subgroup->subresult :i (val group))})
Я бы не стал рассматривать обобщение по: p и: i для этого — если бы у вас было более двух ключей, тогда, возможно, я бы сгенерировал карту k -> результат подгруппы и выполнил какое-то сокращение слияния. В любом случае, у нас есть ответ:
(map group->result (group-by :id test-data))
;; =>
({:id 35462, :p {:b 240000, :a 2640000}, :i {:b 3200, :a 261600}}
{:id 57217, :p {:b 1409999, :a 470001}, :i {:b 120105, :a 48043}})
Комментарии:
1. Спасибо! Очень признателен!
Ответ №3:
Не существует единого «способа Clojure» (я полагаю, вы имеете в виду функциональный способ), поскольку это зависит от того, как вы решаете проблему.
Вот как я буду делать:
(->> test-data
(map (juxt :id :status identity))
(map ->nested)
(apply deep-merge)
(map (fn [[id m]]
{:id id
:p (->ab-map m :p)
:i (->ab-map m :i)})))
;; ({:id 35462, :p {:a 2640000, :b 240000}, :i {:a 261600, :b 3200}}
;; {:id 57217, :p {:a 470001, :b 1409999}, :i {:a 48043, :b 120105}})
Как вы можете видеть, я использовал несколько функций, и вот пошаговое объяснение:
- Извлеките ключи индекса (id status) и саму карту в вектор
(map (juxt :id :status identity) test-data)
;; ([35462 "COMPLETED" {:id 35462, :status "COMPLETED", :p 2640000, :i 261600}]
;; [35462 "CREATED" {:id 35462, :status "CREATED", :p 240000, :i 3200}]
;; [57217 "COMPLETED" {:id 57217, :status "COMPLETED", :p 470001, :i 48043}]
;; [57217 "CREATED" {:id 57217, :status "CREATED", :p 1409999, :i 120105}])
- Преобразовать во вложенную карту (идентификатор, затем статус)
(map ->nested *1)
;; ({35462 {"COMPLETED" {:id 35462, :status "COMPLETED", :p 2640000, :i 261600}}}
;; {35462 {"CREATED" {:id 35462, :status "CREATED", :p 240000, :i 3200}}}
;; {57217 {"COMPLETED" {:id 57217, :status "COMPLETED", :p 470001, :i 48043}}}
;; {57217 {"CREATED" {:id 57217, :status "CREATED", :p 1409999, :i 120105}}})
- Объединить вложенную карту по идентификатору
(apply deep-merge *1)
;; {35462
;; {"COMPLETED" {:id 35462, :status "COMPLETED", :p 2640000, :i 261600},
;; "CREATED" {:id 35462, :status "CREATED", :p 240000, :i 3200}},
;; 57217
;; {"COMPLETED" {:id 57217, :status "COMPLETED", :p 470001, :i 48043},
;; "CREATED" {:id 57217, :status "CREATED", :p 1409999, :i 120105}}}
- Для атрибута
:p
и:i
сопоставьте с:a
и:b
в соответствии со статусом
(->ab-map {"COMPLETED" {:id 35462, :status "COMPLETED", :p 2640000, :i 261600},
"CREATED" {:id 35462, :status "CREATED", :p 240000, :i 3200}}
:p)
;; => {:a 2640000, :b 240000}
И ниже приведены несколько вспомогательных функций, которые я использовал:
(defn ->ab-map [m k]
(zipmap [:a :b]
(map #(get-in m [% k]) ["COMPLETED" "CREATED"])))
(defn ->nested [[k amp; [v amp; r :as t]]]
{k (if (seq r) (->nested t) v)})
(defn deep-merge [amp; xs]
(if (every? map? xs)
(apply merge-with deep-merge xs)
(apply merge xs)))
Комментарии:
1. Спасибо! Хорошее пошаговое объяснение!
Ответ №4:
Я бы подошел к нему скорее следующим образом, чтобы он мог обрабатывать любое количество записей для каждого :id
значения. Конечно, возможно много вариантов.
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[tupelo.core :as t] ))
(dotest
(let [test-data [{:id 35462, :status "COMPLETED", :p 2640000, :i 261600}
{:id 35462, :status "CREATED", :p 240000, :i 3200}
{:id 57217, :status "COMPLETED", :p 470001, :i 48043}
{:id 57217, :status "CREATED", :p 1409999, :i 120105}]
d1 (group-by :id test-data)
d2 (t/forv [[id entries] d1]
{:id id
:status-all (mapv :status entries)
:p-all (mapv :p entries)
:i-all (mapv :i entries)})]
(is= d1
{35462
[{:id 35462, :status "COMPLETED", :p 2640000, :i 261600}
{:id 35462, :status "CREATED", :p 240000, :i 3200}],
57217
[{:id 57217, :status "COMPLETED", :p 470001, :i 48043}
{:id 57217, :status "CREATED", :p 1409999, :i 120105}]})
(is= d2 [{:id 35462,
:status-all ["COMPLETED" "CREATED"],
:p-all [2640000 240000],
:i-all [261600 3200]}
{:id 57217,
:status-all ["COMPLETED" "CREATED"],
:p-all [470001 1409999],
:i-all [48043 120105]}])
))