2
votes

I wish to implement (in Clojure) a nested for loop that converts every element in a 2D array into zero. Like the C code written below.

void set_to_zero(int n, int m[v][v]) {
          int i, j;
          for(i = 0; i < n; i++)
            for(j = 0; j < n; j++)
              m[i][j] = 0;
        }

This is what I was able to do

(defn trial [n m]
  (loop [i 0
         j 0]
    (if (= i (count (range n)))
      (println m)
      (if (= j (count (range n)))
        (recur i j)
        (assoc-in m[i j] 0)
        )
      )
    )
  )

This is what I get: i.e only one element changes and the rest stays same.

(trial 4 [[9 8 2 3][8 4 5 6][6 1 8 9][3 1 8 9]])
=> [[0 8 2 3] [8 4 5 6] [6 1 8 9] [3 1 8 9]]

Update

(defn trial [n m]
  (for [i (range n)
         j (range n)]
    (if (> i n)
      m
      (if-not (> j n)
        ;(recur (inc i) j)
        (assoc-in m[i j] 0)
        ;(println i j)
        )
      )
    )
  )

New result

(trial 4 [[9 8 2 3][8 4 5 6][6 1 8 9][3 1 8 9]])
=>
([[0 8 2 3] [8 4 5 6] [6 1 8 9] [3 1 8 9]]
 [[9 0 2 3] [8 4 5 6] [6 1 8 9] [3 1 8 9]]
 [[9 8 0 3] [8 4 5 6] [6 1 8 9] [3 1 8 9]]
 [[9 8 2 0] [8 4 5 6] [6 1 8 9] [3 1 8 9]]
 [[9 8 2 3] [0 4 5 6] [6 1 8 9] [3 1 8 9]]
 [[9 8 2 3] [8 0 5 6] [6 1 8 9] [3 1 8 9]]
 [[9 8 2 3] [8 4 0 6] [6 1 8 9] [3 1 8 9]]
 [[9 8 2 3] [8 4 5 0] [6 1 8 9] [3 1 8 9]]
 [[9 8 2 3] [8 4 5 6] [0 1 8 9] [3 1 8 9]]
 [[9 8 2 3] [8 4 5 6] [6 0 8 9] [3 1 8 9]]
 [[9 8 2 3] [8 4 5 6] [6 1 0 9] [3 1 8 9]]
 [[9 8 2 3] [8 4 5 6] [6 1 8 0] [3 1 8 9]]
 [[9 8 2 3] [8 4 5 6] [6 1 8 9] [0 1 8 9]]
 [[9 8 2 3] [8 4 5 6] [6 1 8 9] [3 0 8 9]]
 [[9 8 2 3] [8 4 5 6] [6 1 8 9] [3 1 0 9]]
 [[9 8 2 3] [8 4 5 6] [6 1 8 9] [3 1 8 0]])

At the moment, it changes all the elements into 0 but does it separately would like it to return as one array with all the elements equal to zero.

P.S I am sure there are more efficient ways to achieve all zeros in a 2D vector but I'm particularly interested in the for loop method since it's popular in other languages and can help one more easily translate codes from other languages to Clojure (in some cases).

Thanks.

6
for is not a loop. It is a list comprehension. - akond
... and if does not work the way your comments there suggest either - cfrick

6 Answers

3
votes

Since you already have the sizes of the structure (its a vector of vectors) I think there is no need to pass in any sizes. So the one thing to make sure is to keep the vectors (many tools in the clojure belt use (lazy) sequences).

Using mapv does that. The function to map with can be (constantly 0). Then map that again over the outer vector. E.g.

Plain clojure:

(mapv (partial mapv (constantly 0)) [[9 8 2 3][8 4 5 6][6 1 8 9][3 1 8 9]])
; → [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]]

Alternative:

Using specter:

(setval [ALL ALL] 0 [[9 8 2 3][8 4 5 6][6 1 8 9][3 1 8 9]])
; → [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]]
2
votes

First, you're never increasing the values of i and j. So, they never change. Thus, you never get to the recur call.

They way you phrase your question gives the impression that you think, you edit the vector in-place. You don't. With every assoc-in you're creating a new vector. (A new associative data-structure to be more precise, but regardless.) A lot of copying will happen under the hoods.

My take on this is that you'd best create a fresh data-structure from the sizes of the existing one. If the nested vectors can be of differing sizes, the code @cfrick wrote (map ... constantly) is good. If all of the nested vectors have the same size, there's a simpler alternative. Try to find it and tell us how it goes. :-)

2
votes

Clojure arrays are immutable, so if you want to operate in an imperative/mutable fashion you need to use an atom. Consider the following code:

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(defn new-array
  [M N]
  (vec (for [i (range M)] 
         (vec (for [j (range N)]
                (* i j))))))

(defn set-array-elem
  [arr i j val]
  (assoc-in arr [i j] val))

(defn change-array
  [arr]
  (let [work (atom arr)]
    (doseq [i (range (count @work))]
      (doseq [j (range (count (get @work i)))]
        (swap! work set-array-elem i j
          (* (inc i) (+ 3 j))))) ; set it to an "interesting" value
    @work))

(dotest
  (let [arr1 (new-array 3 5)]
    (is= arr1
      [[0 0 0 0 0]
       [0 1 2 3 4]
       [0 2 4 6 8]])
    (is= (change-array arr1)
      [[3  4  5  6  7]
       [6  8 10 12 14]
       [9 12 15 18 21]])))

Function set-array-elem returns a modified copy of the input array. The swap! in change-array calls this function and keeps the output in the atom work, replacing the previous immutable value. Thus we slowly transition from the original array to the final result, one element at at time.

I understand this is a learning exercise. If you ever need to manipulate arrays (nested vectors), please consider using either tupelo.array or tupelo.array.mutable and save a lot of writing (& debugging!)

1
votes

You can build a list of 0 with repeat:

(repeat 3 0)
; (0 0 0)

You can convert that into a vector with vec:

(vec (repeat 3 0))
; [0 0 0]

You just need to replace 3 with the length of each sub vectors:

(mapv #(-> (count %) (repeat 0) vec) [[1] [2 3] [4 5 6]])
; [[0] [0 0] [0 0 0]]
1
votes

So if you know what the dimensions of your array are, create a new one.

(defn make-ary [m n]
  (vec (repeat m (vec (repeat n 0)))))
0
votes

I guess i was using assoc-in the wrong way. Was meant to use recur to implement assoc-in. I assume that was why I got several instances of the 2D vector returned instead of one. Using recur helped do that.

Thanks @cfrick @Stefan Kamphausen @Alan Thompson for pointing me to the right direction.

(defn trial [n m]
  (loop [i 0
         j 0
         m m
         ]
    (if (= i n)
      m
      (if (= j (dec n))
        (recur (inc i) 0 (assoc-in m[i j] 0))
        (recur i (inc j) (assoc-in m[i j] 0))
        )
      )
    )
  )

(trial 8 [[6 1 8 8 6 1 8 8][8 4 5 6 6 1 8 8][6 1 8 8 6 1 8 8][3 1 8 9 6 1 8 8][6 1 8 8 6 1 8 8][6 1 8 8 6 1 8 8][6 1 8 8 6 1 8 8][6 1 8 8 6 1 8 8]])

[[0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0]]