Como usar Map, Filter y Reduce en JavaScript

Fuente: https://code.tutsplus.com/es/tutorials/how-to-use-map-filter-reduce-in-javascript–cms-26209

La programación funcional ha estado creando gran revuelo en el mundo del desarrollo estos días. Y por una buena razón: las técnicas funcionales pueden ayudarte a escribir código más declarativo que es fácil de entender en una ojeada, refactorizar y testear.

Uno de los pilares de la programación funcional es su especial uso de listas y operaciones de listas. Y esas cosas son exactamente lo que suenan: arrays de cosas y lo que haces con ellas. Pero el enfoque funcional las trata un poco diferente de lo que podrías esperar.

Este artículo le da una mirada de cerda a los llamados tres grandes operadores de las listas: mapfilter y reduce Comprender estas tres funciones constituye un paso importante para poder escribir código funcional limpio y te abre las puertas a las potentes técnicas de la programación funcional y reactiva.

Esto también significa que nunca volverás a escribir un for de nuevo.

¿Quieres saber más? Vamos a ello

Un map de lista a lista

A menudo, nos encontramos en la necesidad de tomar un array y modificar cada elemento de la misma manera. Los ejemplos tipicos son elevar al cuadrado cada elemento de un array de números, recibir el nombre d euna lista de usuarios o correr una expresión regular a un array de strings.

map es un método construido para hacer exactamente eso. Está definido en Array.prototype, entonces puedes llamarlo en cualquier array y acepta un callback como primer argumento.

Cuando llamas un map en un array, este ejecuta el callback en cada elemento dentro de él, retornando un nuevo array con los valores que el callback retorna.

Bajo el capó, map pasa tres argumentos a tu callback:

  1. El item actual en el array
  2. El indice en el array del elemento actual
  3. El array entero al que estas mapeando.

Veamos algo de código

map en la práctica

Supongamos que tenemos una app que mantiene un array de tus tareas del día. Cada tarea es un objeto de nombre task, cada uno con las propiedades name y duration.

// Durations are in minutes

var tasks = [

  {

    'name'     : 'Write for Envato Tuts+',

    'duration' : 120

  },

  {

    'name'     : 'Work out',

    'duration' : 60

  },

  {

    'name'     : 'Procrastinate on Duolingo',

    'duration' : 240

  }

];

Digamos que queremos crear un nuevo array con solo el nombre de cada tarea, entonces podemos darle una mirada a todo lo que hemos hecho en el día. Usando un ciclo for, escribiríamos algo como esto:

var task_names = [];

for (var i = 0, max = tasks.length; i < max; i += 1) {

    task_names.push(tasks[i].name);

}

JavaScript también ofrece un ciclo forEach Este funciona como un for, pero maneja por nosotros todo el desorden de revisar en cada iteracion el indice contra la longitud del array.

var task_names = [];

tasks.forEach(function (task) {

    task_names.push(task.name);
    
});

Usando map podemos escribir

var task_names = tasks.map(function (task, index, array) {

    return task.name; 

});

He incluido los parámetros index y array para recordarle que estan ahi si los necesita. Desde que no los usamos aqui, podrias omitirlos y el código seguiría funcionando bien.

Hay algunas diferencias importantes entre los dos enfoques.

  1. Usando map, no tienes que preocuparte el estado del ciclo for por ti mismo.
  2. Puedes operar en el elemento directamente, en lugar de obtenerlo con su indice en el array.
  3. No necesitas crear un nuevo array y hacer push en él. map retorna el producto final de una vez, por lo que podemos simplemente asignar el valor de retorno a una nueva variable.
  4. Recuerda incluir un return en tu callback. Si no lo haces, obtendrás un nuevo array lleno de undefined.

Resulta que todas las funciones que veremos hoy comparten las mismas características.

El hecho de que no tengamos que manejar manualmente el estado del ciclo hace nuestro código simple y más mantenible. El hecho de que podamos operar directamente en el elemento en lugar de usar su indice en el array, hace las cosas más legibles.

El uso de un forEach soluciona ambos problemas por nosotros Pero map sigue teniendo al menos dos ventajas distinguibles:

  1. forEach retorna undefined, por lo que no se puede encadenar con otros métodos de un array. map retorna un array, por lo que puedes encadenarlo con otros métodos de los arrays.
  2. map retorna un array con el producto final, en lugar de requerirnos ir transformando un array dentro del ciclo.

