Что лучше ?: (уменьшить + ...) или (применить + ...)? - PullRequest
13 голосов
/ 02 августа 2009

Должен ли я использовать

(apply + (filter prime? (range 1 20)))

или

(reduce + (filter prime? (range 1 20)))

Изменить: Это источник для простое в clojure из оптимизации инструментария.

(defn prime?  [n]  
  (cond
    (or (= n 2) (= n 3))          true
    (or (divisible? n 2) (< n 2)) false
    :else                        
     (let [sqrt-n (Math/sqrt n)]
       (loop [i 3]
           (cond
              (divisible? n i) false
              (< sqrt-n i)     true
              :else            (recur (+ i 2)))))))

Ответы [ 5 ]

18 голосов
/ 02 августа 2009

Если вы спрашиваете с точки зрения производительности, reduce немного лучше:

(time (dotimes [_ 1e6] (apply + (filter even? (range 1 20)))))
"Elapsed time: 9059.251 msecs"
nil

(time (dotimes [_ 1e6] (reduce + (filter even? (range 1 20)))))
"Elapsed time: 8420.323 msecs"
nil

Около 7% разницы в этом случае, но YMMV в зависимости от машины.

Вы не указали свой источник для функции prime?, поэтому я заменил even? в качестве предиката. Имейте в виду, что в вашей среде выполнения может доминировать prime?, и в этом случае выбор между reduce и apply имеет значение еще меньше.

Если вы спрашиваете, что является более «странным», я бы сказал, что реализация reduce предпочтительнее, поскольку то, что вы делаете, - это уменьшение / свертывание в смысле функционального программирования.

13 голосов
/ 02 августа 2009

Я думаю, что reduce будет предпочтительнее, когда он доступен, потому что apply использует список в качестве аргументов функции, но когда у вас есть большое количество - скажем, миллион - элементов в списке , вы создадите вызов функции с миллионом аргументов! Это может вызвать некоторые проблемы с некоторыми реализациями Lisp.

8 голосов
/ 12 августа 2009

(уменьшение оп ...) является нормой и (применять оп ...) исключение (особенно для str и concat).

7 голосов
/ 03 августа 2009

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

Реду собирается собирать их по одному и объединять результаты в одно целое, никогда не принимая весь список сразу.

6 голосов
/ 02 мая 2012

Я собираюсь сыграть адвоката дьявола и спорить за apply.

reduce - это взятие clojure fold (точнее foldl), левой складки, и обычно определяется начальным элементом, потому что операция складывания состоит из двух частей:

  • начальное (или "нулевое") значение

  • операция объединения двух значений

так, чтобы найти сумму набора чисел, естественный способ использования + - это (fold + 0 values) или, в ближайшем будущем, (reduce + 0 values).

это явно показывает результат для пустого списка, что важно, потому что для меня не очевидно, что + возвращает 0 в этом случае - в конце концов, + - это бинарный оператор (все, что fold необходимо или предполагается).

теперь, на практике, получается, что + у clojure определяется как больше , чем бинарный оператор. это займет много или даже нулевые значения. прохладно. но если мы используем эту «дополнительную» информацию, можно сообщить об этом читателю. (apply + values) делает это - он говорит: «Я использую + странным образом, как больше, чем бинарный оператор». и это помогает людям (по крайней мере мне) понять код.

[интересно спросить, почему apply кажется более понятным. и я думаю, что это отчасти то, что вы говорите читателю: «смотри, + был спроектирован так, чтобы принимать несколько значений (вот для чего применяется apply), и поэтому реализация языка будет включать в себя случай нулевых значений». этот неявный аргумент отсутствует при reduce, примененном к одному списку.]

альтернативно, (reduce + 0 values) тоже хорошо. но (reduce + values) вызывает во мне инстинктивную реакцию: «да, + дает ноль?».

и если вы не согласны, то, пожалуйста, перед тем, как понизить голос или опубликовать ответ, вы уверены о том, что (reduce * values) вернет пустой список?

...