Как Clojure преобразовывает следующие данные? - PullRequest
1 голос
/ 22 апреля 2019

Итак, я сегодня играл с 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 way" . Итак, мой вопрос: что такое "Clojure way" для достижения того, что я произвел?

Ответы [ 4 ]

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

Единственное, что выделяется для меня, это использование as->, когда ->> будет работать так же хорошо, и некоторая работа выполняется избыточно, и некоторые возможности деструктурирования:

(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)))
2 голосов
/ 22 апреля 2019

Это довольно странное преобразование, ключи кажутся немного произвольными, и трудно обобщить из 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 голос
/ 22 апреля 2019

Я бы подошел к нему, как показано ниже, чтобы он мог обрабатывать любое количество записей для каждого значения :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]}])
    ))
1 голос
/ 22 апреля 2019

Нет ни одного "Clojure way" (я полагаю, вы имеете в виду функциональный путь), поскольку это зависит от того, как вы разложите проблему.

Вот как я это сделаю:

(->> 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}])
Преобразовать во вложенную карту (идентификатор, затем статус)
(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}}})
Объединить вложенную карту по id
(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 & [v & r :as t]]]
  {k (if (seq r) (->nested t) v)})

(defn deep-merge [& xs]
  (if (every? map? xs)
    (apply merge-with deep-merge xs)
    (apply merge xs)))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...