2

I've been trying to idiomatically loop through a nested vector like below:

[[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]]

I also need to return the coordinates once I've found a value. eg The call (find-key-value 3) should return [1 2]

This is what I have so far but its not giving me the output that I need it would return ([] [] [] [] [] [1 2] [] [] []) where as i only need [1 2]

(defn find-key-value
  [array value]
  (for [x (range 0 (count array))]
    (loop [y   0
           ret []]
      (cond
        (= y (count (nth array x))) [x y]
        :else (if (= value (get-in array [x y]))
                (recur (+ 1 y) (conj ret [x y]))
                (recur (+ 1 y) ret))))))

Anyone have any ideas on how I can fix my code to get to my desired solution or have a better approach in mind!

2
  • It seems that (find-key-value 3) should return coordinates [1 5], not [1 2] Commented Apr 9, 2021 at 23:31
  • @AlanThompson - it appears to me that [1 2] means second sub-collection (index 1) at third element of sub-collection (index 2). Commented Apr 10, 2021 at 16:58

6 Answers 6

4

A list comprehension can be used to find coordinates of all values satisfying a predicate:

(defn find-locs [pred coll]
  (for [[i vals] (map-indexed vector coll)
        [j val] (map-indexed vector vals)
        :when (pred val)]
    [i j]))

(find-locs #(= 3 %) [[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]])
=> ([1 5])

(find-locs zero? [[0 1 1] [1 1 1] [1 0 1]])
=> ([0 0] [2 1])

The posed question seems to imply that the keywords in the inputs should be ignored, in which case the answer becomes:

(defn find-locs-ignore-keyword [pred coll]
  (for [[i vals] (map-indexed vector coll)
        [j val] (map-indexed vector (remove keyword? vals))
        :when (pred val)]
    [i j]))

(find-locs-ignore-keyword #(= 3 %) [[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]])
=> ([1 2])
Sign up to request clarification or add additional context in comments.

Comments

1

there is a function in clojure core, which exactly suites the task: keep-indexed. Which is exactly indexed map + filter:

(defn find-val-idx [v data]
  (ffirst (keep-indexed
           (fn [i row]
             (seq (keep-indexed
                   (fn [j [_ x]] (when (= v x) [i j]))
                   (partition 2 row))))
           data)))

user> (find-val-idx 3 [[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]])
;;=> [1 2]

user> (find-val-idx 10 [[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]])
;;=> nil

user> (find-val-idx 1 [[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]])
;;=> [0 0]

Comments

0

There is a map-indexed that is sometimes helpful. See the Clojure Cheatsheet and other docs listed here.

==> Could you please edit the question to clarify the search conditions?


Here is an outline of what you could do to search for the desired answer:

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

(defn coords
  [data pred]
  (let [result (atom [])]
    (doseq [row (range (count data))
            col (range (count (first data)))]
      (let [elem    (get-in data [row col])
            keeper? (pred elem)]
        (when keeper?
          (swap! result conj [row col]))))
    (deref result)))

(dotest
  (let [data [[11 12 13]
              [21 22 23]
              [31 32 33]]
        ends-in-2? (fn [x] (zero? (mod x 2)))]
    (is= (coords data ends-in-2?)
      [[0 1]
       [1 1]
       [2 1]])))

It is based on the same template project as the docs. There are many variations (for example, you could use reduce instead of an atom).

Please review the docs listed above.

1 Comment

map-indexed would only work for one layer for the vector i need the both the x and y
0
(defn vec-to-map [v] (into {} (into [] (map vec (partition 2 v)))))
(defn vec-vals [v] (vals (vec-to-map v)))
(defn map-vec-index [v el] (.indexOf (vec-vals v) el))

(defn find-val-coord
  ([arr val] (find-val-coord arr val 0))
  ([arr val counter]
    (let [row (first arr)
          idx (map-vec-index row val)]
       (cond (< 0 idx) [counter idx]
             :else (recur (rest arr) val (inc counter)))))) 

(find-val-coord arr 3)    ;; => [1 2]

We can also write functions to pick value or corresponding key from array when coordinate is given:

(defn vec-keys [v] (keys (vec-to-map v)))

(defn get-val-coord [arr coord]
  (nth (vec-vals (nth arr (first coord))) (second coord)))

(defn get-key-coord [arr coord]
  (nth (vec-keys (nth arr (first coord))) (second coord)))


(get-val-coord arr [1 2]) ;; => 3
(get-key-coord arr [1 2]) ;; => :c

Comments

0

I might be over-engineering this answer slightly, but here is a non-recursive and non-lazy approach based on a single loop that will work for arbitrary and mixed levels of nesting and won't suffer from stack overflow due to recursion:

(defn find-key-value [array value]
  (loop [remain [[[] array]]]
    (if (empty? remain)
      nil
      (let [[[path x] & remain] remain]
        (cond (= x value) path
              (sequential? x)
              (recur (into remain
                           (comp (remove keyword?)
                                 (map-indexed (fn [i x] [(conj path i) x])))
                           x))
              :default (recur remain))))))

(find-key-value [[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]] 3)
;; => [1 2]

(find-key-value [[:a 1 [[[[[:c]]]] [[[9 [[[3]] :k]] 119]]]] [:a [[[1]]] :b 1]] 3)
;; => [0 1 1 0 0 1 0 0 0]

(find-key-value (last (take 20000 (iterate vector 3))) 3)
;; => [0 0 0 0 0 0 0 0 0 0 0 0 0 ...]

Comments

0

A simpler solution, assuming 2D array where the inner vectors are key value vectors, uses flattening of the 2D array and .indexOf.

(defn find-coord [arr val]
  (let [m (count (first arr))
        idx (.indexOf (flatten arr) val)]
    [(quot idx m) (quot (dec (mod idx m)) 2)]))
(find-coord arr 3) ;;=> [1 2]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.