Mantener el numero de lugares donde modificas un estado al minimo es un importante principio en la programación funcional. Se hace para tener un código más seguro y más inteligible.

Ahora es buen momento para apuntar a que si estas en Node, pruebas estos ejemplos en la consola de Firefox o usas Babel o Traceur, puedes hacer este código más conciso con funciones de flecha.

var task_names = tasks.map((task) => task.name );

Arrow function permiten omitir la palabra return cuando el código es de una sola línea.

No se puede ser más legible que eso.

Trucos

El callback que pasas al map debe tener una setencia return específica o el map devolverá un array lleno de undefined. No es dificil recordar incluir un valor return, pero no es dificil olvidarlo.

Si se olvida, el map no se quejará. En lugar de eso, tranquilamente devolverá un array lleno de nada. Los errores silenciosos como ese pueden ser sorprendentemente difíciles de depurar.

Afortunadamente, este es el únicasorpresa con map. Pero es una falla bastante común que estoy obligado a enfatizar: ¡Asegúrese siempre de que su devolución de llamada contenga la declaración return!

Implementación

Implementaciones de lectura es una parte importante de la comprensión. Así que, vamos a escribir nuestro propio map ligero para entender mejor lo que está pasando bajo el capó. Si desea ver una implementación de calidad de producción, consulte el Polyfill de Mozilla en MDN.

var map = function (array, callback) {

    var new_array = [];

    array.forEach(function (element, index, array) {
       new_array.push(callback(element)); 
    });

    return new_array;

};

var task_names = map(tasks, function (task) {

    return task.name;

});

Este código acepta un array y la función de devolución de llamada como argumentos. A continuación, crea una nueva array; Ejecuta la devolución de llamada en cada elemento de la matriz en la que pasamos; Empuja los resultados en el nuevo array; Y devuelve el nuevo array. Si ejecuta esto en su consola, obtendrá el mismo resultado que antes. ¡Sólo asegúrese de inicializar las tasks antes de probarlo!

Mientras estamos utilizando un bucle for detras de escena, envolviéndolo en una función oculta los detalles y nos permite trabajar con la abstracción en su lugar.

Eso hace que nuestro código sea más declarativo—dice qué hacer, no cómo hacerlo. Apreciará cuánto más legible, fácil de mantener, y, erm,  depurable esto puede hacer su código.Advertisement

Filtrar el ruido

El siguiente de nuestras operaciones del arsenal es filter. Hace exactamente lo que suena: toma una array, y filtra los elementos no deseados.

Al igual que mapfilter se define en Array.prototype. Está disponible en cualquier array y le pasa una devolución de llamada como su primer argumento. Filter ejecuta esa devolución de llamada en cada elemento de la array y devuelve un nuevo array que contiene sólo los elementos para los que la devolución de llamada devuelve true.

También como mapfilter pasa su devolución de llamada tres argumentos:

  1. El elemento actual
  2. El índice actual
  3. La array a la que llamó a filter

filter en la práctica

Vamos a revisar nuestro ejemplo de task. En lugar de sacar los nombres de cada tarea, digamos que quiero obtener una lista de sólo las tareas que me llevó dos horas o más para hacer.

Usando forEach, escribiríamos:

var difficult_tasks = [];

tasks.forEach(function (task) {
    if (task.duration >= 120) {
        difficult_tasks.push(task);
    }
});

Con filter:

var difficult_tasks = tasks.filter(function (task) {
    return task.duration >= 120;
});

// Using ES6
var difficult_tasks = tasks.filter((task) => task.duration >= 120 );

Aquí, he avanzado y deje de lado los argumentos array e index a nuestra devolución de llamada, ya que no los usamos.

Al igual que mapfilter nos permite:

  • Evitar la mutación de un array dentro de un forEach o el bucle for
  • Asignar su resultado directamente a una nueva variable, en lugar de empujar en un array que hemos definido en otra parte

Sorpresas

La devolución de llamada que pasas a map debe incluir una declaración de devolución si desea que funcione correctamente. Con filter, también tiene que incluir una declaración de devolución, y debe asegurarse de que devuelve un valor booleano.

Si olvida su declaración de devolución, su devolución de llamada volverá undefined, cuyo filter coaccionará de manera inútil a false. En lugar de lanzar un error, devolverá silenciosamente un array vacía.

