Как реализовать 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] 

чаще всего обратный результат, а не на вход. Это распространенная идиома при рекурсивной обработке односвязных списков. Он сохраняет линейную сложность с этой структурой данных.

вы можете предложить различные реализации для других seqs, 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)))))))