Problema Descrizione
Per l'elaborazione di grandi strutture di dati in Clojure, sequenze pigri offrono una bella approccio, idiomatica. Bisogna essere prudenti per evitare la ritenzione della testa , però.Rimozione elementi di sequenze pigri in una grande struttura ad albero Clojure, evitando ritenzione testa
ho lotta con l'elaborazione di una grande struttura ad albero simile a questo:
R Root
__________|____________________
A B C, D, E, ... 1st Level Children
_______|_______ _______|_______
X Y Y ... Y X Y X Y Y ... Y X Y 2nd Level Children
- Tutti i nodi sono mappe con una chiave
:content
. Il valore di qualsiasi:content
è un seq pigro con tutti i figli di quel nodo. - L'intero albero non si adatta alla memoria. Ci sono troppi articoli
Y
al 2 ° livello. - L'albero intero escluso gli articoli
Y
si adatta alla memoria.
Dopo aver elaborato l'albero, desidero finire con un nuovo albero, in cui sono stati rimossi tutti Y
nodi:
R
______|__________________
A B C, D, E, ...
_____|___ _____|___
X X ... X X X ... X
codice di esempio e ulteriori spiegazioni
;; Generating example data
;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn root [content]
{:tag :root :content content})
(defn lazy-elements [n tag content]
(lazy-seq (repeat n {:tag tag :content content})))
(defn level-1 [content]
(lazy-elements 3 :A content))
(defn level-2 [n]
(concat (lazy-elements 10 :X '(:leaf))
(lazy-elements n :Y '(:leaf))))
(defn remove-nodes [node]
(remove #(= (:tag %) :Y) node))
;; Illustrating usage
;;;;;;;;;;;;;;;;;;;;;
;; runs and runs and runs... and eventually returns correctly
(defn valid-run []
(->> (root (level-1 (level-2 1e8)))
:content
first
:content
remove-nodes))
;; Does not terminate properly, runs out of memory
(defn invalid-run []
(->> (root (level-1 (level-2 1e8)))
:content
(map :content) ; source of head retention
(map remove-nodes)))
Il secondo esempio si blocca (a seconda della memoria disponibile, potrebbe essere necessario regola il numero di elementi di livello 2). La mappatura su :content
di tutti gli articoli di livello 1 introduce un riferimento che causa problemi di ritenzione della testa quando si passa da tutti gli elementi di contenuto a per rimuovere gli elementi indesiderati :Y
.
Sono stato in grado di utilizzare i dati da qualcosa come valid-run
, metterlo in uno stato mutabile di var holding, facendo ciò per tutti i nodi rilevanti e poi ricucire tutti insieme i dati. Ma sono molto scontento di questo approccio per il fatto di dover dipendere dalla mutabilità e dover utilizzare un codice abbastanza imperativo per unire i dati alla fine (eseguendo, ad esempio, gli indici delle liste).
Domanda
come può essere raggiunto in uno stile dichiarativo funzionale? Idealmente, vorrei evitare di dover usare lo stato mutabile oltre ad essere troppo imperativo (ad esempio cucendo insieme le raccolte usando indici e simili).
Risorse
I seguenti articoli e frammenti sono interessanti letture su aspetti di questo problema:
- XML manipulation in Clojure
- Rich Hickey on incidental complexity of head retention
- Related Clojure documentation
- Head retention funkiness in a nutshell
altro sfondo
Eventualmente ho bisogno di questo per elaborare file XML di grandi dimensioni. Grandi mezzi> 1 GB e l'analisi in un albero non funzioneranno nella memoria disponibile . Da tale XML voglio mettere alcuni elementi nel bucket A (diciamo una tabella del database) e tutto il resto dell'albero XML nel bucket B. La struttura XML, naturalmente, dovrebbe essere mantenuta per i sottotitoli estratti.
Invece di analizzare l'XML in un albero, è anche possibile elaborare l'XML come un flusso di eventi, ad esempio tramite data.xml/source-seq. Tuttavia, significherebbe perdere la semantica dell'albero XML. Funzionerebbe, ma non essere bello. Ma forse ci sono altri approcci per gestire il codice XML in primo luogo.
Stai costruendo un nuovo albero. Puoi scriverlo mentre lo crei, e quindi liberare ciò che hai già scritto? Al livello i, hai una sequenza pigra di nodi. Scegli il prossimo nodo e scendi in esso. Una volta terminato con quel nodo, scrivi l'albero sostitutivo e liberalo e tutto l'albero sottostante. Decrescente significa iterare quel processo, ad eccezione della parte scritta. L'idea è di tenere in memoria solo la sottostruttura che stai esaminando, e di scrivere e dimenticare ciò che hai già elaborato, portando pigramente in memoria nuovi sottoalberi. Non sono sicuro che tutto funzioni. – Mars
Un altro: stai pulendo Y solo dai nodi foglia, giusto? Allo stesso livello? In tal caso, costruisci una sequenza di sequenze di nodi foglia, che rappresentano quelli che si trovano sotto lo stesso nodo. Questi dovrebbero avere identificatori, in modo che tu possa ricollegarli ai loro nodi parent dopo aver rimosso gli Y. Vale a dire: cammini l'albero prima solo per costruire la sequenza di sequenze di nodi fogliari. Quindi rimuovi Y's.Quindi si cammina nuovamente l'albero per creare un nuovo albero con i nuovi gruppi di nodi foglia. Scrivi sottostrutture per evitare di avere il nuovo albero tutto in memoria. Di nuovo, non sono sicuro ... – Mars
I miei suggerimenti potrebbero essere idee orribili. Appena fuori dalla mia testa. – Mars