Si vas a la otra ruta y devuelve algo que no es explícitamente true o false, entonces el filter tratará de averiguar lo que quieres decir con la aplicación de las reglas de coerción de JavaScript. Más a menudo que no, esto es un error. Y, al igual que el olvido de su declaración de devolución, será un silencio.

Asegúrese siempre de que sus devoluciones de llamada incluyan una declaración de devolución explícita. Y asegúrese siempre de que sus devoluciones de llamada en filter devuelvan true o false. Su cordura se lo agradecerá.Advertisement

Implementación

Una vez más, la mejor manera de entender un pedazo de código es … bueno, escribirlo. Vamos a hacer rodar nuestro propio filter ligero. La buena gente de Mozilla tiene un polyfill de fuerza industrial para que leas, también.

var filter = function (array, callback) {

    var filtered_array = [];

    array.forEach(function (element, index, array) {
        if (callback(element, index, array)) {
            filtered_array.push(element);    
        }
    });

    return filtered_array;

};

Reducción de Arrays

map crea una nueva array mediante la transformación de cada elemento en una array, de forma individual. filter crea una nueva matriz eliminando los elementos que no pertenecen. reduce, por otro lado, toma todos los elementos en un array, y los reduce en un solo valor.

Al igual que el map y filterreduce se define en Array.prototype y está disponible en cualquier matriz y pasa una devolución de llamada como su primer argumento. Pero también toma un segundo argumento opcional: el valor para comenzar a combinar todos los elementos en una array.

reduce pasa la devolución de llamada cuatro argumentos:

  1. El valor actual
  2. El valor anterior
  3. El índice actual
  4. La array de la que llamas a reduce

Observe que la devolución de llamada obtiene un valor anterior en cada iteración. En la primera iteración, no hay valor anterior. Esta es la razón por la que tiene la opción de pasar a reduce un valor inicial: Actúa como el «valor anterior» para la primera iteración, cuando de otro modo no sería uno.

Por último, tenga en cuenta que reduce devuelve un solo valor, no una array que contiene un único elemento. Esto es más importante de lo que podría parecer, y volveré a él en los ejemplos.

reduce en la práctica

Dado que reduce es la función que la gente encuentra más extraterrestre al principio, comenzaremos caminando paso a paso a través de algo sencillo.

Digamos que queremos encontrar la suma de una lista de números. Usando un bucle, que asi:

var numbers = [1, 2, 3, 4, 5],
    total = 0;
    
numbers.forEach(function (number) {
    total += number;
});

Si bien esto no es un mal caso de uso para forEachreduce aún tiene la ventaja de permitirnos evitar la mutación. Con reduce, escribiríamos:

var total = [1, 2, 3, 4, 5].reduce(function (previous, current) {
    return previous + current;
}, 0);

Primero, llamamos reduce en nuestra lista de númbers. Pasamos una devolución de llamada, que acepta el valor anterior y el valor actual como argumentos, y devuelve el resultado de agregarlos juntos. Puesto que pasamos 0 como segundo argumento para reduce, usaremos eso como el valor de previous en la primera iteración.

Si lo tomamos paso a paso, se ve así:

IteraciónAnteriorActualTotal
1011
2123
3336
46410
510515

Si no eres un fanático de las tablas, ejecuta este fragmento en la consola:

var total = [1, 2, 3, 4, 5].reduce(function (previous, current, index) {
    var val = previous + current;
    console.log("The previous value is " + previous + 
                "; the current value is " + current +
                ", and the current iteration is " + (index + 1));
    return val;
}, 0);

console.log("The loop is done, and the final value is " + total + ".");

Para recapitular: reduce las iteraciones sobre todos los elementos de una array, combinándolos como se especifique en la devolución de llamada. En cada iteración, su devolución de llamada tiene acceso al valor anterior, que es el valor total-hasta-ahora, o acumulado; El valor actual; El índice actual; Y el array entero, si usted los necesita.

Volvamos a nuestro ejemplo de tareas. Hemos obtenido una lista de nombres de tareas de map, y una lista filtrada de tareas que tomó mucho tiempo con … bueno, filter.

¿Y si queríamos saber la cantidad total de tiempo que pasamos trabajando hoy?

Usando un bucle forEach, escribirías:

var total_time = 0;
    
tasks.forEach(function (task) {
    // The plus sign just coerces 
    // task.duration from a String to a Number
    total_time += (+task.duration);
});

Con reduce, se convierte en:

