Clojure map-longest
I am trying to write a Clojure utility function called map-longest
(alternate name suggestion appreciated). This function will have the following "signature":
(map-longest fun missing-value-seq c1 & colls)
and will behave similarly to map
, except than it will continue processing the supplied collections until the longest is exhausted. For collections shorter than the longest, when it runs out of values, it will take them from the missing-values-seq
. It should be lazy, but obviously cannot be used with infinite collections.
Example use:
(print (apply str
(map-longest #(str %1 \space %2 \space %3 \newline) (repeatedly "--")
["a1" "a2" "a3"] ["b1" "b2"] ["c1" "c2" "c3" "c4"])))
It should produce the following output:
a1 b1 c1
a2 b2 c2
a3 -- c3
-- -- c4
but I may have the call wrong.
How do I implement this? Does the clojure.core or clojure-contrib library already have something like this? As an alternative to missing-value-seq
, would it be better to pass in a second function to generate the missing values (e.g.: #(identity "--")
in my example)?
Use case: I am writing a small text spider solitaire player as a开发者_JS百科n exercise in learning Clojure/functional programming. I need to be able to display the game tableaus (tableaux for purists :-)).
Here is a 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 have taken the missing-value-fn
approach rather than the missing-value-seq
one.
Update
Updated the 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
Please note that this will replace nil
s in the colls with the value returned by the missing-value-fn
.
This is not completely function you need, but a bit simplified version, so you could get the point:
(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 omitted laziness, and you can add it easily with delay
and force
. I also changed missing-value-seq
to just missing-value
- I believe this is not the problem for you to replace it with 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
An implementation that relies on Clojure's internal laziness is as follows
(defn map-longest
[f missing-value & vs]
(when (seq vs)
(take
(apply max (map count vs))
(apply map
(fn[& ivs]
(apply f ivs))
(map
#(concat % (repeat missing-value))
vs)))))
and
(map-longest (fn[a b c]
(vector a b c))
"--"
["a1" "a2" nil]
["b1" "b2"]
["c1" "c2" nil "c4"])
returns
(["a1" "b1" "c1"] ["a2" "b2" "c2"] [nil "--" nil] ["--" "--" "c4"])
Notice that nil is not the same as missing.
精彩评论