Каков способ Clojure для преобразования следующих данных?

#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}})

  

Как вы можете видеть, я использовал несколько функций, и вот пошаговое объяснение:

  1. Извлеките ключи индекса (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}])
  
  1. Преобразовать во вложенную карту (идентификатор, затем статус)
 (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}}})
  
  1. Объединить вложенную карту по идентификатору
 (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}}}
  
  1. Для атрибута :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]}])
    ))