var total_time = tasks.reduce(function (previous, current) {
    return previous + current;
}, 0);

// Using arrow functions
var total_time = tasks.reduce((previous, current) previous + current );

Fácil.

Eso es casi todo lo que hay. Casi, porque JavaScript nos proporciona un método más poco conocido, llamado reduceRight. En los ejemplos anteriores, reduce iniciado en el primer elemento de la array, iterando de izquierda a derecha:

var array_of_arrays = [[1, 2], [3, 4], [5, 6]];
var concatenated = array_of_arrays.reduce( function (previous, current) {
        return previous.concat(current);
});

console.log(concatenated); // [1, 2, 3, 4, 5, 6];

reduceRight hace lo mismo, pero en la dirección opuesta:

var array_of_arrays = [[1, 2], [3, 4], [5, 6]];
var concatenated = array_of_arrays.reduceRight( function (previous, current) {
        return previous.concat(current);
});

console.log(concatenated); // [5, 6, 3, 4, 1, 2];

Yo uso reduce todos los días, pero nunca he necesitado reduceRight. Creo que probablemente tampoco. Pero en el caso de que alguna vez lo hagas, ahora sabes que está ahí.

Sorpresas

Las tres grandes sorpresas con reduce son:

  1. Olvidar return
  2. Olvidar un valor inicial
  3. Esperar una matriz cuando reduce devuelve un valor único

Afortunadamente, los dos primeros son fáciles de evitar. Decidir cuál debe ser su valor inicial depende de lo que está haciendo, pero conseguirá aprender como funciona rápidamente.

El último podría parecer un poco extraño. Si reduce sólo alguna vez devuelve un valor único, ¿por qué esperaría un array?

Hay algunas buenas razones para eso. Primero, reduce siempre devuelve un solo valor, no siempre un solo número. Si reduce una array de arrays, por ejemplo, devolverá un array único. Si está en el hábito o la reducción de arrays, sería justo esperar que un array que contiene un solo elemento no sería un caso especial.

En segundo lugar, si reduce devuelve una matriz con un solo valor, sería natural jugar agradable con map y filter, y otras funciones en los arrays que es probable que esté usando con él.

Implementación

Es hora de nuestra última mirada bajo el capó. Como de costumbre, Mozilla tiene un polyfill a prueba de balas para reducir si desea comprobarlo.

var reduce = function (array, callback, initial) {
    var accumulator = initial || 0;
    
    array.forEach(function (element) {
       accumulator = callback(accumulator, array[i]);
    });
    
    return accumulator;
};

Dos cosas a tener en cuenta, aquí:

  1. Esta vez, usé el nombre accumulator en vez de previous. Esto es lo que normalmente verá en otros lugares.
  2. Asigno al accumulator un valor inicial, si un usuario proporciona uno, y por defecto a 0, si no. Así es como el real reduce se comporta, también.

Ponerlo todo junto: Map, Filter, Reduce, y Encadenamiento

En este punto, es posible que no esté tan impresionado.

Justamente: mapfilter, y reduce, por su cuenta, no son muy interesantes.

Después de todo, su verdadero poder radica en su capacidad de encadenamiento.

Digamos que quiero hacer lo siguiente:

  1. Recoger dos días de tareas.
  2. Convertir las duraciones de las tareas en horas, en lugar de minutos.
  3. Filtrar todo lo que tomó dos horas o más.
  4. Sumar todo.
  5. Multiplique el resultado por una tarifa por hora para facturación.
  6. Produce una cantidad formateada en dólares.

En primer lugar, vamos a definir nuestras tareas para el lunes y el martes:

var monday = [
        {
            'name'     : 'Write a tutorial',
            'duration' : 180
        },
        {
            'name'     : 'Some web development',
            'duration' : 120
        }
    ];

var tuesday = [
        {
            'name'     : 'Keep writing that tutorial',
            'duration' : 240
        },
        {
            'name'     : 'Some more web development',
            'duration' : 180
        },
        {
            'name'     : 'A whole lot of nothing',
            'duration'  : 240
        }
    ];
    
var tasks = [monday, tuesday];

