【问题标题】:What to return in a collection when using map使用地图时在集合中返回什么
【发布时间】:2012-05-08 13:53:04
【问题描述】:

我阅读了很多关于 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 之前地图应该返回什么。在完成 map 之后,我注意到我最终得到了一个如下所示的列表:(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,所以欢迎任何帮助。

【问题讨论】:

    标签: dictionary clojure reduce


    【解决方案1】:

    口号是清晰。

    • 使用filter,而不是map。那么你不必选择一个空 您以后必须决定不采取行动的价值。
    • 命名过滤/映射功能会有所帮助。使用let 这样做 或letfn,而不是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 ...)) jars。所以……

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

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

    这样的功能之一是

    (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]
    • 如果操作真的很关键,并且提供了因素 作为常量,您可以考虑使用 回到使用 or 的宏。

    这有点像shaggy-dog story,但它叙述了你所提到的问题所引发的想法。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 我认为filter 会更好,因为(or (= 0 (mod x 3)) (= 0 (mod x 5))) 会在失败时返回false 而不是nil
      • @dbaupp 切换“else”条件以返回nil 将启用我的解决方案或从if 切换到when
      • 但它不允许您像(filter #(or (= 0 (mod % 3)) (= 0 (mod % 5))) ...) 那样轻松地编写它。 (我想说的是keep 有它的用途,但filter 是在这里使用的合适功能:))
      【解决方案3】:

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

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

      您开始使用的是惯用的 clojure,无需再将其复杂化, 所以下一个例子只是为了说明让地图决定包含什么的意义:

      (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)))
      

      【讨论】:

      • +1...我不知道直观性:) 我只是尽可能多地阅读文档,但问题是我知道我已经忘记了我读过的一些东西: ) 回到映射产生的数据,如果我有一种情况,我既不想要零(在这种情况下很好)也不想要输出中的 nil 怎么办?在 ouptut 集合中“过滤掉”不需要的元素的常见 Clojure 方法是什么?只需使用 filter?
      • 是的,如果你发现自己说哦,我可以“只使用 X”,你很可能在正确的轨道上
      • @ArthurUlfeldt 在什么意义上减少“决定包括什么”?
      • @Thumbnail 用于进行归约的函数负责将每个数据包含到结果中。它需要决定在结果中包含什么以及它应该如何影响结果。
      猜你喜欢
      • 1970-01-01
      • 2014-01-21
      • 2011-07-22
      • 1970-01-01
      • 2013-10-07
      • 1970-01-01
      • 2018-10-29
      • 1970-01-01
      • 2011-01-12
      相关资源
      最近更新 更多