2010-11-04 18 views
52

Prepending a una lista es fácil:¿Cuál es la forma idiomática de anteponer a un vector en Clojure?

user=> (conj '(:bar :baz) :foo) 
(:foo :bar :baz) 

Al añadir a un vector es fácil:

user=> (conj [:bar :baz] :foo) 
[:bar :baz :foo] 

Cómo hacer yo prepend (idiomático) a un vector, mientras volviendo a un vector? Esto no funciona, ya que devuelve un ss, no un vector:

user=> (cons :foo [:bar :baz])  
(:foo :bar :baz) 

Esto es feo (IMVHO):

user=> (apply vector (cons :foo [:bar :baz])) 
[:foo :bar :baz] 

Nota: Yo, básicamente, sólo quiero una estructura de datos que puedo añadir y anteponer a. Anexar a listas grandes debería tener una gran penalización de rendimiento, así que pensé en vectores ...

+0

sería negligente no señalar que el último ejemplo 'feo' se puede simplificar en una forma ligeramente menos fea: '(aplicar vector: foo [: bar: baz])' (acaba de sacar el 'contras '). Pero estoy de acuerdo que es un poco incómodo que, más allá de la solución '(vector ...)', básicamente hay 'concat'. Si solo hubiera una sintaxis azucarada/bonita para splatting arguments, en lugar de 'apply' (como' ~ @ 'pero no solo para macros) ... * suspiro * – chbrown

Respuesta

67

Los vectores no están diseñados para anteponerse. Sólo tiene O (n) Prefijo:

user=> (into [:foo] [:bar :baz]) 
[:foo :bar :baz] 

Lo que queremos es más probable una finger tree.

+0

+1 para árboles de dedo - una estructura de datos muy interesante !! – mikera

+1

Una buena manera de poner diapositivas en línea, también: http://talk-finger-tree.heroku.com – 0x89

+2

rrb-vectores pueden concatenarse (y por lo tanto antepuestos) en el tiempo O (log n). Ver https://github.com/clojure/core.rrb-vector – optevo

15

Sé que esta pregunta es antigua, pero nadie dijo nada acerca de las listas de diferencias y como dices que realmente solo quieres algo que puedas agregar y preceder, parece que las listas de diferencias pueden ayudarte. No parecen populares en Clojure, pero son MUY fáciles de implementar y mucho menos complejos que los árboles de dedo, así que hice una pequeña biblioteca de lista de diferencias , e incluso la probé. Estos concatenan en O (1) tiempo (preceder o anexar). La conversión de una lista de a una lista debería costar O (n), que es una buena compensación si está haciendo mucha concatenación. Si no está haciendo una gran cantidad de concatenación , simplemente con las listas, ¿verdad? :)

Estas son las funciones en esta pequeña biblioteca:

dl: Una lista de diferencias es en realidad una función que concats sus propios contenidos con el argumento y devuelve la lista resultante. Cada vez que produce una lista de diferencias, está creando una pequeña función que actúa como una estructura de datos.

dlempty: Desde una lista diferencia simplemente concats su contenido al argumento , una lista vacía diferencia es lo mismo que la función identidad .

Undl: Debido a la diferencia que las listas de hacer, se puede convertir una lista diferencia a una lista de lo normal con sólo llamar con nula, por lo que esta función no es realmente necesario; es solo por conveniencia.

dlcons: conses un elemento al frente de la lista - no totalmente necesario, pero Consing es una operación bastante común y que es sólo una sola línea (como todas las funciones, aquí).

dlappend: concats dos listas de diferencias. Creo que su definición es lo más divertido - ¡compruébalo! :)

Y ahora, aquí está esa pequeña biblioteca - 5 funciones de una línea que le dan una O (1) anexar/anteponer la estructura de datos. No está mal, ¿eh? Ah, la belleza de Lambda Cálculo ...

(defn dl 
    "Return a difference list for a list" 
    [l] 
    (fn [x] (concat l x))) 

; Return an empty difference list 
(def dlempty identity) 

(defn undl 
    "Return a list for a difference list (just call the difference list with nil)" 
    [aDl] 
    (aDl nil)) 

(defn dlcons 
    "Cons an item onto a difference list" 
    [item aDl] 
    (fn [x] (cons item (aDl x)))) 

(defn dlappend 
    "Append two difference lists" 
    [dl1 dl2] 
    (fn [x] (dl1 (dl2 x)))) 

Usted puede verlo en acción con esto:

(undl (dlappend (dl '(1 2 3)) (dl '(4 5 6)))) 

que devuelve:

(1 2 3 4 5 6) 

Esto también devuelve el Lo mismo:

((dl '(1 2 3)) '(4 5 6)) 

¡Diviértete con las listas de diferencias!

actualización

Estas son algunas definiciones que pueden ser más difíciles de entender, pero creo que son mejores:

(defn dl [& elements] (fn [x] (concat elements x))) 
(defn dl-un [l] (l nil)) 
(defn dl-concat [& lists] (fn [x] ((apply comp lists) x))) 

Esto te permite decir algo como esto:

(dl-un (dl-concat (dl 1) (dl 2 3) (dl) (dl 4))) 

Que devolvería

(1 2 3 4) 
2

Como dijo el optevo usuario en los comentarios debajo del dedo árboles respuesta, puede utilizar el lib clojure/core.rrb-vector, que implementa la RRB-árboles:

RRB-árboles construir sobre PersistentVectors de Clojure, añadiendo concatenación de tiempo logarítmica y rebanar . ClojureScript es compatible con la misma API excepto por la ausencia de la función vector-of.

Decidí publicar esto como una respuesta separada, porque creo que esta biblioteca se merece eso. Es compatible con ClojureScript y es mantenido por Michał Marczyk, que es bastante conocido dentro de la comunidad Clojure por su trabajo en la implementación de diversas estructuras de datos.

2

Sugeriría utilizar las funciones de conveniencia built into the Tupelo Library. Por ejemplo:

(append [1 2] 3 ) ;=> [1 2 3 ] 
(append [1 2] 3 4) ;=> [1 2 3 4] 

(prepend 3 [2 1]) ;=> [ 3 2 1] 
(prepend 4 3 [2 1]) ;=> [4 3 2 1] 

en comparación, con Clojure bruto, es fácil cometer un error:

; Add to the end 
(concat [1 2] 3) ;=> IllegalArgumentException 
(cons [1 2] 3) ;=> IllegalArgumentException 
(conj [1 2] 3) ;=> [1 2 3] 
(conj [1 2] 3 4) ;=> [1 2 3 4] 

; Add to the beginning 
(conj  1 [2 3]) ;=> ClassCastException 
(concat 1 [2 3]) ;=> IllegalArgumentException 
(cons  1 [2 3]) ;=> (1 2 3) 
(cons 1 2 [3 4]) ;=> ArityException 
Cuestiones relacionadas