Y ahora, nuestra transformación para que se vea bien:

    var result = tasks.reduce(function (accumulator, current) {
                        return accumulator.concat(current);
                    }).map(function (task) {
                        return (task.duration / 60);
                    }).filter(function (duration) {
                        return duration >= 2;
                    }).map(function (duration) {
                        return duration * 25;
                    }).reduce(function (accumulator, current) {
                        return [(+accumulator) + (+current)];
                    }).map(function (dollar_amount) {
                        return '$' + dollar_amount.toFixed(2);
                    }).reduce(function (formatted_dollar_amount) {
                        return formatted_dollar_amount;
                    });

O, más concisamente:

                  // Concatenate our 2D array into a single list
var result = tasks.reduce((acc, current) => acc.concat(current))
                  // Extract the task duration, and convert minutes to hours
                  .map((task) => task.duration / 60)
                  // Filter out any task that took less than two hours
                  .filter((duration) => duration >= 2)
                  // Multiply each tasks' duration by our hourly rate
                  .map((duration) => duration * 25)
                  // Combine the sums into a single dollar amount
                  .reduce((acc, current) => [(+acc) + (+current)])
                  // Convert to a "pretty-printed" dollar amount
                  .map((amount) => '$' + amount.toFixed(2))
                  // Pull out the only element of the array we got from map
                  .reduce((formatted_amount) =>formatted_amount); 

Si has llegado tan lejos, esto debería ser bastante sencillo. Sin embargo, hay dos rarezas que explicar.

Primero, en la línea 10, tengo que escribir:

// Remainder omitted
reduce(function (accumulator, current) {
    return [(+accumulator) + (+current_];
})

Dos cosas para explicar aquí:

  1. Los signos + frente al accumulator y current coaccionan sus valores a números. Si no lo hace, el valor devuelto será la cadena bastante inútil, "12510075100".
  2. Si no envuelve esa suma entre paréntesis, reduce devolvera un solo valor, no un array. ¡Eso terminaría lanzando un TypeError, porque puede utilizar solamente el map en un array!

El segundo bit que podría ponerte incómodo es el último reduce, a saber:

// Remainder omitted
map(function (dollar_amount) {
    return '$' + dollar_amount.toFixed(2);
}).reduce(function (formatted_dollar_amount) {
    return formatted_dollar_amount;
});

Esta llamada al map devuelve un array que contiene un solo valor. Aquí, llamamos reduce para obtener ese valor.

La otra manera de hacerlo sería quitar la llamada a reduce e indexar en el array que map devuelve:

var result = tasks.reduce(function (accumulator, current) {
                    return accumulator.concat(current);
                }).map(function (task) {
                    return (task.duration / 60);
                }).filter(function (duration) {
                    return duration >= 2;
                }).map(function (duration) {
                    return duration * 25;
                }).reduce(function (accumulator, current) {
                    return [(+accumulator) + (+current)];
                }).map(function (dollar_amount) {
                    return '$' + dollar_amount.toFixed(2);
                })[0];

Eso es perfectamente correcto. Si está más cómodo usando un índice de array, siga adelante.

Pero le recomiendo que no lo haga. Una de las formas más poderosas de utilizar estas funciones es en el ámbito de la programación reactiva, en la que no podrá utilizar índices de array. Patear ese hábito ahora hará que aprender técnicas reactivas mucho más fácil mas adelante.

Finalmente, vamos a ver cómo nuestro amigo el bucle forEach lo haría:

var concatenated = monday.concat(tuesday),
    fees = [],
    formatted_sum,
    hourly_rate = 25,
    total_fee = 0;

concatenated.forEach(function (task) {
    var duration = task.duration / 60;
    
    if (duration >= 2) {
        fees.push(duration * hourly_rate);
    }
});

fees.forEach(function (fee) {
    total_fee += fee
});


var formatted_sum = '$' + total_fee.toFixed(2);

Tolerable, pero desordenado.

Conclusión y próximos pasos

En este tutorial, has aprendido cómo trabajan mapfilter y reduce; Cómo usarlos; Y aproximadamente cómo se implementan. Usted ha visto que todos ellos le permiten evitar el estado mutante, que utilizando for y forEach cada lazos requiere, y ahora debe tener una buena idea de cómo encadenar todos juntos.

Mover archivos mdf y ldf sql server

Al hacer el movimiento entre unidades de disco de los archivos, apareció un error de acceso a las unidades, eso es porque hay que asignar permisos al usuario de servicio MSSQLSERVER a los archivos en la unidad nueva.

El usuario se debe buscar como NT SERVICE\MSSQLSERVER y con ello ya se pueden asignar los permisos.

Check TestDB file permission for mdf and ldf