Like everyone else, using lists is really not very good if you need to do such things. Random access is what vectors are made for. assoc-in does it efficiently. With lists, you cannot get away from recursion down in sublists and replace most of them with modified versions of yourself all the way to the top.
This code will do this, albeit inefficiently and clumsily. Borrowing from dermatitis:
(defn replace-in-list [coll nx] (concat (take n coll) (list x) (nthnext coll (inc n)))) (defn replace-in-sublist [coll ns x] (if (seq ns) (let [sublist (nth coll (first ns))] (replace-in-list coll (first ns) (replace-in-sublist sublist (rest ns) x))) x))
Using:
user> (def x '(0 1 2 (0 1 (0 1 2) 3 4 (0 1 2)))) #'user/x user> (replace-in-sublist x [3 2 0] :foo) (0 1 2 (0 1 (:foo 1 2) 3 4 (0 1 2))) user> (replace-in-sublist x [3 2] :foo) (0 1 2 (0 1 :foo 3 4 (0 1 2))) user> (replace-in-sublist x [3 5 1] '(:foo :bar)) (0 1 2 (0 1 (0 1 2) 3 4 (0 (:foo :bar) 2)))
You will get an IndexOutOfBoundsException if you give n more than the length of the subscriptions. It is also not tail recursive. This is also not idiomatic, because good Clojure code eliminates the use of lists for everything. This is terrible. I would probably use mutable Java arrays before using this. I think you get the point.
Edit
Reasons why lists in this case are worse than vectors:
user> (time (let [x '(0 1 2 (0 1 (0 1 2) 3 4 (0 1 2)))] ;' (dotimes [_ 1e6] (replace-in-sublist x [3 2 0] :foo)))) "Elapsed time: 5201.110134 msecs" nil user> (time (let [x [0 1 2 [0 1 [0 1 2] 3 4 [0 1 2]]]] (dotimes [_ 1e6] (assoc-in x [3 2 0] :foo)))) "Elapsed time: 2925.318122 msecs" nil
You also do not need to write assoc-in yourself, it already exists. Look at the implementation for assoc-in sometime; it is simple and understandable (compared to the list) thanks to vectors that provide efficient and simple random access by index through get .
You also do not need to specify vectors, as you should specify lists. Lists in Clojure strongly mean "I'm calling a function or macro here."
Vectors (and mappings, sets, etc.) can intersect through seq s. You can transparently use vectors as a list, so why not use vectors and have the best of both worlds?
Vectors are also highlighted visually. Clojure code is smaller from huge blob parens than other Lisps, due to the widespread use of [] and {} . Some people find this annoying; I find it easier to read. (My editor syntax emphasizes () , [] and {} differently, which helps even more.)
In some cases, I would use a list for the data:
- If I have an ordered data structure that needs to grow from the front, I will never need random access to
- Creating seq "manually" as through
lazy-seq - Writing a macro that needs to return a code as data