I'd like to build a function, which, given a 2D matrix and some element from that matrix, will return the indexes of the element's position:
(get-indices [[1 2 3] [4 5 6] [7 8 9]] 6)
;=> [1 2]
which, given back to get-in, will return the element itself:
(get-in [[1 2 3] [4 5 6] [7 8 9]] [1 2])
;=> 6
I wanted the function (get-indices) to be fast, so I was thinking about doing a macro which will expand to something similar to the (cond ...)
part of this function (but generic for every 2D matrix of size NxN):
(defn get-indices
[matrix el]
(let [[[a b c] [d e f] [g h i]] matrix]
(cond
(= a el) [0 0]
(= b el) [0 1]
(= c el) [0 2]
(= d el) [1 0]
(= e el) [1 1]
(= f el) [1 2]
(= g el) [2 0]
(= h el) [2 1]
(= i el) [2 2])))
I came up with this macro:
(defmacro get-indices
[matrix el]
(let [size (count matrix)
flat (flatten matrix)
compare-parts (map #(list '= % el) flat)
indices (for [x (range size) y (range size)] [x y])]
(cons 'cond (interleave compare-parts indices))))
It seemed just nice... But when called with var, not a direct value, it throws an exception:
(def my-matrix [[1 2 3] [4 5 6] [7 8 9]])
(get-indices my-matrix 6)
;=> java.lang.UnsupportedOperationException: count not supported on this
; type: Symbol (NO_SOURCE_FILE:0)
To me it seems like the symbol "matrix" isn't resolved to value at macro expansion time or something like that, but I'm absolute beginner in macros...
How can I make this macro to work also with vars as arguments?
I was also thinking about using syntax-quote etc., but I'd like to avoid having (let ...)
as a part of the macro output and also didn't know how to implement (interleave compare-parts indices)
within the syntax-quote....
Writing this as a macro is a disastrous choice. As a function it's pretty simple, and more efficient than what you wanted your macro to expand to anyway:
(defn get-indices [matrix el]
(let [h (count matrix), w (count (first matrix))]
(loop [y 0, x 0, row (first matrix), remaining (rest matrix)]
(cond (= x w) (recur (inc y) 0 (first remaining), (rest remaining))
(= y h) nil
(= (first row) el) [y x]
:else (recur y (inc x) (rest row) remaining)))))
Conversely, as a macro it is simply impossible. Macros are for writing code at compile-time - how could you generate the cond-based code for 2D matrices at compile time, if you don't know the matrix's size until runtime?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With