Reputation: 55
I'm having trouble figuring out how I can find a coordinate of a character within an array.
I have small ASCII maps that look like this:
+-----+
|XPX X|
|X X|
|X XX|
|X X|
|XX DX|
+-----+
This map is split up by lines into an array so the array looks like:
["+-----+" "|XPX X|" "|X X|" "|X XX|" "|X X|" "|XX DX|" "+-----+"]
What I am trying to do is pass a character such as 'P' and find out the coordinate of the character in the map. In this instance 'P' is located at the coordinate (2,3).
As a starting point, I tried to figure out the column of the character using
(doseq [m map] (println (clojure.string/index-of m "P")))
This returned the output of
nil
2
nil
nil
nil
nil
nil
nil
At this point, I am now confused as to how I can just return '2' and the array index that this row is in.
Upvotes: 1
Views: 312
Reputation: 17859
i would go with something like this (a bit more generalized)
(def field ["+-----+"
"|XPX X|"
"|X ZX|"
"|X XX|"
"|X X|"
"|XX DX|"
"+-----+"])
(defn find-2d [pred field]
(for [[i line] (map-indexed vector field)
[j ch] (map-indexed vector line)
:when (pred ch)]
[i j ch]))
user> (find-2d #{\P} field)
;;=> ([1 2 \P])
user> (find-2d #{\P \Z} field)
;;=> ([1 2 \P] [2 4 \Z])
user> (find-2d #{\D \P \Z} field)
;;=> ([1 2 \P] [2 4 \Z] [5 4 \D])
user> (find-2d #(Character/isUpperCase %) field)
;;=> ([1 1 \X] [1 2 \P] [1 3 \X] [1 5 \X]
;; [2 1 \X] [2 4 \Z] [2 5 \X] [3 1 \X]
;; [3 4 \X] [3 5 \X] [4 1 \X] [4 5 \X]
;; [5 1 \X] [5 2 \X] [5 4 \D] [5 5 \X])
another one is more functional (although a less readable)
(defn find-2d [pred field]
(filter (comp pred last)
(mapcat #(map vector (repeat %1) (range) %2)
(range)
field)))
works exactly the same as the first one
Upvotes: 1
Reputation: 2792
One way to do it - assuming your input is a vector of strings as you have presented, is like so:
(def the-map ["+-----+" "|XPX X|" "|X X|" "|X XX|" "|X X|" "|XX DX|" "+-----+"])
(defn get-index [item coll]
(some #(when ((complement nil?) %) %)
(map-indexed (fn [ix x] (let [i (clojure.string/index-of x item)]
(when i [ix i])))
coll)))
This will return the first instance of whatever item you are looking for, e.g. for "+" it would be [0,0]
. Also (2,3) is actually (1,2) as clojure uses zero-based indexing.
You can shorten this a little by using keep
with identity
(defn get-index [item coll]
(first (keep identity
(map-indexed (fn [ix x] (let [i (clojure.string/index-of x item)]
(when i [ix i])))
coll))))
EDIT:
I realized it's redundant to have (first (keep identity....)
, instead just use some
with identity
:
(defn get-index [item coll]
(some identity
(map-indexed (fn [ix x] (let [i (clojure.string/index-of x item)]
(when i [ix i])))
coll)))
Some answers below gave you a way to get every matching coordinate, rather than the first. All you have to do is change some identity
to remove nil?
in my above version to accomplish this.
For example:
(defn get-index [item coll]
(remove nil?
(map-indexed (fn [ix x] (let [i (clojure.string/index-of x item)]
(when i [ix i])))
coll)))
Finally, if it is really your intention to have indexes be one-based, you can just increment each found index:
(defn get-index [item coll]
(some identity
(map-indexed (fn [ix x] (let [i (clojure.string/index-of x item)]
(when i [(inc ix) (inc i)])))
coll)))
Upvotes: 2
Reputation: 29958
I added another P
char to show how to find all of them. Here is a simpler solution using for
:
(defn strs->array [strs] (mapv vec strs))
(def data ["+-----+"
"|XPX X|"
"|X X|"
"|X XX|"
"|X P X|"
"|XX DX|"
"+-----+"])
(def data-array (strs->array data))
(defn find-chars-2 [ch-array tgt-char]
(let [num-rows (count ch-array)
num-cols (count (first ch-array))]
(for [ii (range num-rows)
jj (range num-cols)
:let [curr-char (get-in ch-array [ii jj])]
:when (= tgt-char curr-char)]
[ii jj])))
with result:
(find-chars-2 data-array \P)) => ([1 2] [4 3])
Using get-in
assumes you have nested vectors, hence the need for strs->array
. We also assume the data is rectangular (not ragged) and haven't put in any error-checking that a real solution would need.
Upvotes: 0
Reputation: 16035
I went a little bit further and wrote a function that find coordinates of all the occurrences of a char.
(let [a ["+-----+" "|XPX X|" "|X X|" "|X XX|" "|X X|" "|XX DX|" "+-----+"]]
(letfn [(find-coords [a ch]
(let [op (fn [f] (comp inc #(f % (count (first a)))))]
(->> a
(clojure.string/join "")
(map-indexed vector)
(filter (comp (partial = ch) second))
(map first)
(map (juxt (op quot) (op rem))))))]
(find-coords a \P)))
=> ([2 3])
(find-coords a \X)
=> ([2 2] [2 4] [2 6] [3 2] [3 6] [4 2] [4 5] [4 6] [5 2] [5 6] [6 2] [6 3] [6 6])
Upvotes: 1
Reputation: 2684
(def strmap ["+-----+" "|XPX X|" "|X X|" "|X XX|" "|X X|" "|XX DX|" "+-----+"])
(defn find-char-2d
[arr char-to-find]
(some->> (map-indexed (fn [i row]
[i (clojure.string/index-of row char-to-find)])
arr)
(filter second)
first
(map inc)))
(println (find-char-2d strmap "Q")) ; prints "nil"
(println (find-char-2d strmap "P")) ; prints "(2, 3)"
The map-indexed
loops through the row
strings, searching each one for your substring while keeping track of the row index i
. The some->>
threading macro passes the result (which is a LazySeq
) to the filter
, which removes all elements with nil
columns. Since we only want the first coordinates (assuming the character you're searching for can only exist once in the map), we select the first element. If the substring doesn't exist in the map, we will get nil
, and the the some->>
will short-circuit so that nil
is returned. Otherwise, the indices will both be incremented (since your coordinates are 1-indexed.
Alternatively, you could linearize the your map, find the index in the linear map, and then calculate the 2d coordinates from the linear index:
(defn find-char-2d
[arr char-to-find]
(some-> (clojure.string/join "" arr)
(clojure.string/index-of char-to-find)
((juxt #(-> (/ % (count arr)) int inc)
#(inc (mod % (count (first arr))))))))
Upvotes: 1