Clojure map-longest

I am trying to write a Clojure utility utility called map-longest (alternate name score). This function will have the following "signature":

 (map-longest fun missing-value-seq c1 & colls) 

and will behave similarly to map , except that it will continue to process the collected collections until the longest is exhausted. For collections, shorter than the longest, when it ends with values, it takes them from missing-values-seq . It should be lazy, but obviously cannot be used with endless collections.

Usage example:

 (print (apply str (map-longest #(str %1 \space %2 \space %3 \newline) (repeatedly "--") ["a1" "a2" "a3"] ["b1" "b2"] ["c1" "c2" "c3" "c4"]))) 

He should create the following output:

 a1 b1 c1 a2 b2 c2 a3 -- c3 -- -- c4 

but I may have the wrong call.

How to implement this? Does the clojure.core library or clojure -contrib already have something like this? As an alternative to missing-value-seq , would it be better to pass a second function to generate missing values ​​(for example: #(identity "--") in my example)?

Use case: I write a little spider solitaire in the form of exercises when learning Clojure / functional programming. I need to display game tables (tables for purists :-)).

+5
clojure map
source share
2 answers

Here is the solution:

 (defn map-longest ([fn missing-value-fn c1] (map fn c1)) ([fn missing-value-fn c1 & colls] (lazy-seq (when (not-every? empty? (conj colls c1)) (let [firsts (map first (conj colls c1))] (cons (apply fn (map #(if (nil? %) (missing-value-fn) %) firsts)) (apply map-longest (conj (map rest colls) (rest c1) missing-value-fn fn)))))))) 

Test:

 user=> (print (apply str (map-longest #(str %1 \space %2 \space %3 \newline) #(identity "--") ["a1" "a2" "a3"] ["b1" "b2"] ["c1" "c2" "c3" "c4"]))) a1 b1 c1 a2 b2 c2 a3 -- c3 -- -- c4 nil 

Note that I took the missing-value-fn approach, not the missing-value-seq .

Update

Updated code to take care of the case mentioned by ffriend in the comments.

Test:

 user=> (print (apply str (map-longest #(str %1 \space %2 \space %3 \newline) #(identity "--") ["a1" "a2" nil] ["b1" "b2"] ["c1" "c2" nil "c4"]))) a1 b1 c1 a2 b2 c2 -- -- -- -- -- c4 nil 

Note that this will replace nil in the columns with the value returned by missing-value-fn .

+4
source share

This is not the complete function that you need, but a slightly simplified version, so you can get the following information:

 (defn first-or-val [col missing] (if (empty? col) missing (first col))) (defn map-longest [f missing-value & cols] (loop [cols cols, ret '()] (cond (every? empty? cols) (reverse ret) :else (recur (map rest cols) (conj ret (apply f (map #(first-or-val % missing-value) cols))))))) 

I missed laziness and you can easily add it with delay and force . I also changed missing-value-seq to missing-value - I believe this is not a problem for you to replace it with a sequence or generator.

Example:

 (print (apply str (map-longest #(str %1 \space %2 \space %3 \newline) "--" ['a1 'a2 'a3] ['b1 'b2] ['c1 'c2 'c3 'c4]))) 

Result:

 a1 b1 c1 a2 b2 c2 a3 -- c3 -- -- c4 
+1
source share

All Articles