How to replace the last element in a vector in Clojure

As a newbie to Clojure, I often have difficulty expressing the simplest things. For example, to replace the last element in a vector that would be

v[-1]=new_value 

in python, I get the following options in Clojure:

 (assoc v (dec (count v)) new_value) 

which is quite long and expressionless, at least, or

 (conj (vec (butlast v)) new_value) 

worse, since it has an O(n) .

It makes me feel stupid, like a caveman trying to restore a Swiss watch with a club.

What is the correct way for Clojure to replace the last element in a vector?


To support my O(n) class for butlast -version (Clojure 1.8):

 (def v (vec (range 1e6))) #'user/v user=> (time (first (conj (vec (butlast v)) 55))) "Elapsed time: 232.686159 msecs" 0 (def v (vec (range 1e7))) #'user/v user=> (time (first (conj (vec (butlast v)) 55))) "Elapsed time: 2423.828127 msecs" 0 

Thus, basically over 10 times the number of elements is 10 times slower.

+7
clojure
source share
2 answers

I would use

 (defn set-top [coll x] (conj (pop coll) x)) 

For example,

 (set-top [1 2 3] :a) => [1 2 :a] 

But he is also working on lists:

 (set-top '(1 2 3) :a) => (:a 2 3) 

The Clojure stack functions — peek , pop and conj — work on the natural open end of a sequential collection.

But there is no one right way.


How do different solutions respond to an empty vector?

  • Your Python v[-1]=new_value throws an exception, as do yours (assoc v (dec (count v)) new_value) and mine (defn set-top [coll x] (conj (pop coll) x)) .
  • Your (conj (vec (butlast v)) new_value) returns [new_value] . butlast has no effect.
+9
source share

If you insist on being “clean,” your 2 or 3 solutions will work. I prefer to be simpler and more explicit using helper functions from the Tupelo library :

 (s/defn replace-at :- ts/List "Replaces an element in a collection at the specified index." [coll :- ts/List index :- s/Int elem :- s/Any] ...) (is (= [9 1 2] (replace-at (range 3) 0 9))) (is (= [0 9 2] (replace-at (range 3) 1 9))) (is (= [0 1 9] (replace-at (range 3) 2 9))) As with drop-at, replace-at will throw an exception for invalid values of index. 

Similar helper functions exist for

  • insert-in
  • drop in
  • before the name
  • Append

Note that all of the above works equally well for any Clojure list (impatient or lazy) or Clojure vector. The conj solution will fail if you are not careful to always force the input to the vector first, as in your example.

+1
source share

All Articles