Transitioning To Sequences

Problem #157 in 4Clojure.com is listed as a very simple problem. Despite having used Clojure’s map function a lot, I saw the solution to this problem recursively using loop and recur, by walking through each element of the function and determining its count. The following solution came from http://blog.qinjian.me/2012/12/21/4clojure_solutions/ . Look for #157 in the midst of quite a few solutions.


(fn __[coll]
  (loop [x (- (count coll) 1)
      l []]
  (if (>= x 0)
    (recur (- x 1)
        (conj l (list (nth coll x) x)))
    (reverse l))))

This solution makes more sense to me than this solution.


#(map list % (range))

It just doesn’t look as obvious. But the prevailing wisdom is to use Clojure’s sequence functions to handle problems like this.

Advertisements

8 Comments

Filed under Clojure

8 responses to “Transitioning To Sequences

  1. Interesting case, I solved this problem with

    #(partition 2 (interleave % (range)))

    And when I saw this solution I was impressed as well. I often use map with a function and a single sequence like:

    (map * (range 1 10))

    and the way it works is obvious. But reading the map function doc there is another use that is

    (map f col1 col2 col3)

    When you specify more than one collection it apply the f function to the first item in all collection such as:

    (f (first col1) (first col2) (first col3))

    and it repeat this for every item in the collections (first item of every collection, then second item of every collection etc).
    Ex:

    (map vector (range 1 10) ‘(:a :b :c :d :e :f :g :h :i) (range 1000 1010))

    ;=> ([1 :a 1000] [2 :b 1001] [3 :c 1002] [4 :d 1003] [5 :e 1004] …)

    And since I discovered this, It actually make perfectly sense, and I found myself using it various times.

    So I think that a Clojure construct is going to look obvious only after you internalize it.
    Once the use it become idiomatic then automatically it become more readable.

    For instance it took me a couple of reading before understanding your solution.

    Bruno

    • Octopusgrabbus

      Thanks. You have hit on an important point, internalizing. I’m getting there, and just require more use of the language.

  2. Actually the loop/recur solution you show is non-obvious to me. I have to read all of it in detail to know what it’s doing. It’s building lists up with conj, recursing, and reversing all based on indexes. The other solution you put in is clearer to me.

    #(map list % (range))

    To me this reads much more simply. Like so:

    Map the ‘list’ function across the remaining arguments (which must be sequencable collections) . The remaining arguments are a vector [:a :b :c] and a list of the result of the lazy sequence that range returns.

    So as you map over the vector you get the first element of the vector and the first element of the range (which when range is called with no arguments will be 0 because range returns a infinite lazy sequence) and then make a list out of them, and repeat until you get to the end of the vector (because it’s the shortest of the collections your mapping over). Map takes all the results and puts them in a list as they are created. Finally you get the result ((:a 0) (:b 1) (:c 2))

    So to me it reads exactly like what it’s doing. The other one certainly isn’t wrong, so I don’t mean to imply that at all. I think it comes down to being comfy with the idea of applying functions in a mathematical sense. Such as: f(g(x)).

    Anyway, good discussion.

  3. My solution is similar to the second option:

    map #(vector %2 %) (range)

    which, when applied to the problem results in

    (map #(vector %2 %) (range) [:a :b :c])

    The nice thing about map is it will take any number of sequences and will apply the fuction to the element off each sequence. It terminates when the shortest sequence is completely read. The first solution essentially emulating the “map” function with loop/recur and the “range” function with “x” and the operations to it.

    Map reads threw a sequence, applying a function to each element, returning a sequence of equal size. This will replace the loop, recur, if conj and reverse statements in the former functions. Range simple returns a sequence of numbers.

    If you really prefer using the loop, it can be simplified with:

    (fn [coll] (loop [x 0 rtnval [] [f & r] coll]
    (if f
    (recur (inc x) (conj rtnval [f x]) r)
    rtnval)))

    • Octopusgrabbus

      I have to get used to the fact that map will stop realizing the unlimited range sequence, when map runs out of sequence elements to process.

  4. toujw

    You could also simply have used:

    #(keep-indexed (fn [i x] [x i]) %)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s