如何在Clojure中使用reduce实现map

How to implement map using reduce in Clojure

书中Clojure for the Brave and True at the end of the section covering reduce有一个挑战:

If you want an exercise that will really blow your hair back, try implementing map using reduce

事实证明,这比我想象的要难得多(至少对我这个 Clojure 初学者而言)。几个小时后,我想到了这个:

(defn map-as-reduce
  [f coll]
  (reduce #(cons (f %2) %1) '() (reverse coll)))

有更好的方法吗?令我特别沮丧的是,我必须反转输入集合才能使其正常工作。好像有点不雅观!

请记住,您可以在向量末尾有效地插入:

(defn map' [f coll]
  (reduce #(conj %1 (f %2)) [] coll))

示例:

(map' inc [1 2 3])
;=> [2 3 4]

这个 map' 和原来的 map 之间的一个区别是原来的 map returns 一个 ISeq 而不是 Seqable:

(seq? (map inc [1 2 3]))
;=> true

(seq? (map' inc [1 2 3]))
;=> false

您可以通过将 map' 的上述实现与 seq:

组合起来来解决这个问题
(defn map' [f coll]
  (seq (reduce #(conj %1 (f %2)) [] coll)))

现在最重要的区别是,虽然原来的 map 是懒惰的,但这个 map' 是急切的,因为 reduce 是急切的。

您可以使用 conj 附加到矢量而不是添加到列表中:

(defn my-map [f coll]
  (reduce (fn [result item]
              (conj result (f item)))
          [] coll))

(my-map inc [1 2 3]) => [2 3 4] 

更常见的是反转结果,而不是输入。在以递归方式处理单链表时,这是一个常见的习惯用法。它保留了这种数据结构的线性复杂性。

您可能希望为其他 seq 提供不同的实现,例如。例如,向量,可能基于 conj 而不是 cons.

我不会太担心这种运动优雅

纯属娱乐: map 实际上接受多个集合作为参数。这是一个扩展的实现:

(defn map-with-reduce
  ([f coll] (seq (reduce #(conj %1 (f %2)) [] coll)))
  ([f coll & colls]
    (let [colls (cons coll colls)]
      (map-with-reduce (partial apply f)
                       (partition (count colls) 
                                  (apply interleave colls))))))

回复:

user> (map-with-reduce inc [1 2 3])
(2 3 4)

user> (map-with-reduce + [1 2 3] [4 5] [6 7 8])
(11 14)

正如已经指出的那样。您不必反转输入。 cons 将一个项目添加到序列的开头(即使在向量上),而 conj 将始终以最自然的方式添加,它总是以最快的方式为集合添加一个项目。对于列表,它将从左到右添加,对于向量,它将从左到右添加。

我注意到大多数建议的答案都使用 'reduce' 所以请允许我建议这个主要使用递归的答案:

(defn my-map [f coll]
 (loop [f f coll coll acc []]
   (if (empty? coll)
     acc
     (recur f (rest coll) (conj acc (f (first coll)))))))

真实地图在其集合参数上调用 seq 并且 returns 是一个惰性 seq,所以也许这是为了让它更接近真实地图?

(defn my-map
  [f coll]
  (lazy-seq (reduce #(conj %1 (f %2)) [] (seq coll))))

我会把它添加为评论,但我没有名气。 :)