Что лучше?: (уменьшить +...) или (применить +...)?
Должен ли я использовать
(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)))))))
Ответы
Ответ 1
Если вы спрашиваете с точки зрения производительности, 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
имеет значение еще меньше.
Если вы спрашиваете, что больше "lispy", я бы сказал, что реализация reduce
предпочтительнее, поскольку то, что вы делаете, - это сокращение/списание в смысле функционального программирования.
Ответ 2
Я бы подумал, что reduce
будет предпочтительнее, когда он будет доступен, потому что apply
использует список как аргументы функции, но когда у вас есть большое число - скажем, миллион элементов в списке, вы построите вызов функции с миллионом аргументов! Это может вызвать некоторые проблемы с некоторыми реализациями Lisp.
Ответ 3
(уменьшить op...) является нормой и (применять op...) исключение (особенно для str и concat).
Ответ 4
Я бы ожидал, что применим для реализации ленивого списка, который может быть уродливым, и вы никогда не захотите считать, что ваш список не ленив, потому что вы внезапно обнаружите, что вы получаете халтуру с огромным использованием памяти.
Сокращение собирает их 1 по одному и свертывает результаты вместе в одно целое, не принимая сразу весь список.
Ответ 5
Я собираюсь играть адвоката дьявола и спорить о apply
.
reduce
имеет значение clojure принимает fold
(точнее foldl
), левое сложение и обычно определяется с помощью начального элемента, поскольку операция сложения имеет две части:
поэтому, чтобы найти сумму набора чисел, естественным способом использования +
является (fold + 0 values)
или, в clojure, (reduce + 0 values)
.
это явно показывает результат для пустого списка, что важно, потому что для меня не очевидно, что +
возвращает 0
в этом случае - в конце концов, +
является двоичным оператором (все это fold
требует или предполагает).
теперь, на практике, оказывается, что clojure +
определяется как более чем двоичный оператор. это потребует много или даже нулевых значений. круто. но если мы используем эту "дополнительную" информацию, она будет дружественной, чтобы сообщить об этом читателю. (apply + values)
делает это - он говорит: "Я использую + странным образом, как более чем двоичный оператор". и это помогает людям (мне, по крайней мере) понять код.
[интересно спросить, почему apply
чувствует себя более ясным. и я частично думаю, что вы говорите читателю: "Посмотрите, +
был разработан, чтобы принимать несколько значений (для чего применяется приложение), поэтому реализация языка будет включать случай нулевых значений". этот неявный аргумент отсутствует с reduce
, применяемым к одному списку.]
(reduce + 0 values)
также отлично. но (reduce + values)
вызывает у меня инстинктивную реакцию: "да, +
дает нуль?".
и если вы не согласны, то, пожалуйста, перед тем, как вы опубликуете или опубликуете ответ, уверены ли вы, что (reduce * values)
вернется для пустого списка?