fyquah95
fyquah95

Reputation: 828

Clojure - apply to all but nth element

I have a vector that looks like:

[ "1" "2" "3" "4" ]

I wish to write a function returns the vector to:

[ 1 "2" 3 4 ]
; Note that the second element is still a string

Note that nothing is changed, an entirely new vector is returned. What is the simplest way to do this in clojure?

Upvotes: 2

Views: 295

Answers (2)

Alan Thompson
Alan Thompson

Reputation: 29958

Here is how I would do it. Note that the index is zero-based:

(defn map-not-nth 
  "Transform all elements of coll except the one corresponding to idx (zero-based)."
  [func coll idx]   
  {:pre  [ (<= 0 idx (count coll)) ]
   :post [ (= (count %) (count coll)) 
           (= (nth coll idx) (nth % idx) ) ] }
  (let [coll-tx (map func coll)   ; transform all data
        result  (flatten  [ (take idx coll-tx)        ; [0..idx-1]
                            (nth coll idx)            ; idx
                            (drop (inc idx) coll-tx)  ; [idx+1..N-1]
                          ] ) ]
    result ))

(def xx [ 0 1 2 3 4 ] )

(prn (map-not-nth str xx 0))
(prn (map-not-nth str xx 1))
(prn (map-not-nth str xx 2))
(prn (map-not-nth str xx 3))
(prn (map-not-nth str xx 4))

Result is:

user=> (prn (map-not-nth str xx 0))
(0 "1" "2" "3" "4")
user=> (prn (map-not-nth str xx 1))
("0" 1 "2" "3" "4")
user=> (prn (map-not-nth str xx 2))
("0" "1" 2 "3" "4")
user=> (prn (map-not-nth str xx 3))
("0" "1" "2" 3 "4")
user=> (prn (map-not-nth str xx 4))
("0" "1" "2" "3" 4)

Upvotes: 1

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91554

map-indexed is a decent choice. call a function you pass with the value of one of the items form your input and the index where it was found (index first). that function can choose to produce a new value or return the existing one.

user> (map-indexed (fn [i v]
                     (if-not (= 1 i)
                       (Integer/parseInt v)
                       v))
                   [ "1" "2" "3" "4"])
(1 "2" 3 4)

When the if returns v it is the exact same value in the resulting map so you keep the benefits of structural sharing in the parts you choose to keep. If you want the output to be kept as a vector then you can use mapv and pass the index sequence your self.

user> (mapv (fn [i v]
              (if-not (= 1 i)
                (Integer/parseInt v)
                v))
            (range)
            [ "1" "2" "3" "4"])
[1 "2" 3 4]

there are many ways to write this

Upvotes: 5

Related Questions