【问题标题】:How can operations like map, filter and reverse can be defined in terms of a reduce?如何根据 reduce 定义 map、filter 和 reverse 等操作?
【发布时间】:2014-10-28 13:35:57
【问题描述】:

在这篇博文中,"CSP and transducers in JavaScript",作者说:

首先,我们必须认识到许多数组(或其他集合)操作,如mapfilterreverse,可以用reduce 来定义。

我的问题是:如何根据 reduce 定义 map、filter 和 reverse 等操作?您能否提供 Clojure 中的示例?

【问题讨论】:

  • 这是一个奇怪的地方使用concat,在减少你选择你自己的基本情况,所以选择具有conj行为所需的那个。

标签: recursion clojure functional-programming reduce fold


【解决方案1】:

如何根据 reduce 定义 map、filter 和 reverse 等操作?

这被称为"universality of fold"fold下面是自然折叠(foldr):

显然,可以通过 fold 来描述各种归约:

sum :: [Int] -> Int           product :: [Int] -> Int
sum = fold (+) 0              product = fold (*) 1

and :: [Bool] -> Bool         or :: [Bool] -> Bool
and = fold (&&) True          or = fold (||) False

但我们也可以写出不明显的约简:

-- appending a list
(++) :: [a] -> [a] -> [a]
(++ ys) = fold (:) ys

-- reversing a list
reverse :: [a] -> [a]
reverse = fold (\x xs -> xs ++[x]) []

和一般map

map :: (a -> b) -> ([a] -> [b])
map f = fold (\x xs -> f x : xs) []

filter:

filter :: (a -> Bool) -> ([a] -> [a])
filter p = fold (\x xs -> if p x then x : xs else xs) []

甚至fold left:

foldl f v xs = fold (\x g -> (\a -> g (f a x))) id xs v

参考资料:

  1. A tutorial on the universality and expressiveness of fold,格雷厄姆·赫顿,1999 年。
  2. Writing foldl using foldr,在这里。

【讨论】:

  • 很荣幸您能回答我的问题。 Haskell 人员为接触 Clojure 社区所做的所有工作都很棒。我目前正在研究 RWH - 直到第 7 章。
  • @hawkeye 什么工作,hawkeye?我很感兴趣。是 Clojure Haskell 的斗牛犬,就像赫胥黎是达尔文的斗牛犬一样。
  • @Thumbnail “.. 对纯粹的函数代码没有决定,但尽管如此,他全心全意地公开支持函数式编程..” - 类比似乎可行 ;-)
【解决方案2】:

编辑以识别mapvfilterv


标准reverse根据reduce定义的:

(defn reverse [coll]
  (reduce conj () coll))

mapfilter 是惰性的,因此可以对无限序列进行操作。 reduce 无法做到这一点。

话虽如此,reduce 可以实现 mapvfiltervmapfilter 的急切类似物。

(defn mapv [f coll]
  (vec (reverse (reduce (fn [acc x] (cons (f x) acc)) () coll))))

(defn filterv [pred coll]
  (vec (reverse (reduce (fn [acc x] (if (pred x) (cons x acc) acc)) () coll))))

如果我们在向量中累积,我们可以不用reverses 和vecs:

(defn mapv [f coll]
  (reduce (fn [acc x] (conj acc (f x))) [] coll))

(defn filterv [pred coll]
  (reduce (fn [acc x] (if (pred x) (conj acc x) acc)) [] coll))

这几乎就是标准filterv 的实现方式。

【讨论】:

  • 为什么conj 用于反向,而cons(和外部反向)用于映射/过滤器?
  • @user2864740 在reverse 中,(defn conj [coll x] ...) 具有reduce 的正确顺序的参数; cons 需要包装在一个反转其参数的函数中:#(cons %2 %1)。在map 中,cons 内的reduce 叠加 - 因此反转 - 结果,因此需要再次反转它们。这同样适用于filter。 Clojure 习惯用法是在这种情况下使用向量 - 避免需要反转乘积。我已经附加了执行此操作的版本。请注意,这些将作为序列返回,以防有人在它们上使用 conjdisj
  • @user2864740 最后的编辑使我的一些回复无效。
【解决方案3】:

这是真的,如果我们不关心懒惰的话。在 Clojure 中,mapfilter 是惰性的,但 reduce 是急切的。 reverse 不仅不懒惰,而且标准定义使用reduce。对惰性取模,我们可以得到其他相同的结果:

user> (defn eager-map [f coll]
        (reduce (fn [acc v] (conj acc (f v)))
        []
        coll))
#'user/eager-map
user> (eager-map inc (range 10))
[1 2 3 4 5 6 7 8 9 10]

user> (defn eager-filter [f coll]
         (reduce (fn [acc v] (if (f v) (conj acc v) acc))
                 []
                 coll))
#'user/eager-filter
user> (eager-filter even? (range 10))
[0 2 4 6 8]

user> (defn eager-reverse [coll]
         (reduce conj () coll))
#'user/eager-reverse
user> (eager-reverse (range 10))
(9 8 7 6 5 4 3 2 1 0)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多