如何用foldl实现zip(用急切的语言)

时间:2015-01-24 22:45:45

标签: haskell clojure functional-programming fold

我最近认识的一位Clojure程序员说,根据Clojure reduce(Haskell' s foldl')可以实现大量的序列函数。但令人遗憾的是,只有(map list xs ys)无法实现zip(Haskell' reduce)。

现在,我已经了解了折叠的普遍性,所以我很确定这是不对的:zip可以foldr,{I}如果foldl无法实现,那就会感到惊讶。出于讨论的目的,我们忽略了foldlfoldr相比的渴望问题:想象我们拥有无限的记忆,只能使用有限的序列。

因此,我将Haskell代码转换为implement zip with foldr,并将其转换为Clojure,尽力调整foldr和foldl之间的差异:

(defn zip [xs ys]
  (letfn [(done [_] [])
          (step [z x]
            (fn [ys]
              (if (empty? ys)
                []
                (conj (z (rest ys)) [x (first ys)]))))]
    ((reduce step done xs) ys)))
user> (zip [1 2] '[a b c]) ;=> [[1 b] [2 a]]

实际上我们确实得到了xs和ys组合在一起的元素对,但是没有顺序:xs的第一个元素与ys的最后一个元素配对,依此类推。我可以看到问题的原因:我们生成的函数从左边开始消耗ys,但是最外面的闭包(首先调用)已经关闭了xs的最后一个元素,所以它不能将它们配对正确的顺序。

我认为修复是以某种方式从内到外构建闭包,但我无法真正看到如何做到这一点。我很乐意接受Haskell或Clojure中的解决方案。

我希望找到zip = foldl f x形式的解决方案,以便我可以说它"只是"减少。当然我可以反转其中一个列表,但最后它看起来像zip xs ys = foldl f x xs $ reverse ys,看起来并不令人满意或干净。

4 个答案:

答案 0 :(得分:3)

In Haskell

-- foldr f z xs == foldl (\r x a-> r (f x a)) id xs z

zip1 xs ys = -- foldr f (const []) xs ys
            foldl (\r x a-> r (f x a)) id xs (const []) ys
  where
    f x r []     = []
    f x r (y:ys) = (x,y) : r ys
  

前奏> zip1 [1..9] [100..120]
  [(1100),(2101),(3102),(4103),(5104),(6105),(7106),(8107),(9108)]

由于Clojure喜欢在列表末尾添加,因此另一个变体是

zip2 xs ys = foldl f (const []) xs ys
  where
    f r x [] = []
    f r x ys = let (ys0,y) = (init ys, last ys) -- use some efficient Clojure here
               in r ys0 ++ [(x,y)]
  

前奏> zip2 [1..9] [100..120]
  [(1112),(2113),(3114),(4115),(5116),(6117),(7118),(8119),(9120)]

正如您所看到的那样,列表的 end 排在前面而不是前面。

答案 1 :(得分:2)

简单实施

reverse = foldl (flip (:)) []

并将其应用于第二个列表。

答案 2 :(得分:2)

另一位Clojure专家指出,在Clojure中使用与Haskell的折叠解决方案相同的结构更容易:

(defn zip [xs ys]
  (first (reduce
          (fn [[acc rest :as state] itm]
            (if rest
              [(conj acc [itm (first rest)])
               (next rest)]
              state))
          [[] (seq ys)]
          xs)))

这只是折叠一对,第一个是构建的结果序列,第二个是ys的剩余部分; xs通过reduce一次送入一个项目。在Haskell中,这看起来像:

zip' :: [a] -> [b] -> [(a,b)]
zip' xs ys = fst $ foldl f ([], ys) xs
  where f state@(_, []) _x = state
        f (acc, (y:ys)) x = ((acc ++ [(x, y)]), ys)

除了当然acc ++ [(x, y)]在Clojure中比在Haskell中更合理,因为它有效。

答案 3 :(得分:0)

These two解决方案的格式均为(-> (reduce f init x) something),而不仅仅是(reduce f init x)。两者都携带具有累积序列和一些额外状态的容器,然后从容器中提取序列。在一种情况下,容器是封闭物,而另一种情况是载体。

如果你想"只是" (reduce f init x)然后认识到你已经拥有累积序列本身所需的所有状态 - 它的长度。

(defn zip* [xs ys] 
  (let [xs (vec xs)
        f (fn [a y]
            (if (contains? xs (count a))
              (conj a [(xs (count a)) y])
              a))]
    (reduce f [] ys)))