Clojure: How to replace an item in a nested list?

I have this deeply nested list (list of lists) and I want to replace one arbitrary element in the list. How can i do this? (A built-in replacement can replace many occurrences, while I only need to replace one element.)

+7
clojure
source share
5 answers

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
+9
source share

For simple cases, the recursive lookup function will give you what you need with great complexity. when things get a little trickier it’s time to crack a clojure to build a zipper : "Clojure includes a purely functional, generic tree walking and editing using a method called a zipper (in the zip namespace)."

adapted from an example in: http://clojure.org/other_libraries

 (defn randomly-replace [replace-with in-tree] (loop [loc dz] (if (zip/end? loc) (zip/root loc) (recur (zip/next (if (= 0 (get-random-int 10)) (zip/replace loc replace-with) loc))))) 

they will work with any nested (seq'able) even xmls

+6
source share

This is not the answer to your question, but if you have vectors instead of lists:

 user=> (update-in [1 [2 3] 4 5] [1 1] inc) [1 [2 4] 4 5] user=> (assoc-in [1 [2 3] 4 5] [1 1] 6) [1 [2 6] 4 5] 

Therefore, if possible, avoid lists in favor of vectors for better access. If you have to work with lazy-seq from different sources, this, of course, is not so much advice ...

+5
source share

You can use this function and adapt it for your needs (nested lists):

 (defn replace-item "Returns a list with the n-th item of l replaced by v." [lnv] (concat (take nl) (list v) (drop (inc n) l))) 
0
source share

A simple offer from the peanut gallery:

  • copy the internal list to a vector;
  • script these vector elements randomly and with your heart content using assoc ;
  • copy the vector back to the list;
  • replace the nested list with an external list.

This may result in decreased performance; but if it was a performance-sensitive operation, you should work with vectors first.

0
source share

All Articles