如何在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))))
我会把它添加为评论,但我没有名气。 :)
书中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
usingreduce
事实证明,这比我想象的要难得多(至少对我这个 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))))
我会把它添加为评论,但我没有名气。 :)