Как реализовать map с помощью reduce в Clojure
В книге Clojure для храбрых и истинных в конце раздела о уменьшить есть задача:
Если вы хотите упражнение, которое действительно взорвет ваши волосы назад, попробуйте реализовать
map
С помощьюreduce
оказывается, это было намного сложнее (по крайней мере, для меня, новичка Clojure), чем я думал. Через несколько часов я пришел к следующему выводу:--8-->
(defn map-as-reduce
[f coll]
(reduce #(cons (f %2) %1) '() (reverse coll)))
- Это лучше как это сделать? Я особенно разочарован тем фактом, что мне нужно отменить сбор входных данных, чтобы это работало правильно. Это как-то неэлегантно!
6 ответов
помните, что вы можете эффективно вставить в конец вектора:
(defn map' [f coll]
(reduce #(conj %1 (f %2)) [] coll))
пример:
(map' inc [1 2 3])
;=> [2 3 4]
одно различие между этим map'
и оригинал map
Это оригинал map
возвращает 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
стремится.
просто для удовольствия: 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))))))
в repl:
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)
реальная карта вызывает seq на свой аргумент(ы) коллекции и возвращает ленивый seq, так что, может быть, это, чтобы немного приблизить его к реальной карте?
(defn my-map
[f coll]
(lazy-seq (reduce #(conj %1 (f %2)) [] (seq coll))))
Я бы добавил Это в качестве комментария, но у меня нет репутации. :)
можно использовать 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
s, e. г., векторы, возможно, основанные на conj
вместо cons
.
Я бы не слишком беспокоиться о элегантность С такого рода упражнений.
Как уже было отмечено. Вам не нужно отменять ввод. минусы добавляют элемент в начало последовательности (даже на векторах), тогда как conj всегда будет добавлять самым естественным образом, он всегда добавляет элемент самым быстрым способом для коллекции. он будет добавлять слева направо для списка и слева направо для векторов.
Я заметил, что большинство предложенных ответов используют "уменьшить", поэтому позвольте мне предложить этот, используя в основном рекурсию:
(defn my-map [f coll]
(loop [f f coll coll acc []]
(if (empty? coll)
acc
(recur f (rest coll) (conj acc (f (first coll)))))))