user1418084
user1418084

Reputation:

How to iterate over ArrayMap in clojure?

I am totally new to clojure (started learning yesterday) and functional programming so please excuse my ignorance. I've been trying to read a lot of the clojure documentation, but much of it is totally over my head.

I'm trying to iterate over an ArrayMap of this set up:

{city1 ([[0 0] [0 1] [1 1] [1 0]]), city2 ([[3 3] [3 4] [4 4] [4 3]]), city3 ([[10 10] [10 11] [11 11] [11 10]])} 

(^hopefully that syntax is correct, that is what it looks like my terminal is printing)

where the city name is mapped to a vector of vectors that define the points that make up that city's borders. I need to compare all of these points with an outside point in order to determine if the outside point is in one of these cities and if so which city it is in.

I'm using the Ray Casting Algorithm detailed here to determine if an outside point is within a vector of vectors.

Upvotes: 2

Views: 1039

Answers (1)

xsc
xsc

Reputation: 6073

Maps actually implement the clojure.lang.ISeq interface which means that you can use all the higher-level sequence operations on them. The single elements are pairs of the form [key value], so, to find the first element that matches a predicate in-city? you could e.g. use some:

(some
  (fn [[city-name city-points]]                  ;; the current entry of the map
    (when (in-city? the-other-point city-points) ;; check the borders
      city-name))                                ;; return the name of a matching city
  cities)

You might also use keep to find all elements that match the predicate but I guess there is no overlap between cities in your example.


Update: Let's back off a little bit, since working with sequences is fun. I'm not gonna dive into all the sequence types and just use vectors ([1 2 3 ...]) for examples.

Okay, for a start, let's access our vector:

(first [1 2 3]) ;; => 1
(rest [1 2 3])  ;; => [2 3]
(last [1 2 3])  ;; => 3
(nth [1 2 3] 1) ;; => 2

The great thing about functional programming is, that functions are just values which you can pass to other functions. For example, you might want to apply a function (let's say "add 2 to a number") to each element in a sequence. This can be done via map:

(map
  (fn [x]
    (+ x 2))
  [1 2 3])
;; => [3 4 5]

If you haven't seen it yet, there is a shorthand for function values where % is the first parameter, %2 is the second, and so on:

(map #(+ % 2) [1 2 3]) ;; => [3 4 5]

This is concise and useful and you'll probably see it a lot in the wild. Of course, if your function has a name or is stored in a var (e.g. by using defn) you can use it directly:

(map pos? [-1 0 1]) ;; => [false false true]

Using the predicate like this does not make a lot of sense since you lose the actual values that produce the boolean result. How about the following?

(filter pos? [-1 0 1]) ;; => [1]
(remove pos? [-1 0 1]) ;; => [-1 0]

This selects or discards the values matching your predicate. Here, you should be able to see the connection to your city-border example: You want to find all the cities in a map that include a given point p. But maps are not sequences, are they? Indeed they are:

(seq {:a 0 :b 1}) ;; => [[:a 0] [:b 1]]

Oh my, the possibilities!

(map first {:a 0 :b 1})                 ;; => [:a :b]
(filter #(pos? (second %)) {:a 0 :b 1}) ;; => [[:b 1]]

filter retrieves all the matching cities (and their coordinates) but since you are only interested in the names - which are stored as the first element of every pair - you have to extract it from every element, similarly to the following (simpler) example:

(map first (filter #(pos? (second %)) {:a 0 :b 1})) 
:: => [:b]

There actually is a function that combines map and filter. It's called keep and return every non-nil value its predicate produces. You can thus check the first element of every pair and then return the second:

(keep
  (fn [pair]
    (when (pos? (second pair))
      (first pair)))
  {:a 0 b 1})
;; => [:b]

Everytime you see yourself using a lot of firsts and seconds, maybe a few rests inbetween, you should think of destructuring. It helps you access parts of values in an easy way and I'll not go into detail here but it can be used with sequences quite intuitively:

(keep
  (fn [[a b]] ;; instead of the name 'pair' we give the value's shape!
    (when (pos? b)
      a))
  {:a 0 :b 1})
;; => [:b]

If you're only interested in the first result you can, of course, directly access it and write something like (first (keep ...)). But, since this is a pretty common use case, you get some offered to you by Clojure. It's like keep but will not look beyond the first match. Let's dive into your city example whose solution should begin to make sense by now:

(some
  (fn [[city-name city-points]]
    (when (in-city? p city-points)
      city-name))
  all-cities)

So, I hope this can be useful to you.

Upvotes: 5

Related Questions