2012-03-01 3 views
28

En un lenguaje de programación que es puramente funcional (como Haskell) o donde solo lo está usando de una manera funcional (por ejemplo, clojure); supongamos que tiene una lista/seq/enumerable (de tamaño desconocido) de enteros y desea generar una nueva lista/seq/enumerable que contenga las diferencias entre los elementos sucesivos, ¿cómo lo haría?¿Cómo se calcula la diferencia entre los elementos sucesivos de una lista de tamaño desconocido, funcionalmente?

Lo que hice anteriormente en C# fue doblar sobre la lista y mantener un objeto de estado como el valor agregado que grabó el elemento 'anterior' para que pueda hacer una diferencia sobre el elemento actual. La lista de resultados también tuvo que entrar en el objeto de estado (que es un problema para una lista de tamaño desconocido)

¿Cuál es el enfoque general para hacer este tipo de cosas funcionalmente?

+0

¿Qué tipo de lista es? ¿Es una lista vinculada donde puede recorrer la lista hasta llegar a NIL? Si es así, ¿por qué no iterar desde el segundo elemento y en cada iteración guardar el valor del elemento anterior y calcular la diferencia e insertarla en la nueva lista? – Odinn

+0

@Odinn Care para poner eso en el código? –

+1

Me gustaría ver el enfoque de C# para comparar, especialmente si está utilizando LINQ para hacer esto. – eternalmatt

Respuesta

30

En Haskell probablemente solo usarías alguna función de orden superior como zipWith. Por lo que podría hacer algo como esto:

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

Nota cómo he manejado el caso [] separado - si se pasa una lista vacía a tail obtiene un error de tiempo de ejecución, y Haskellers realmente, realmente tiempo de ejecución odio errores Sin embargo, en mi función, tengo garantizado que el ls no está vacío, por lo que usar tail es seguro. (Como referencia, tail sólo devuelve todo excepto el primer elemento de la lista. Es lo mismo que en el Esquema cdr.)

Esto sólo toma la lista y su cola y se combinan todos los elementos utilizando la función (-).

Dada una lista [1,2,3,4], esto sería algo como esto:

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

Este es un patrón común: se puede calcular sorprendentemente muchas cosas utilizando hábilmente funciones de orden superior estándar. Tampoco tiene miedo de pasar una lista y su propia cola a una función: no hay mutación que lo desordene y el compilador a menudo es muy hábil para optimizar un código como este.

Casualmente, si te gusta listas por comprensión y no les importa que permite la extensión ParallelListComp, se podría escribir zipWith (-) (tail ls) ls así:

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

O en pointfree 'diff = zipWith (-) = << tail' – is7s

+11

O' diff xs = zipWith (-) (drop 1 xs) xs'. – augustss

+2

O 'diff ls = zipWith (flip (-)) ls (tail ls)' – Landei

25

En clojure, puede utilizar la función map:

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

+1 para un gran ejemplo, también vale la pena señalar que esto es flojo, por lo que las diferencias solo se calcularán a pedido. coll puede ser incluso una secuencia infinita. – mikera

+0

Tuve que ir y pegar esto en un clojure REPL para convencerme de que este es, de hecho, código clojure válido. Después de entrecerrar los ojos por un momento, estoy empezando a darle sentido. ¡Es increíble, gracias! –

4

Así es como se puede hacer en Haskell sin ninguna función estándar, solo recursividad y coincidencia de patrones:

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 
+4

Aunque la forma idiomática es usar 'zipWith'. – dave4420

5

Simplemente para complementar las respuestas idiomáticas: es posible en los lenguajes funcionales procesar una lista usando un objeto de estado, tal como lo describió. Definitivamente se desaconseja en los casos en que existen soluciones más simples, pero posibles.

El siguiente ejemplo implementa la iteración calculando el nuevo 'estado' y pasándolo recursivamente a sí mismo.

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

El estado está representado en la (prev) valor anterior de la lista, los diferenciales calculados hasta el momento (acc) y el resto de la lista de la izquierda para procesar (coll).

+0

¿Probablemente podría hacer lo mismo con el acumulador y reducir el funcionamiento? –

+2

@PieterBreed Sí, el ejemplo es en realidad un 'reducir' implementado por recursión. –

13

También puede hacer coincidir elementos consecutivos. En OCaml:

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

Y la cola recursiva habitual versión:

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

@Fabrice Su edición no está en el espíritu de ediciones en este sitio. Creo que su sugerencia es una mejora, pero ¿qué sucede si el que responde originalmente no está de acuerdo? ¿Qué pasa si él piensa que empeora su respuesta? –

+4

Estoy satisfecho con las ediciones de Fabrice :-) – Thomas

7

Por otra solución Clojure, tratan

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

OK, aquí hay dos versiones de C# para los que están interesados :

En primer lugar, la versión incorrecta, o la que anteriormente era imperativa (en otras palabras, I) podría intentar escribir e la programación funcional se aprende:

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

A continuación, una versión mejor uso de los idiomas expresaron en otras respuestas en esta pregunta:

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

No estoy seguro, pero podría ser cierto que en esta versión la fuente enumerable se repite dos veces.

Cuestiones relacionadas