4
votes

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....

1

1 Answers

10
votes

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?