2013-05-18 14 views
5

Comprendo che elenchi e vettori in Clojure possono essere utilizzati quasi in modo intercambiabile nella maggior parte delle situazioni. Ecco un caso semplice che miOttieni elemento da sequenza in clojure

(nth [2 4] 0) ;=> 2 
(nth '(2 4) 0) ;=> 2 
(get [2 4] 0) ;=> 2 
(get '(2 4) 0) ;=> nil -- wtf??? 

sorprese La documentazione di get colloqui mappatura di una chiave, ma funziona con vettori o insiemi bene. La documentazione di nth menziona anche get, parlando delle loro differenze solo in casi limite.

La situazione del mondo reale in cui ho affrontato questo strano comportamento era che ho caricato un file yaml. Ne risultò una struttura annidata di mappe ed elenchi. Volevo accedere a un elemento con get-in.

(def form (parse-yaml some-yaml-file)) 
(def best-friend (get-in form [:friends 0 :first-name])) 

Non funziona, perché get-in usa get internamente. Così ho una questione pratica una parte teorica e:

  • È questo comportamento di get considerata corretta e si aspettava? Se è così, per favore, spiega perché.
  • Come posso accedere a un elemento nidificato in una tale struttura di mappe ed elenchi?

risposta

14

Il comportamento di get è corretto e previsto. get funziona su strutture di dati "con chiave", in cui i valori sono mappati alle chiavi. Ciò include i vettori che mappano gli indici ai valori e imposta .

Le liste non forniscono accesso casuale agli elementi; sono pensati per essere attraversati linearmente. Poiché gli schemi di accesso supportati sono così diversi, elenchi e vettori sono assolutamente non destinati a essere utilizzati in modo intercambiabile e la libreria di base di Clojure non fa alcuno sforzo per supportare tale utilizzo. (nth è un bizzarro esempio di una funzione che esegue sia le ricerche a tempo costante sia quelle logaritmiche costanti e traversate lineari, una strana bestia in terra Clojure).

Ci sono naturalmente maggiori differenze turistiche su "modifica" (nel persistente struttura dati senso: creare copie modificate), come il modo in cui conj opere e la disponibilità di assoc per vettori (come già accennato in un nota a piè di pagina: la sostituzione di un elemento in una lista comporta la ricostruzione dell'intero prefisso fino a quel punto).

Se si desidera utilizzare modelli di accesso di tipo vettoriale con i propri dati, è necessario inserirlo in un vettore. Le liste possono essere convertite in vettori (in tempo lineare) con vec. Se si ha a che fare con un formato di serializzazione in cui è ambiguo restituire elenchi o vettori per alcuni dati e il parser non accetta un'opzione per dirgli quale dovrebbe essere utilizzato, potrebbe essere necessario eseguire qualche post-elaborazione (clojure.walk potrebbe essere utile, in particolare le funzioni prewalk e postwalk, supponendo che siano coinvolti solo tipi di dati Clojure di base).


In realtà, più vale per i vettori: sono associative, in modo da poterli utilizzare con assoc ((assoc [0 1 2] 0 :foo) rendimenti [:foo 1 2]; indici solo fino a (count the-vector) sono supportati, per assoc ing agli indici che già esiste nel vettore e immediatamente dopo la fine).

Per gli scopi di questa discussione, gli insiemi possono essere considerati per mappare i propri membri a se stessi. Questo è vero in Clojure, nel senso che un set usato come funzione restituisce il membro stesso quando applicato ad esso - e nil per i non membri - e anche nel senso che è ciò che l'implementazione sembra sotto il cofano.

5

Codice esempio supplemento alla ottima risposta di Michał Marczyk:

(def form 
    {:friends 
    '({:id 1, :first-name "bob"} 
    {:id 2, :first-name "sue"}) 
    :languages 
    '({:id 1, :name "Clojure"})}) 

(-> form :friends (nth 0) :first-name) 
;=> "bob" 

(def form' 
    (clojure.walk/prewalk #(if (list? %) (vec %) %) form)) 

(get-in form' [:friends 0 :first-name]) 
;=> "bob" 
+0

+1 per la versione filettata, non ho pensato di esso –