2016-05-13 1 views
0

J'ai besoin de modifier dynamiquement les données de cette structure:Clojure prewalk récursion infinie pour les requêtes datomic

[:db/id 
:list/title 
:list/type 
{:list/items [... lots of nested data ...]}] 

à ce qui suit:

[:db/id 
:list/title 
:list/type 
{(default :list/items []) [... lots of nested data ...]}] 

Depuis que je me occupe plusieurs requêtes différentes, je peux être Assurez-vous que la jointure sera le quatrième élément du vecteur. Mais j'ai besoin de remplacer chaque instance de :list/items par (default :list/items []).

La seule façon que je sais de faire ceci est en utilisant clojure.walk/prewalk. Cependant, elle conduit à une récursion infinie:

(clojure.walk/prewalk #(if (= :list/items %) 
         '(default :list/items []) 
         %) 
         query) 

Une fois que la promenade trouve :list/items et le remplace par '(default :list/items []), il trouve alors la :list/items de la valeur remplacée, et remplace celle. Et ainsi de suite.

Je peux utiliser un atome pour m'assurer que la valeur n'est remplacée qu'une seule fois, mais cela ressemble à de la triche.

D'autres approches?

Répondre

1

dans ce cas, vous avez probablement utiliser postwalk:

user> 
(def query [:db/id 
      :list/title 
      :list/type 
      {:list/items [:db/id 
          :list/title 
          :list/type 
          {:list/items []}]}]) 
#'user/query 

user> (clojure.walk/postwalk #(if (= :list/items %) 
           '(default :list/items []) 
           %) 
          query) 
[:db/id :list/title :list/type 
{(default :list/items []) [:db/id :list/title :list/type {(default :list/items []) []}]}] 

postwalk ne pas aller plus loin sur le contenu même si la feuille a été remplacée par une nouvelle collection:

user> (clojure.walk/prewalk #(do (println %) 
           (if (= % 1) [10] %)) 
          [[1 2 3 [1 2]] [1 2]]) 
[[1 2 3 [1 2]] [1 2]] 
[1 2 3 [1 2]] 
1 
10 ;; goes deeper 
2 
3 
[1 2] 
1 
10 ;; and here 
2 
[1 2] 
1 
10 ;; and here 
2 
[[[10] 2 3 [[10] 2]] [[10] 2]] 

user> (clojure.walk/postwalk #(do (println %) 
            (if (= % 1) [10] %)) 
          [[1 2 3 [1 2]] [1 2]]) 
1 
2 
3 
1 
2 
[[10] 2] 
[[10] 2 3 [[10] 2]] 
1 
2 
[[10] 2] 
[[[10] 2 3 [[10] 2]] [[10] 2]] 
[[[10] 2 3 [[10] 2]] [[10] 2]] 

par la manière, il y a une belle fonctions prewalk-replace/postwalk-replace pour votre cas précis:

user> (clojure.walk/postwalk-replace 
     {:list/items '(default :list/items [])} query) 

[:db/id :list/title :list/type 
{(default :list/items []) [:db/id :list/title :list/type {(default :list/items []) []}]}] 

mise à jour, après les commentaires: un exemple (synthétique) de plus de contrôle sur le remplacement. Disons que vous voulez remplacer les éléments spécifiques dans une collection arbitraire de vecteurs imbriqués, mais pour remplacer l'élément une seule fois (première fois que vous le voir), et laisser le reste inchangé:

user> (require '[clojure.zip :as z]) 

user> 
(defn replace-once [rep coll] 
    (loop [curr (z/vector-zip coll) rep rep] 
    (if (empty? rep) (z/root curr) 
     (let [n (z/node curr) r (rep n)] 
      (cond (z/end? curr) (z/root curr) 
       r (recur (z/replace curr r) (dissoc rep n)) 
       :else (recur (z/next curr) rep)))))) 
#'user/replace-once 

user> (replace-once {1 100 2 200} [[4 3 2] [10 1 2] 1 2 [5 3 2]]) 
[[4 3 200] [10 100 2] 1 2 [5 3 2]] 

(ici vous enlèverait juste remplacé éléments de carte de remplacement de candidats (rep), et passez-le plus loin avec la récursivité, jusqu'à ce qu'il soit vide)

+0

Oh, nice, postwalk-replace est parfait. Par curiosité, disons que je devais utiliser prewalk pour manipuler la requête d'une autre manière. Aurais-je besoin d'utiliser un atome pour suivre le remplacement? Ou y a-t-il un autre moyen? – egracer

+0

de quelle façon voulez-vous dire exactement? Je ne peux pas y penser tout de suite) – leetwinski

+1

mais je vous proposerais de jeter un coup d'œil aux 'zippers' pour ça. Si vous voulez un contrôle précis sur le remplacement et l'itération sur l'arbre. Par exemple, il vous permet de remplacer un élément, puis d'ignorer cet élément, ou de le suivre dans certains accumulateurs (puisque les fermetures à glissière fonctionnent bien avec 'loop/recur' – leetwinski