Фильтрация коллекции
Фильтрация — это отбор тех элементов коллекции, которые соответствуют условию.
Условие записывается как функция, которая принимает один элемент и возвращает true (оставить его) или false (пропустить его). Такая функция называется предикатом.
Фильтрация строит новый массив. Исходный массив не изменяется.
У массивов уже есть метод filter(), поэтому свою вспомогательную функцию писать не нужно. Вы вызываете его на массиве и передаёте предикат: words.filter(s => s.length > 3).
Метод filter() есть только у массивов. У Map его нет, поэтому сначала превратите его в массив пар, отфильтруйте этот массив и постройте из результата новый Map.
Будьте осторожны с splice(). Он тоже удаляет элементы, но изменяет исходный массив вместо того, чтобы создать новый.
Фильтрация по предикату
Самый простой способ фильтрации — передать предикат в filter(). Метод проходит по массиву и оставляет каждый элемент, который проходит проверку.
У Map нет своего filter(). Разложите его через [...m] в массив пар [key, value], отфильтруйте эти пары и передайте результат в new Map().
Если условию нужна ещё и позиция элемента, используйте второй аргумент предиката. Это индекс.
Чтобы оставить элементы, которые не соответствуют условию, поставьте перед условием !. Отдельного filterNot() нет.
Массив типа (string | number | null)[] может хранить значения разных типов. Защитник типа — функция, у которой возвращаемый тип записан как v is string, — сообщает TypeScript, какие значения пережили фильтрацию, поэтому результат имеет тип string[]. Тот же приём убирает null и undefined — два значения, которые означают «нет значения».
Фильтрация массива
Оставьте только те слова, которые длиннее 3 букв. Условие s.length > 3 передаётся в filter() как стрелочная функция. Результат — новый массив [ 'three', 'four' ].
Вывод: программа на TypeScript и захваченный вывод
const words: string[] = ["one", "two", "three", "four"]
// filter() складывает в новый массив каждый элемент, которому предикат сказал «да».
const longerThan3 = words.filter(s => s.length > 3)
console.log(longerThan3)
Фильтрация Map
Разложите Map на пары [key, value] и оставьте те пары, ключ которых заканчивается на "1", а значение больше 10. endsWith() проверяет окончание строки. Остаётся только одна пара, поэтому новый Map печатается как Map(1) { 'key11' => 11 }.
Вывод: программа на TypeScript и захваченный вывод
const numbersMap = new Map<string, number>([
["key1", 1],
["key2", 2],
["key3", 3],
["key11", 11],
])
// У Map нет filter(): разложите его на пары, отфильтруйте их и постройте новый Map.
const filteredMap = new Map(
[...numbersMap].filter(([key, value]) => key.endsWith("1") && value > 10)
)
console.log(filteredMap)
Фильтрация с использованием индекса
Предикат может принимать второй аргумент — индекс элемента. Оставьте слово, только если его индекс не равен 0 и в нём меньше 5 букв.
Вывод: программа на TypeScript и захваченный вывод
const words: string[] = ["one", "two", "three", "four"]
// Второй аргумент предиката — это индекс, поэтому условие может использовать и его.
const filteredIdx = words.filter((s, index) => index !== 0 && s.length < 5)
console.log(filteredIdx)
Фильтрация с отрицанием условия
Отбросьте слова из 3 букв или меньше. Условие s.length <= 3 отрицается с помощью !, поэтому остаются только более длинные слова.
Вывод: программа на TypeScript и захваченный вывод
const words: string[] = ["one", "two", "three", "four"]
// Метода filterNot() нет: отрицайте условие с помощью ! внутри filter().
const filteredNot = words.filter(s => !(s.length <= 3))
console.log(filteredNot)
Фильтрация по типу
Массив содержит null, два числа и две строки. Защитник типа (v): v is string => typeof v === "string" оставляет только строки, а также сужает тип: strings имеет тип string[], поэтому для его элементов разрешён toUpperCase(). Каждая строка выводится в верхнем регистре, по одной на строку.
Вывод: программа на TypeScript и захваченный вывод
const mixed: (string | number | null)[] = [null, 1, "two", 3.5, "four"]
// Защитник типа: возвращаемый тип «v is string» говорит, что именно прошло проверку.
const isString = (v: string | number | null): v is string => typeof v === "string"
// strings имеет тип string[], поэтому toUpperCase() разрешён для его элементов.
const strings: string[] = mixed.filter(isString)
for (const s of strings) {
console.log(s.toUpperCase())
}
Удаление null и undefined
Массив содержит строки вперемешку с null и undefined. Защитник типа оставляет значения, которые не являются ни тем, ни другим, поэтому результат имеет тип string[] — нулевая часть типа исчезла, и строки можно использовать без дополнительной проверки.
Вывод: программа на TypeScript и захваченный вывод
// И null, и undefined означают «нет значения».
const nullableList: (string | null | undefined)[] = [null, "one", undefined, "two"]
// Без «v is string» результат сохранил бы в своём типе null и undefined.
const isPresent = (v: string | null | undefined): v is string =>
v !== null && v !== undefined
const values: string[] = nullableList.filter(isPresent)
console.log(values)
Разбиение
Иногда нужны обе группы: элементы, которые соответствуют условию, и те, которые не соответствуют.
Встроенного partition() нет. С этим справляется небольшая обобщённая вспомогательная функция: она проходит по массиву один раз и помещает каждый элемент либо в массив match, либо в массив rest. Функция возвращает оба массива как кортеж — массив с фиксированной длиной и типом для каждой позиции, — а вы распаковываете его с помощью const [match, rest] = ....
Разделение массива надвое
Слова длиннее 3 букв попадают в match; все остальные попадают в rest. Оба массива выводятся на двух строках.
Вывод: программа на TypeScript и захваченный вывод
// partition возвращает кортеж: подходящие элементы и все остальные.
function partition<T>(items: T[], test: (item: T) => boolean): [T[], T[]] {
const match: T[] = []
const rest: T[] = []
for (const v of items) {
if (test(v)) {
match.push(v)
} else {
rest.push(v)
}
}
return [match, rest]
}
const words: string[] = ["one", "two", "three", "four"]
const [match, rest] = partition(words, s => s.length > 3)
console.log(match)
console.log(rest)
Проверка предикатов
Иногда вам нужны не сами подходящие элементы, а только ответ «да» или «нет».
- any: проходит ли проверку хотя бы один элемент? На это отвечает
some(). - none: не проходит ли проверку ни один элемент? Поставьте
!передsome(). - all: проходит ли проверку каждый элемент? Это
every().
Оба метода останавливаются, как только ответ известен. some() останавливается на первом элементе, который прошёл проверку, а every() — на первом, который её не прошёл.
Без условия вопрос «есть ли хоть один элемент?» — это words.length > 0, а «нет ни одного элемента?» — это words.length === 0.
На пустом массиве every() возвращает true. Нет ни одного элемента, который не прошёл бы проверку, поэтому проверка ни разу не нарушена. Это называется тривиальной истиной.
any
Проверьте, заканчивается ли хотя бы одно слово на "e", а затем проверьте, есть ли в массиве хоть один элемент. Оба ответа — true.
Вывод: программа на TypeScript и захваченный вывод
const words: string[] = ["one", "two", "three", "four"]
// some() возвращает true, когда проверку проходит хотя бы один элемент.
console.log(words.some(s => s.endsWith("e")))
// «есть ли хоть один элемент?» — это просто проверка длины.
console.log(words.length > 0)
none
Проверьте, что ни одно слово не заканчивается на "a" — это true, — а затем проверьте, пуст ли массив, что даёт false.
Вывод: программа на TypeScript и захваченный вывод
const words: string[] = ["one", "two", "three", "four"]
// «none» — это противоположность «any»: ни один элемент не проходит проверку.
console.log(!words.some(s => s.endsWith("a")))
// «нет ни одного элемента?» — это просто проверка длины.
console.log(words.length === 0)
all
Проверьте, каждое ли слово заканчивается на "e". Слово "two" не заканчивается, поэтому ответ — false. Затем вызовите every() на пустом массиве, что даёт true.
Вывод: программа на TypeScript и захваченный вывод
const words: string[] = ["one", "two", "three", "four"]
const empty: string[] = []
// every() возвращает true только тогда, когда проверку проходит каждый элемент.
console.log(words.every(s => s.endsWith("e")))
// В пустом массиве нет элемента, который не прошёл бы проверку, поэтому every() даёт true.
console.log(empty.every(s => s.endsWith("e")))