web-dev-qa-db-fra.com

Quelle est la manière idiomatique de faire précéder un vecteur dans Clojure?

Il est facile de préparer une liste:

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

L'ajout à un vecteur est simple:

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

Comment ajouter (idiomatiquement) un vecteur à un vecteur, tout en récupérant un vecteur? Cela ne fonctionne pas car il renvoie une séquence, pas un vecteur:

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

C'est moche (IMVHO):

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

Remarque: Je veux simplement une infrastructure de données à laquelle je peux ajouter et ajouter. L'ajout à de grandes listes devrait avoir une grande pénalité de performance, j'ai donc pensé aux vecteurs ..

54
0x89

Les vecteurs ne sont pas conçus pour le pré-paiement. Vous avez seulement O(n) préfixe:

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

Ce que vous voulez est très probablement un arbre des doigts .

71
kotarak

Je sais que cette question est ancienne, mais personne n'a dit quoi que ce soit sur les listes de différences et puisque vous dites que vous voulez vraiment quelque chose que vous pouvez ajouter et ajouter, il semble que les listes de différences puissent vous aider. Ils ne semblent pas populaires dans Clojure, mais ils sont TRÈS faciles à implémenter et beaucoup moins complexes que les arbres à doigts, donc j'ai fait une petite bibliothèque de listes de différences, tout à l'heure (et même testée). Ceux-ci se concaténent en O(1) temps (ajout ou ajout). La conversion d'une liste de différences en liste devrait vous coûter O (n), ce qui est un bon compromis si vous êtes faire beaucoup de concaténation. Si vous ne faites pas beaucoup de concaténation, alors respectez les listes, non? :)

Voici les fonctions de cette petite bibliothèque:

dl: Une liste de différences est en fait une fonction qui concatène son propre contenu avec l'argument et renvoie la liste résultante. Chaque fois que vous produisez une liste de différences, vous créez une petite fonction qui agit comme une structure de données.

dlempty: Puisqu'une liste de différences concatient juste son contenu à l'argument, une liste de différences vide est la même chose que la fonction d'identité.

ndl: En raison de ce que font les listes de différences, vous pouvez convertir une liste de différences en liste normale simplement en l'appelant avec nil, donc cette fonction n'est pas vraiment nécessaire; c'est juste pour plus de commodité.

dlcons: contient un élément au début de la liste - pas totalement nécessaire, mais le consing est une opération assez courante et c'est juste une ligne (comme toutes les fonctions, ici).

dlappend: concatère deux listes de différences. Je pense que sa définition est la plus amusante - vérifiez-la! :)

Et maintenant, voici cette petite bibliothèque - 5 fonctions d'une ligne qui vous donnent une structure de données O(1) append/prepend). Pas mal, hein? Ah, la beauté de Lambda Calculus. ..

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

Vous pouvez le voir en action avec ceci:

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

qui renvoie:

(1 2 3 4 5 6)

Cela renvoie également la même chose:

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

Amusez-vous avec les listes de différences!

Mise à jour

Voici quelques définitions qui peuvent être plus difficiles à comprendre, mais je pense qu'elles sont meilleures:

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

Cela vous permet de dire quelque chose comme ceci:

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

Qui reviendrait

(1 2 3 4)
17
Bill Burdick

Comme l'utilisateur optevo l'a dit dans les commentaires sous la réponse des arbres à doigts, vous pouvez utiliser le clojure/core.rrb-vector lib, qui implémente les arbres RRB:

Les arbres RRB s'appuient sur les vecteurs persistants de Clojure, ajoutant une concaténation de temps logarithmique et un découpage. ClojureScript est pris en charge avec la même API, sauf pour l'absence de vector-of fonction.

J'ai décidé de poster ceci comme une réponse séparée, parce que je pense que cette bibliothèque le mérite. Il prend en charge ClojureScript et est maintenu par Michał Marczyk , qui est assez connu au sein de la communauté Clojure pour son travail sur la mise en œuvre de diverses structures de données.

2
Rafał Cieślak

Si vous ne craignez pas les quasiquotes, cette solution est en fait assez élégante (pour certaines définitions de "élégant"):

> `[~:foo ~@[:bar :baz]]

[:foo :bar :baz]

J'utilise en fait cela à l'occasion dans du vrai code, car la syntaxe déclarative le rend assez lisible à mon humble avis.

1
drcode

Je suggère d'utiliser les fonctionnalités pratiques intégrées à la bibliothèque Tupelo . Par exemple:

(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 comparaison, avec du Clojure brut, il est facile de se tromper:

; 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
1
Alan Thompson