如何在功能上计算未知大小列表的连续元素之间的差异?

时间:2012-03-01 07:54:17

标签: haskell clojure functional-programming ocaml

在纯函数式编程语言中(如Haskell),或者只是以函数方式使用它(例如clojure);假设你有一个整数的list / seq / enumerable(未知大小),你想要生成一个包含连续项之间差异的新list / seq / enumerable,你会怎么做?

我之前在C#中所做的是折叠列表并将状态对象保留为记录“上一个”项目的聚合值,以便您可以从当前项目对其进行差异处理。结果列表也必须进入状态对象(这对于未知大小的列表来说是一个问题)

在功能上做这种事情的一般方法是什么?

7 个答案:

答案 0 :(得分:32)

在Haskell中你可能只使用一些更高阶函数,如zipWith。所以你可以这样做:

diff [] = []
diff ls = zipWith (-) (tail ls) ls

请注意我是如何单独处理[]案例的 - 如果将空列表传递给tail则会出现运行时错误,而Haskellers 确实 讨厌运行时错误。但是,在我的函数中,我保证ls不为空,因此使用tail是安全的。 (作为参考,tail只返回除列表第一项以外的所有内容。它与Scheme中的cdr相同。)

这只是获取列表及其尾部,并使用(-)函数组合所有项目。

给定一个列表[1,2,3,4],这将是这样的:

zipWith (-) [2,3,4] [1,2,3,4]
[2-1, 3-2, 4-3]
[1,1,1]

这是一种常见的模式:通过巧妙地使用标准的高阶函数,您可以计算出惊人的很多东西。你也不怕把一个列表和它自己的尾部传递给一个函数 - 没有任何突变让你搞砸了,编译器通常非常聪明地优化这样的代码。

巧合的是,如果您喜欢列表推导并且不介意启用ParallelListComp扩展程序,则可以像这样编写zipWith (-) (tail ls) ls

[b - a | a <- ls | b <- tail ls]

答案 1 :(得分:25)

在clojure中,您可以使用map函数:

(defn diff [coll]
  (map - coll (rest coll)))

答案 2 :(得分:13)

您还可以对连续元素进行模式匹配。在OCaml:

let rec diff = function
  | [] | [_]       -> []
  | x::(y::_ as t) -> (y-x) :: diff t

通常的尾递归版本:

let diff =
  let rec aux accu = function
  | [] | [_]       -> List.rev accu
  | x::(y::_ as t) -> aux ((y-x)::accu) t in
  aux []

答案 3 :(得分:7)

对于另一个Clojure解决方案,请尝试

(map (fn [[a b]] (- b a))
     (partition 2 1 coll))

答案 4 :(得分:5)

只是为了补充惯用的答案:在函数式语言中,使用状态对象处理列表是可能的,就像您描述的那样。在存在更简单的解决方案的情况下,绝对不鼓励这种情况。

以下示例通过计算新的“state”并递归地将其传递给self来实现迭代。

(defn diffs
  ([coll] (diffs (rest coll) (first coll) []))
  ([coll prev acc]
     (if-let [s (seq coll)]
       ; new 'state': rest of the list, head as the next 'prev' and
       ; diffs with the next difference appended at the end:
       (recur (rest s) (first s) (conj acc (- (first s) prev)))
       acc)))

状态在列表的前一个(prev)值中表示,到目前为止计算的差异(acc)和列表的其余部分留待处理(coll )。

答案 5 :(得分:4)

这是如何在没有任何标准函数的情况下在Haskell中完成的,只是递归和模式匹配:

diff :: [Int] -> [Int]
diff []     = []
diff (x:xs) = hdiff xs x


hdiff :: [Int] -> Int -> [Int]
hdiff []     p = []
hdiff (x:xs) p = (x-p):hdiff xs x

答案 6 :(得分:1)

好的,这里有两个感兴趣的C#版本:

首先,学习错误的版本,或者之前命令式的版本(换句话说,我)可能会尝试编写函数式编程:

  private static IEnumerable<int> ComputeUsingFold(IEnumerable<int> source)
  {
     var seed = new {Result = new List<int>(), Previous = 0};
     return source.Aggregate(
        seed,
        (aggr, item) =>
           {
              if (aggr.Result.Count > 0)
              {
                 aggr.Result.Add(item - aggr.Previous);   
              }
              return new { Result = aggr.Result, Previous = item };
           }).Result;
  }

然后使用在这个问题的其他答案中表达的习语的更好的版本:

  private static IEnumerable<int> ComputeUsingMap(IEnumerable<int> source)
  {
     return source.Zip(source.Skip(1), (f, s) => s - f);
  }

我不确定,但在这个版本中,源枚举可能会被迭代两次。