1

我阅读了很多关于 Clojure 的文档(并且需要再次阅读)并阅读了关于 SO 的几个 Clojure 问题,以获得对语言的“感觉”。除了 elisp 中的一些小函数之外,我以前从未用任何 Lisp 语言编写过。我在 Clojure 中编写了我的第一个项目 Euler 解决方案,在继续深入之前,我想更好地了解有关mapreduce的一些知识。

使用 lambda,我最终得到以下结果(将 3 或 5 或两者的所有倍数相加在 1 到 1000 之间):

(reduce + (map #(if (or (= 0 (mod %1 3)) (= 0 (mod %1 5))) %1 0) (range 1 1000)))

我把它放在一行是因为我把它写在 REPL 上(它给出了正确的解决方案)。

没有 lambda,我写了这个:

(defn val [x] (if (or (= 0 (mod x 3)) (= 0 (mod x 5))) x 0))

然后我计算这样做的解决方案:

(reduce + (map val (range 1 1000)))

在这两种情况下,我的问题都涉及在执行reduce之前地图应该返回什么。完成地图后,我注意到我最终得到了一个如下所示的列表:(0 0 3 0 5 6 ...)

我尝试删除val定义末尾的“0”,但随后我收到了一个由(nil nil 3 nil 5 6 etc.)组成的列表。我不知道nil是否有问题。我发现无论如何我都会在进行左折叠时进行求和,这样零就不是真正的问题了。

但是仍然:返回的合理地图是什么?(0 0 3 0 5 6 ...) 或 (nil nil 3 nil 5 6...) 或 (3 5 6 ...) (我将如何处理最后一个?)或其他什么?

我应该“过滤掉”零点/零点吗?如果是的话怎么办?

我知道我在问一个基本问题,但 map/reduce 显然是我会经常使用的东西,所以欢迎任何帮助。

4

3 回答 3

2

听起来您已经对将映射关注点与归约分离的需求有了直观的理解。 map 生成的数据不被归约使用是完全自然的。事实上,使用零是加法的标识值这一事实使这更加优雅。

  • 映射工作是生成新数据(在本例中为 3 5 或“忽略”)
  • 减少的工作是决定要包括什么并产生最终结果。

您开始使用的是惯用的 clojure,无需再将其复杂化,因此下一个示例只是为了说明让 map 决定要包含的内容的意义:

(reduce #(if-not (zero? %1) (+ %1 %2) %2) (map val (range 10)))

在这个人为的例子中,reduce 函数忽略了零。在典型的现实世界代码中,如果这个想法就像过滤掉一些值一样简单,那么人们倾向于只使用这个filter函数

(reduce + (filter #(not (zero? %)) (map val (range 10))))

您也可以从过滤器开始并跳过地图:

(reduce + (filter #(or (zero? (rem % 3)) (zero? (rem % 5))) (range 10)))
于 2012-04-27T20:34:59.967 回答
1

口号是清晰。

  • 使用filter,不使用map。然后,您不必选择稍后必须决定不采取行动的空值。
  • 命名过滤/映射功能会有所帮助。使用let or letfn, not defn,除非您在其他地方使用过该功能。

根据这个建议采取行动使我们...

(let [divides-by-3-or-5? (fn [n] (or (zero? (mod n 3)) (zero? (mod n 5))))]
  (reduce + (filter divides-by-3-or-5? (range 1 1000))))

你可能想暂时停在这里。

这读起来不错,但divides-by-3-or-5?功能卡在喉咙里。改变因素,我们需要一个全新的功能。而那个重复的短语(zero? (mod n ...))罐子。所以 ...

我们想要一个函数,它——给定一个可能因素的列表(或其他集合)——告诉我们它们中的任何一个是否适用于给定的数字。换句话说,我们想要

  • 一组数字的函数 - 可能的因素 - ...
  • 返回一个数字的函数- 候选者 - ...
  • 这告诉我们候选人是否可以被任何可能的因素整除。

一个这样的功能是

(fn [ns] (fn [n] (some (fn [x] (zero? (mod n x))) ns)))

...我们可以这样使用

(let [divides-by-any? (fn [ns] (fn [n] (some (fn [x] (zero? (mod n x))) ns)))]
  (reduce + (filter (divides-by-any? [3 5]) (range 1 1000))))

笔记

  • 这种“改进”使程序变慢了一点。
  • divides-by-any?可能被证明足以被提升为 defn.
  • 如果操作很关键,您可以考虑去除冗余因素。例如[2 3 6]可以简化为[6].
  • 如果操作非常关键,并且因子作为常量提供,您可以考虑使用返回 using 的宏创建过滤器函数or

这是一个有点毛茸茸的故事,但它讲述了你提到的问题所引发的想法。

于 2014-04-06T11:32:26.933 回答
0

在你的情况下,我会使用keep而不是map. 它类似于,map除了它只保留非零值。

于 2012-04-28T07:55:59.440 回答