1
votes

Quelle est la manière Clojure de transformer les données suivantes?

Je viens donc de jouer avec Clojure aujourd'hui.

En utilisant ces données,

[{:id 35462, :p {:a 2640000, :b 240000}, :i {:a 261600, :b 3200}}
 {:id 57217, :p {:a 470001, :b 1409999}, :i {:a 48043, :b 120105}}]

Puis transformez les données ci-dessus avec,

(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))

Pour produire,

(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}])

Mais j'ai le sentiment que mon code n'est pas de la "façon Clojure" . Ma question est donc la suivante: quelle est la "méthode Clojure" pour réaliser ce que j'ai produit?


0 commentaires

4 Réponses :


2
votes

C'est une transformation assez étrange, les clés semblent un peu arbitraires et il est difficile de généraliser à partir de n = 2 (ou même de savoir si n jamais> 2).

J'utiliserais la décomposition fonctionnelle pour éliminer certains des points communs et obtenir une certaine traction. Tout d'abord, transformons les statuts en nos clés ...

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

Ensuite, avec cela en main, j'aimerais avoir un moyen facile de sortir la «viande» de la sous-structure. Ici, pour une clé donnée dans les données, je fournis le contenu de la carte englobante pour cette clé et un résultat de groupe donné.

(defn group->result [group] 
        {
         :id (key group)
         :p (subgroup->subresult :p (val group))
         :i (subgroup->subresult :i (val group))})

Avec ceci, le transformateur principal devient beaucoup plus traitable:

(defn subgroup->subresult [k subgroup]
    (apply array-map (mapcat #(vector (status->ab (:status %)) (k %)) subgroup)))

Je n'envisagerais pas de généraliser à travers: p et: i pour cela - si vous aviez plus de deux clés, alors peut-être que je générerais une carte de k -> le résultat du sous-groupe et faire une sorte de fusion de réduction. Quoi qu'il en soit, nous avons une réponse:

(def status->ab {"COMPLETED" :a "CREATED" :b})


1 commentaires

Merci! Très appréciée!



3
votes

Les seules choses qui me démarquent sont l'utilisation de as-> alors que ->> fonctionnerait aussi bien, et certains travaux sont effectués de manière redondante, et une certaine déstructuration opportunités:

(into {} (filter #(= (:status %) "COMPLETED") (shuffle test-data)))

Cependant, verser ces valeurs de filter ed (qui sont des cartes elles-mêmes) dans une carte me semble suspect. Cela crée un scénario de la dernière chance dans lequel l'ordre de vos données de test affecte la sortie. Essayez ceci pour voir comment différents ordres de données de test affectent la sortie:

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


1 commentaires

Merci! Je suis vraiment curieux de savoir comment vous décomposez un problème comme celui-ci. Envie de partager?



1
votes

Il n'y a pas de "Clojure way" (je suppose que vous voulez dire de manière fonctionnelle) car cela dépend de la façon dont vous décomposez un problème.

Voici comment je vais faire:

(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)))

Comme vous pouvez le voir, j'ai utilisé quelques fonctions et voici l'explication étape par étape: p >

  1. Extraire les clés d'index (id + status) et la carte elle-même en vecteur
(->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}
  1. Transformer en carte imbriquée (identifiant, puis statut)
(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. Fusionner la carte imbriquée par identifiant
(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. Pour l'attribut : p et : i , mappez sur : a et : b selon l'état
(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}])

Voici les quelques fonctions d'aide que j'ai utilisées:

(->> 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 commentaires

Merci! Belle explication étape par étape!



1
votes

Je l'aborderais plus comme suit, afin qu'il puisse gérer n'importe quel nombre d'entrées pour chaque valeur : id . Bien sûr, de nombreuses variantes sont possibles.

(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]}])
    ))


0 commentaires