How do you compute the difference between successive elements of a list of unknown size, functionally?

空扰寡人 提交于 2019-11-28 22:43:00

In Haskell you would probably just use some higher order function like zipWith. So you could do something like this:

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

Note how I handled the [] case separately--if you pass an empty list to tail you get a runtime error, and Haskellers really, really hate runtime errors. However, in my function, I'm guaranteed the ls is not empty, so using tail is safe. (For reference, tail just returns everything except the first item of the list. It's the same as cdr in Scheme.)

This just takes the list and its tail and combine all of the items using the (-) function.

Given a list [1,2,3,4], this would go something like this:

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

This is a common pattern: you can compute surprisingly many things by cleverly using standard higher-order functions. You are also not afraid of passing in a list and its own tail to a function--there is no mutation to mess you up and the compiler is often very clever about optimizing code like this.

Coincidentally, if you like list comprehensions and don't mind enabling the ParallelListComp extension, you could write zipWith (-) (tail ls) ls like this:

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

In clojure, you can use the map function:

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

You can also pattern-match consecutive elements. In OCaml:

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

And the usual tail-recursive version:

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

For another Clojure solution, try

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

Just to complement the idiomatic answers: it is possible in functional languages to process a list using a state object, just like you described. It is definitely discouraged in cases when simpler solutions exist, but possible.

The following example implements iteration by computing the new 'state' and passing it recursively to 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)))

The state is represented in in the previous (prev) value from the list, the diffs computed so far (acc) and the rest of the list left to process (coll).

nist

This is how it can be done in Haskell without any standard functions, just recursion and pattern matching:

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

OK, here are two C# versions for those who are interested:

First, the bad version, or the one the previously imperative (in other words I) might try to write as functional programming is learnt:

  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;
  }

Then a better version using the idioms expressed in other answers in this question:

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

I am not sure, but it might be true that in this version the source enumerable is iterated over twice.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!