Monday, March 31, 2014

How to zip in Clojure

In programming, there's time when we have 2 arrays and we want values at the same index of both arrays to have operation on each other producing an array of result. In imperative style, we have to setup a temporary counter variable to store index the current operation is on, increment it until reaching the last value of an array. For example, we want to sum values of each index.

var a = [1,2,3,4];
var b = [9,8,7,6];
var c = [0,0,0,0];
for (var i = 0; i < a.length; i++) {
c[i] = a[i] + b[i];
}
c // => [10, 10, 10]
view raw sum.js hosted with ❤ by GitHub
In functional style, there's a function called 'zip' (Haskell, Ruby) which allows us to pair up values of each index preparing to apply operation on each pair later.

a = [1,2,3,4]
b = [9,8,7,6]
a.zip(b) # => [[1, 9], [2, 8], [3, 7], [4, 6]]
a.zip(b).map { |(x,y)| x + y } # => [10, 10, 10]
view raw sum.rb hosted with ❤ by GitHub
In Haskell, there's even 'zipWith' which apply operation on the pair immediately instead of producing an intermediate array.

But when it comes to Clojure, I wasn't be able to find a function in its standard library with the same behavior. The closest I could find is 'zipmap' that returns back a map which is not exactly what I want. We definitely can work around a little bit to get a vector.

(zipmap [1 2 3] [9 8 7]) ;; => {3 7, 2 8, 1 9}
(vec (zipmap [1 2 3] [9 8 7])) ;; => [[3 7] [2 8] [1 9]]
view raw zipmap.clj hosted with ❤ by GitHub
But as you can see, it doesn't preserve the order of an original vectors.

The next idea I have is to use 'interleave' and 'partition'

(interleave [1 2 3] [9 8 7]) ;; => (1 9 2 8 3 7)
(partition 2 (interleave [1 2 3] [9 8 7])) ;; => ((1 9) (2 8) (3 7))

It's kinda work, but the fact that we have to call 2 functions is not so satisfied.

I went look up and found this Stack Overflow answer. Yes. Just use simple 'map'!

(map vector [1 2 3] [9 8 7]) ;; => ([1 9] [2 8] [3 7])
;; (vector 1 2 3) => [1 2 3]
view raw map.clj hosted with ❤ by GitHub

I think, I overlooked this solution because in Ruby, the language the I'm most comfortable with, 'map' can only operate on only one array.

Learning that Clojure's map can takes any number of collections is an aha moment for me. So now I can sum elements of more than 2 vectors easily.

(map + [1 2 3] [9 8 7] [5 5 5]) ;; => (15 15 15)
view raw map-sum.clj hosted with ❤ by GitHub

Since the second argument can accept any function, creating Clojure records is as easy as this.

(defrecord Coordinate [x y])
(map ->Coordinate [1 2 3] [9 8 7])
;; => (#user.Coordinate{:x 1, :y 9} #user.Coordinate{:x 2, :y 8} #user.Coordinate{:x 3, :y 7})
view raw record.clj hosted with ❤ by GitHub

No comments:

Collectd PostgreSQL Plugin

I couldn't find this link when searching with google https://www.collectd.org/documentation/manpages/collectd.conf.html#plugin-postgresq...