xgrommx's blog

Просто о сложном.

Манипуляции с массивами

| Comments

Довольно часто нам приходится фильтровать, изменять или приводить все элементы к единому скалярному значению. Иногда это является довольно рутинной задачей и делает некоторые сложности. На помощь нам может прийти три основных понятия функционального программирования, на котором оно основано.

Допустим у нас есть массив с юзерами у которых есть имя, фамилия, возраст, пол и размер оклада заработной платы. Припустим, что нам надо получить только лишь имена.

С помощью обычного императивного подхода это будет выглядить так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var users = [
    {firstName: 'Алексей', lastName: 'Алексеев', age: 25, sex: 'male', salary: 10000},
    {firstName: 'Петр', lastName: 'Петров', age: 30, sex: 'male', salary: 18000},
    {firstName: 'Марина', lastName: 'Алексеевна', age: 26, sex: 'female', salary: 8000},
    {firstName: 'Ольга', lastName: 'Петровна', age: 45, sex: 'female', salary: 19000},
    {firstName: 'Игорь', lastName: 'Васильев', age: 22, sex: 'male', salary: 6000}
];

var names = [];

users.forEach(function(el, i, a) {
    names.push(el.firstName);
});

console.log(names); // => ["Алексей", "Петр", "Марина", "Ольга", "Игорь"] 

Чтоже выглядит довольно не плохо, но есть одно но. Почему бы сразу из текущего массива не получить новый, путем его проекции? На помощь к нам приходит метод map. Он служит своего рода как функция-проектор, которая в зависимости от колбека создает новую проекцию текущего массива. Давайте перепишем наш код:

1
2
3
4
5
6
7
8
9
10
11
12
13
var users = [
    {firstName: 'Алексей', lastName: 'Алексеев', age: 25, sex: 'male', salary: 10000},
    {firstName: 'Петр', lastName: 'Петров', age: 30, sex: 'male', salary: 18000},
    {firstName: 'Марина', lastName: 'Алексеевна', age: 26, sex: 'female', salary: 8000},
    {firstName: 'Ольга', lastName: 'Петровна', age: 45, sex: 'female', salary: 19000},
    {firstName: 'Игорь', lastName: 'Васильев', age: 22, sex: 'male', salary: 6000}
];

var names = users.map(function(el) {
    return el.firstName;
});

console.log(names); // => ["Алексей", "Петр", "Марина", "Ольга", "Игорь"]

Как можно заметить, что функция-проектор, которая используется как колбек у map применится к каждому из элементов этого массива и вернет новый массив с изменнеными значениями. Это можно увидеть на этой диаграмме:

Array map

Довольно не плохо, скажете вы, но что делать если мы хотим получить только мужчин или же только тех у кого присутствует буква “A”. Как и раньше давайте снова напишем в старом и стиле и проверим как это будет работать.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var users = [
    {firstName: 'Алексей', lastName: 'Алексеев', age: 25, sex: 'male', salary: 10000},
    {firstName: 'Петр', lastName: 'Петров', age: 30, sex: 'male', salary: 18000},
    {firstName: 'Марина', lastName: 'Алексеевна', age: 26, sex: 'female', salary: 8000},
    {firstName: 'Ольга', lastName: 'Петровна', age: 45, sex: 'female', salary: 19000},
    {firstName: 'Игорь', lastName: 'Васильев', age: 22, sex: 'male', salary: 6000}
];

var onlyMale = [];

users.forEach(function(el, i, a) {
    if(el.sex === 'male') {
        onlyMale.push(el);
    }
});

console.log(onlyMale); // => [Object, Object, Object]

Чтоже наша сильная половина человечества отфильтрована. Но как и с случаем с map мы бы хотели просто получить новый массив, не проводя никаких итераций. И тут нам на помощь приходит функция filter у которой есть специализированный колбек - предикат. Предикат - это функция, коротая возвращет всего два значения (true или false) тоесть булевые значения.

