Put an element to the tail of a collection

CollectionsClojure

Collections Problem Overview


I find myself doing a lot of:

(concat coll [e]) where coll is a collection and e a single element.

Is there a function for doing this in Clojure? I know conj does the job best for vectors but I don't know up front which coll will be used. It could be a vector, list or sorted-set for example.

Collections Solutions


Solution 1 - Collections

Some types of collections can add cheaply to the front (lists, seqs), while others can add cheaply to the back (vectors, queues, kinda-sorta lazy-seqs). Rather than using concat, if possible you should arrange to be working with one of those types (vector is most common) and just conj to it: (conj [1 2 3] 4) yields [1 2 3 4], while (conj '(1 2 3) 4) yields (4 1 2 3).

Solution 2 - Collections

concat does not add an element to the tail of a collection, nor does it concatenate two collections.

concat returns a seq made of the concatenation of two other seqs. The original type of the collections from which seqs may be inferred are lost for the return type of concat.

Now, clojure collections have different properties one must know about in order to write efficient code, that's why there isn't a universal function available in core to concatenate collections of any kind together. To the contrary, list and vectors do have "natural insertion positions" which conj knows, and does what is right for the kind of collection.

Solution 3 - Collections

This is a very small addendum to @amalloy's answer in order to address OP's request for a function that always adds to the tail of whatever kind of collection. This is an alternative to (concat coll [x]). Just create a vector version of the original collection:

(defn conj*
  [s x]
  (conj (vec s) x))

Caveats:

If you started with a lazy sequence, you've now destroyed the laziness--i.e. the output is not lazy. This may be either a good thing or a bad thing, depending on your needs.

There's some cost to creating the vector. If you need to call this function a lot, and you find (e.g. by benchmarking with Criterium) that this cost is significant for your purposes, then follow the other answers' advice to try to use vectors in the first place.

Solution 4 - Collections

To distill the best of what amalloy and Laurent Petit have already said: use the conj function.

One of the great abstractions that Clojure provides is the Sequence API, which includes the conj function. If at all possible, your code should be as collection-type agnostic as it can be, instead using the seq API to handle operations on collections and picking a particular collection type only when you need to be specific.

If vectors are a good match, then yes, conj will be adding items onto the end. If use lists instead, then conj will be adding things to the front of your collection. But if you then use the standard seq API functions for pulling items from the "top" of a collection (the back of a vector, the front of a list), it doesn't matter which implementation you use, because it will always use the one with best performance and thus adding and removing items will be consistent.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMichiel BorkentView Question on Stackoverflow
Solution 1 - CollectionsamalloyView Answer on Stackoverflow
Solution 2 - CollectionsLaurent PetitView Answer on Stackoverflow
Solution 3 - CollectionsMarsView Answer on Stackoverflow
Solution 4 - CollectionssemperosView Answer on Stackoverflow