Механизм фильтров Collection

в 12:11, , рубрики: javascript, node.js, Веб-разработка, итераторы, коллекции, функциональное программирование, метки: , , , ,

Эта статья является продолжением "Итерируем всё вместе с Collection" и здесь я расскажу о встроенном механизме Collection — фильтрах.

Итеративные методы Collection могут принимать множество различных параметров, но один из самых интересных является filter, давайте рассмотрим на примере:

// Простой map
$C([1, 2, 3, 4, 5]).map(function (el) { return el * 2; }) // [2, 4, 6, 8, 10]

// А теперь только для тех элементов, которые больше 2
$C([1, 2, 3, 4, 5]).map(function (el) { return el * 2; }, {filter: function (el) { return el > 2; }}) // [6, 8, 10]

Т.е. на любой итеративный метод, будь то map, reduce, length и т.д. можно наложить дополнительную функцию фильтр, которая будет отсеивать «лишние» элементы.

Ещё примеры:

// Количество элементов в объекте, значение которых больше 2
$C({a: 1, b: 2, c: 3, e: 4}).length(function (el) { return el > 2; }) // 2

// Количество вхождений каждого символа строки, кроме "o"
$C('foo Bar').group(fuction (el) { return el; }, {filter: function (el) { return el !== 'o'; }})

Но это ещё не всё, Collection позволяет заранее продекларировать все необходимые фильтры, а затем вызывать их по ИД и даже комбинировать. Давайте напишем 2 фильтра: первый будет находить уникальные или не уникальные элементы в исходной коллекции, а второй «склеивать» дубликаты (сразу скажу, что я не преследую здесь цель написать наиболее оптимальный алгоритм, а просто как пример).

// Декларируем наши фильтры с помощью метода addFilter

$C().addFilter('unique', function (el, key, data, i, length) {
    // Параметр this.$ содержит ссылку на пустой объект,
    // который создаётся в рамках каждого итератора для использования в мемоизации (т.е. сохранение промежуточных значений)    

    var cache = this.$.cache = this.$.cache || new Set();
    var final = this.$.final = this.$.final || new Set();

    // this.$.ready не определён, значит идёт первых проход итератора
    if (!this.$.ready) {
        if (cache.has(el)) {
            final.delete(el);
        
        } else {
            final.add(el);
            cache.add(el);
        }

        // Параметр this._ содержит ссылку объект,
        // который содержит характеристики текущей операции (тип данных, различные ограничители и т.д.)

        // Если идёт последняя итерация, то сбрасываем операцию и начинаем проход заново
        if (i === (this._.endIndex || (length() - 1))) {
            this.reset();
            this.$.ready = true;
        }

        // this.FALSE специальная константа, которая означает, 
        // что фильтр всегда должен возвращать false, даже если его инвертировали
        return this.FALSE;
    }
    
    // Идёт второй проход и у нас уже есть индексирующий объект
    return final.has(el);
});

Теперь давайте попробуем в действии

// Метод get возвращает массив элементов коллекции, которые подходят под фильтр
$C([1, 2, 2, 3]).get('unique') // [1, 3]

// Только не уникальные
$C([1, 2, 2, 3]).get('!unique') // [2, 2]

А теперь напишем фильтр, который будет «склеивать» одинаковые элементы в один

$C().addFilter('merge', function (el) {
    this.$.tmp = this.$.tmp || new Set();

    if (!this.$.tmp.has(el)) {
        this.$.tmp.add(el);

        // this.TRUE антоним this.FALSE
        return this.TRUE;
    }

    return this.FALSE;
});

И сейчас можно запустить их вместе

// Только не уникальные
$C([1, 2, 2, 3, 5, 5]).get('!unique && merge') // [2, 5]

Следует заметить, что в составных фильтрах можно также использовать «или» || и круглые скобки для группировок, на которые также можно накладывать знак инверсии !.

Допускается создавать фильтры, которые ссылаются на другие

$C().addFilter('mix', '!unique && merge');

Ну и напоследок: можно назначать «активные» фильтры, которые буду использовать по умолчанию всегда, а фильтры которые буду указаны явно, будут их просто дополнять.

$C([1, 2, 2, 3, 5, 5]).setFilter('unique').get() // [1, 3]
$C([1, 2, 2, 3, 5, 5]).setFilter('!unique').get('merge') // [2, 5]

Давайте напишем условие для пагинации, скажем загрузка 2-й страницы, где на одной странице не более 10 элементов.

// Исходной коллекцией пускай будет некоторый Map
$C(new Map(...)).get({
    filter: '!unique && merge',
    from: 10,
    count: 10
})

Надеюсь, что эта коротенькая заметка была интересной и понятной, всем удачи!

Автор: kobezzza

Источник

Поделиться

* - обязательные к заполнению поля