Changing map behaviour in Clojure

后端 未结 6 636
青春惊慌失措
青春惊慌失措 2020-12-20 18:50

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:

相关标签:
6条回答
  • 2020-12-20 19:29

    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).

    0 讨论(0)
  • 2020-12-20 19:33

    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] [4] [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 -.

    0 讨论(0)
  • 2020-12-20 19:37

    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.

    0 讨论(0)
  • 2020-12-20 19:39

    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 1] (range 1 10))
    (3 3 3 4 5 6 7 8 9)
    

    Edit: Generalized map-pad to arbitrary arity.

    0 讨论(0)
  • 2020-12-20 19:41

    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)
    
    0 讨论(0)
  • 2020-12-20 19:44

    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] [4] [5 6] [7 8 9 10]) 
    (17 16 12 10)
    
    0 讨论(0)
提交回复
热议问题