1
2
3
4
5
6
7
8
9
10
11
12
13
var users = [
    {firstName: 'Алексей', lastName: 'Алексеев', age: 25, sex: 'male', salary: 10000},
    {firstName: 'Петр', lastName: 'Петров', age: 30, sex: 'male', salary: 18000},
    {firstName: 'Марина', lastName: 'Алексеевна', age: 26, sex: 'female', salary: 8000},
    {firstName: 'Ольга', lastName: 'Петровна', age: 45, sex: 'female', salary: 19000},
    {firstName: 'Игорь', lastName: 'Васильев', age: 22, sex: 'male', salary: 6000}
];

var onlyMale = users.filter(function(el) {
    return el.sex === 'male';
});

console.log(onlyMale);

Сдесь также функция колбек(предикат) применится к каждому элементу массива, но в отличии от map, колличество возвращенных элементов не будет равно колличеству елементов исходного массива. Это происходит потому, что наша функция отбросила все элементы, что не подходили условию. Это также можно увидеть на этой диаграмме.

Array filter

Но вдруг возникла задача, что нам нужно привести все элементы массива к одному скалярному значению, например посчитать зарплату всех юзеров. Для этого нам прийдется пройтись по всем элементам массива и агрегировать зарплату всех в одну переменную применяя операцию сложения.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var users = [
    {firstName: 'Алексей', lastName: 'Алексеев', age: 25, sex: 'male', salary: 10000},
    {firstName: 'Петр', lastName: 'Петров', age: 30, sex: 'male', salary: 18000},
    {firstName: 'Марина', lastName: 'Алексеевна', age: 26, sex: 'female', salary: 8000},
    {firstName: 'Ольга', lastName: 'Петровна', age: 45, sex: 'female', salary: 19000},
    {firstName: 'Игорь', lastName: 'Васильев', age: 22, sex: 'male', salary: 6000}
];

var totalSalary = 0;

users.forEach(function(el, i, a) {
    totalSalary += parseInt(el.salary);
});

console.log("Total salary = %d", totalSalary); // => Total salary = 61000

Довольно часто приходится менять оператор для агрегации, например использовать умножение или еще какие-то либо другие операторы. Для того чтобы это было довольно просто в арсенале javascript имеется еще один метод - reduce. У него также есть метод колбек, но он принимает в себя два параметра - первый это предидущее значение, а второй текущее. Первый параметр также выступает в роли аккумулятора, который накапливает значения. Теперь наш пример будет выглядить так:

1
2
3
4
5
6
7
8
9
10
11
12
var users = [
    {firstName: 'Алексей', lastName: 'Алексеев', age: 25, sex: 'male', salary: 10000},
    {firstName: 'Петр', lastName: 'Петров', age: 30, sex: 'male', salary: 18000},
    {firstName: 'Марина', lastName: 'Алексеевна', age: 26, sex: 'female', salary: 8000},
    {firstName: 'Ольга', lastName: 'Петровна', age: 45, sex: 'female', salary: 19000},
    {firstName: 'Игорь', lastName: 'Васильев', age: 22, sex: 'male', salary: 6000}
];

var totalSalary = users.map(function(el) { return el.salary; })
                       .reduce(function(a, b) { return a + b; });

console.log("Total salary = %d", totalSalary);

Как вы заметили, мы также использовали функцию map для того чтобы превратить наш массив в линейный, состоящий только лишь из зарплатной состовляющей. Теперь довольно просто поменять аккумулирующую функцию, на любой оператор, который нам подходит. Также мы можем это увидеть на диаграмме:

Array reduce

Scan и reduce являются синонимами.

Но не все так радужно по поводу этого. Так как выше приведенные функции являются стандартами ecmascript 5, то есть ряд браузеров которые ще это не поддерживают. Особенно это касается Internet Explorer 7,8. Для того чтобы исправить эту проблему существуют своего рода ‘заплатки’ - полифилы. Если мы хотим использовать эти функции везде, то стоит подключить этот скрипт es-5 shim.

Из этих функций, также есть обертки типа sum, every, some - но они являются лишь производными от основных, так сказать упрощают написание кода в часто используемых моментах.

Вы также можете проверить ваш браузер на поддержку тех или иных фич, перейдя по ссылке А на сегодня все, до новых встреч :)

PS: Диаграммы, приведенные в данной статье честно заимствованы с wiki

Comments