# Changing map behaviour in Clojure

I need to modify map function behavior to provide mapping not with minimum collection size but with maximum and use zero for missing elements.

Standard behavior:

``````(map + [1 2 3] [4 5 6 7 8]) => [5 7 9]
``````

Needed behavior:

``````(map + [1 2 3] [4 5 6 7 8]) => [5 7 9 7 8]
``````

I wrote function to do this, but it seems not very extensible with varargs.

``````(defn map-ext [f coll1 coll2]
(let [mx (max (count coll1) (count coll2))]
(map f
(concat coll1 (repeat (- mx (count coll1)) 0))
(concat coll2 (repeat (- mx (count coll2)) 0)))))
``````

Is there a better way to do this?

## 6 Answers

Another lazy variant, usable with an arbitrary number of input sequences:

``````(defn map-ext [f ext & seqs]
(lazy-seq
(if (some seq seqs)
(cons (apply f (map #(if (seq %) (first %) ext) seqs))
(apply map-ext f ext (map rest seqs)))
())))
``````

Usage:

``````user> (map-ext + 0 [1 2 3] [4 5 6 7 8])
(5 7 9 7 8)

user> (map-ext + 0 [1 2 3] [4 5 6 7 8] [3 4])
(8 11 9 7 8)
``````
• I've posted a putative improvement here. – Thumbnail May 22 '15 at 2:21

Your method is concise, but inefficient (it calls `count`). A more efficient solution, which does not require the entirety of its input sequences to be stored in memory follows:

``````(defn map-pad [f pad & colls]
(lazy-seq
(let [seqs (map seq colls)]
(when (some identity seqs)
(cons (apply f (map #(or (first %) pad) seqs))
(apply map-pad f pad (map rest seqs)))))))
``````

Used like this:

``````user=> (map-pad + 0 []  [1 1] (range 1 10))
(3 3 3 4 5 6 7 8 9)
``````

Edit: Generalized `map-pad` to arbitrary arity.

• Nice, but it only accepts 2 collections. We really want the offspring of our two answers :) – Adrian Mouat Jan 27 '12 at 14:53
• Yeah, persisting laziness is a good approach, thanks – mishadoff Jan 27 '12 at 15:27
• Note that using `(map #(or (first %) pad) seqs)` means that any `nil`s and `false`s in the `seqs` will be replaced with `pad`. – Michał Marczyk Jan 28 '12 at 4:38

If you just want it to work for any number of collections, try:

``````(defn map-ext [f & colls]
(let [mx (apply max (map count colls))]
(apply map f (map #(concat % (repeat (- mx (count %)) 0)) colls))))

Clojure> (map-ext + [1 2] [1 2 3] [1 2 3 4])
(3 6 6 4)
``````

I suspect there may be better solutions though (as Trevor Caira suggests, this solution isn't lazy due to the calls to count).

How about that:

``````(defn map-ext [f x & xs]
(let [colls (cons x xs)
res   (apply map f colls)
next  (filter not-empty (map #(drop (count res) %) colls))]
(if (empty? next) res
(lazy-seq (concat res (apply map-ext f next))))))

user> (map-ext + [1 2 3]  [5 6] [7 8 9 10])
(17 16 12 10)
``````

Along the lines of @LeNsTR's solution, but simpler and faster:

``````(defn map-ext [f & colls]
(lazy-seq
(let [colls (filter seq colls)
firsts (map first colls)
rests (map rest colls)]
(when (seq colls)
(cons (apply f firsts) (apply map-ext f rests))))))

(map-ext + [1 2 3]  [5 6] [7 8 9 10])
;(17 16 12 10)
``````

I've just noticed Michał Marczyk's accepted solution, which is superior: it deals properly with asymmetric mapping functions such as `-`.

We can make Michał Marczyk's answer neater by using the convention - which many core functions follow - that you get a default or identity value by calling the function with no arguments. For examples:

``````(+) ;=> 0
(concat) ;=> ()
``````

The code becomes

``````(defn map-ext [f & seqs]
(lazy-seq
(when (some seq seqs)
(cons (apply f (map #(if (seq %) (first %) (f)) seqs))
(apply map-ext f (map rest seqs)))
)))

(map-ext + [1 2 3] [4 5 6 7 8] [3 4])
;(8 11 9 7 8)
``````

I've made the minimum changes. It could be speeded up a bit.

We may need a function that will inject such a default value into a function that lacks it:

``````(defn with-default [f default]
(fn
([] default)
([& args] (apply f args))))

((with-default + 6)) ;=> 6
((with-default + 6) 7 8) ;=> 15
``````

This could be speeded up or even turned into a macro.
{"ft_lang_label":"__label__en","ft_lang_prob":0.76318085,"math_prob":0.97021663,"size":548,"snap":"2019-13-2019-22","text_gpt3_token_len":177,"char_repetition_ratio":0.14154412,"word_repetition_ratio":0.18867925,"special_character_ratio":0.3759124,"punctuation_ratio":0.054054055,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.97516376,"pos_list":[0],"im_url_duplicate_count":[null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2019-05-25T10:07:54Z\",\"WARC-Record-ID\":\"<urn:uuid:f69c8bc0-4375-4ad9-93de-6d2fdbe02c75>\",\"Content-Length\":\"146744\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:70515e1c-5fa3-4fa8-88e2-e99671826d30>\",\"WARC-Concurrent-To\":\"<urn:uuid:38f68baf-b21b-463e-bff2-b13e83ab78fe>\",\"WARC-IP-Address\":\"151.101.1.69\",\"WARC-Target-URI\":\"https://stackoverflow.com/questions/9033678/changing-map-behaviour-in-clojure/9041816\",\"WARC-Payload-Digest\":\"sha1:FU6QQ235UXU4QCRW3FKK6BBT55AR55ZW\",\"WARC-Block-Digest\":\"sha1:IO2RIEMVSPZV5YCIXNECT4XFUS655JD2\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2019/CC-MAIN-2019-22/CC-MAIN-2019-22_segments_1558232257939.82_warc_CC-MAIN-20190525084658-20190525110658-00500.warc.gz\"}"}