Получение частей коллекции
Иногда вам не нужен весь массив. Нужна его часть: диапазон элементов, первые или последние несколько, куски одинакового размера или движущееся окно соседей.
У массивов есть один метод на всё это: slice(). words.slice(low, high) даёт элементы с индекса low до индекса high, не включая его. Опустите high, чтобы дойти до конца. Отрицательный индекс отсчитывается с конца.
arr.slice(low, high)— с индексаlowдоhigh, не включая егоarr.slice(0, n)— первыеnэлементовarr.slice(n)— всё с индексаnдо концаarr.slice(-n)— последниеnэлементов
slice() всегда создаёт новый массив и копирует в него выбранные элементы. Изменение копии не меняет оригинал, а изменение оригинала не меняет копию. В некоторых других языках та же операция даёт представление, разделяющее память с оригиналом, — здесь этого не происходит никогда.
Встроенных take, drop, chunked или windowed нет. Для этих задач в уроке используются slice() с вычисленными границами, цикл for или небольшая обобщённая вспомогательная функция.
Большинство примеров на этой странице используют одни и те же данные: слова one two three four five six.
Срез
Срез — это взятие элементов на некоторых выбранных позициях.
Для набора соседних позиций используйте slice(): words.slice(1, 4) даёт элементы на индексах 1, 2 и 3. Помните, что конечный индекс исключается: чтобы получить элементы с индекса 1 по 3, вы пишете slice(1, 4).
slice() не умеет пропускать элементы. Чтобы взять каждый второй элемент или элементы на позициях, которые вы перечисляете сами, используйте цикл for или map() по массиву индексов.
Срез диапазона
words.slice(1, 4) начинает с индекса 1 и останавливается перед индексом 4. В результате 3 элемента: [ 'two', 'three', 'four' ]. Это новый массив, поэтому его изменение не затронуло бы words.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five", "six"]
// индексы 1, 2 и 3 - конечный индекс 4 не включается
const part = words.slice(1, 4)
console.log(part)
Срез с шагом
Чтобы взять каждый второй элемент, цикл for с i += 2 посещает индексы 0, 2 и 4 и собирает слова. words.filter((_, i) => i % 2 === 0) даёт тот же результат, но он просматривает каждый элемент вместо того, чтобы перепрыгивать через них.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five", "six"]
// берём каждый второй элемент с индекса 0 до индекса 4
const part: string[] = []
for (let i = 0; i <= 4; i += 2) {
part.push(words[i])
}
console.log(part)
Срез по списку индексов
Позиции 3, 5 и 0 хранятся в массиве чисел. map() выбирает слово на каждой позиции, поэтому результат получается в порядке индексов.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five", "six"]
const indices = [3, 5, 0]
// выбираем элементы на заданных позициях в этом же порядке
const part = indices.map(i => words[i])
console.log(part)
Take и Drop
Take означает оставить некоторое число элементов с одного конца. Drop означает отбросить некоторое число элементов с одного конца и оставить остальные.
Оба — это вызовы slice(). words.slice(0, 3) берёт первые 3 элемента, а words.slice(-3) берёт последние 3. words.slice(1) отбрасывает первый элемент, а words.slice(0, -5) отбрасывает последние 5.
Есть и условные версии: takeWhile, takeLastWhile, dropWhile и dropLastWhile. Они не считают элементы. Они идут с одного конца и останавливаются на первом элементе, который не проходит условие. Встроенных версий нет, поэтому работу выполняет небольшая обобщённая вспомогательная функция: она находит индекс, на котором условие перестаёт выполняться, и возвращает slice() с этого индекса.
Взять первые или последние n элементов
words.slice(0, 3) оставляет первые 3 слова. words.slice(-3) оставляет последние 3, потому что отрицательное начало отсчитывается с конца.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five", "six"]
const first3 = words.slice(0, 3) // первые 3 элемента
const last3 = words.slice(-3) // последние 3 элемента
console.log(first3)
console.log(last3)
Отбросить первые или последние n элементов
words.slice(1) пропускает первое слово. words.slice(0, -5) пропускает последние 5 слов, поэтому остаётся только one.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three", "four", "five", "six"]
const withoutFirst = words.slice(1) // отбрасываем первый элемент
const withoutLast5 = words.slice(0, -5) // отбрасываем последние 5 элементов
console.log(withoutFirst)
console.log(withoutLast5)
Брать, пока выполняется условие
takeWhile оставляет слова с начала, пока не встретит слово, начинающееся на f. takeLastWhile оставляет слова с конца, пока не встретит three. startsWith() проверяет начало строки.
Вывод: программа на TypeScript и захваченный вывод
// takeWhile оставляет элементы с начала, пока keep() отвечает «да», затем останавливается.
function takeWhile<T>(items: T[], keep: (item: T) => boolean): T[] {
for (let i = 0; i < items.length; i++) {
if (!keep(items[i])) return items.slice(0, i)
}
return items.slice()
}
// takeLastWhile оставляет элементы с конца, пока keep() отвечает «да», затем останавливается.
function takeLastWhile<T>(items: T[], keep: (item: T) => boolean): T[] {
for (let i = items.length - 1; i >= 0; i--) {
if (!keep(items[i])) return items.slice(i + 1)
}
return items.slice()
}
const words = ["one", "two", "three", "four", "five", "six"]
const fromStart = takeWhile(words, s => !s.startsWith("f"))
const fromEnd = takeLastWhile(words, s => s !== "three")
console.log(fromStart)
console.log(fromEnd)
Отбрасывать, пока выполняется условие
dropWhile пропускает трёхбуквенные слова в начале и оставляет всё, начиная с three. dropLastWhile пропускает слова с конца, пока они содержат букву i. includes() проверяет, содержит ли строка некоторый фрагмент текста.
Вывод: программа на TypeScript и захваченный вывод
// dropWhile пропускает элементы с начала, пока skip() отвечает «да», и оставляет остальные.
function dropWhile<T>(items: T[], skip: (item: T) => boolean): T[] {
for (let i = 0; i < items.length; i++) {
if (!skip(items[i])) return items.slice(i)
}
return []
}
// dropLastWhile пропускает элементы с конца, пока skip() отвечает «да», и оставляет остальные.
function dropLastWhile<T>(items: T[], skip: (item: T) => boolean): T[] {
for (let i = items.length - 1; i >= 0; i--) {
if (!skip(items[i])) return items.slice(0, i + 1)
}
return []
}
const words = ["one", "two", "three", "four", "five", "six"]
const fromStart = dropWhile(words, s => s.length === 3)
const fromEnd = dropLastWhile(words, s => s.includes("i"))
console.log(fromStart)
console.log(fromEnd)
Разбиение на части
Разбиение на части означает разрезание массива на куски одинакового размера. Последний кусок может быть короче, если элементы не делятся нацело.
Встроенного метода разбиения нет, но вспомогательная функция коротка: шагайте по массиву с i += size и каждый раз добавляйте items.slice(i, i + size). slice() никогда не читает за концом массива, поэтому короткий последний кусок получается правильным сам собой.
Результат — массив массивов, и каждый кусок сохраняет свои собственные скобки. Node.js печатает этот результат в одну строку: [ [ 0, 1, 2 ], [ 3, 4, 5 ], [ 6, 7, 8 ], [ 9, 10, 11 ], [ 12, 13 ] ].
Чтобы сразу поработать с каждым куском, добавьте в цепочку map() по частям.
Разрезать на части по 3
Числа от 0 до 13 разрезаны на куски по 3. В последнем куске только 2 элемента: [ 12, 13 ].
Вывод: программа на TypeScript и захваченный вывод
// chunked режет массив на части по size элементов; последняя часть может быть короче.
function chunked<T>(items: T[], size: number): T[][] {
const result: T[][] = []
for (let i = 0; i < items.length; i += size) {
result.push(items.slice(i, i + size))
}
return result
}
const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
const chunks = chunked(numbers, 3)
console.log(chunks)
Сумма каждой части
Те же куски, но каждый складывается с помощью reduce(). Остаются только суммы: [ 3, 12, 21, 30, 25 ].
Вывод: программа на TypeScript и захваченный вывод
// chunked режет массив на части по size элементов; последняя часть может быть короче.
function chunked<T>(items: T[], size: number): T[][] {
const result: T[][] = []
for (let i = 0; i < items.length; i += size) {
result.push(items.slice(i, i + size))
}
return result
}
const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
// складываем каждую часть из 3 элементов
const sums = chunked(numbers, 3).map(chunk => chunk.reduce((a, b) => a + b, 0))
console.log(sums)
Окна
Окно — это группа соседних элементов. Работа с окнами сдвигает группу вдоль массива и собирает каждое положение. В отличие от кусков, окна перекрываются.
Встроенного windowed нет. Это делает цикл for с items.slice(i, i + size). Небольшая обобщённая вспомогательная функция windowed делает это переиспользуемым. С параметром step окно сдвигается сразу на несколько позиций. Если partial равно true, более короткое окно в конце сохраняется, а не отбрасывается.
Окно из 2 элементов, сдвигающееся на 1 шаг, — это то же самое, что составление пары из каждого элемента и следующего за ним. Это цикл, который останавливается за один элемент до конца.
Окна по 3
Окно содержит 3 слова и каждый раз сдвигается на 1 позицию. Цикл останавливается, когда полное окно больше не помещается, поэтому получается 3 окна. Результат — массив массивов, и Node.js печатает его на нескольких строках, по одному окну на строку.
Вывод: программа на TypeScript и захваченный вывод
// windowed возвращает каждую группу из size элементов, сдвигаясь на один шаг за раз.
function windowed<T>(items: T[], size: number): T[][] {
const result: T[][] = []
for (let i = 0; i + size <= items.length; i++) {
result.push(items.slice(i, i + size))
}
return result
}
const numbers = ["one", "two", "three", "four", "five"]
console.log(windowed(numbers, 3))
Окна с шагом и неполные окна
Окно содержит 3 числа и каждый раз сдвигается на 2 позиции. Поскольку partial равно true, последнее окно [ 9, 10 ] сохраняется, хотя оно короткое.
Вывод: программа на TypeScript и захваченный вывод
// windowed возвращает группы по size элементов, сдвигаясь каждый раз на step позиций.
// При partial = true более короткая группа в конце тоже сохраняется.
function windowed<T>(items: T[], size: number, step: number, partial: boolean): T[][] {
const result: T[][] = []
for (let i = 0; i < items.length; i += step) {
let end = i + size
if (end > items.length) {
if (!partial) break
end = items.length
}
result.push(items.slice(i, end))
}
return result
}
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(windowed(numbers, 3, 2, true))
Сумма каждого окна
Каждое окно из 3 соседей складывается с помощью reduce(). Остаются только суммы: [ 6, 9, 12 ].
Вывод: программа на TypeScript и захваченный вывод
const numbers = [1, 2, 3, 4, 5]
// складываем каждое окно из 3 соседей
const sums: number[] = []
for (let i = 0; i + 3 <= numbers.length; i++) {
const nextThree = numbers.slice(i, i + 3)
sums.push(nextThree.reduce((a, b) => a + b, 0))
}
console.log(sums)
Составить пару из каждого элемента и следующего
Первый цикл строит пару из каждого слова и его соседа. Пара — это кортеж, тип [string, string]. Второй цикл сравнивает тех же соседей и записывает, длиннее ли первое слово второго.
Вывод: программа на TypeScript и захваченный вывод
// Пара содержит два соседних элемента.
type Pair = [string, string]
const letters = ["one", "two", "three", "four", "five"]
// каждый элемент вместе со следующим за ним
const pairs: Pair[] = []
for (let i = 0; i + 1 < letters.length; i++) {
pairs.push([letters[i], letters[i + 1]])
}
console.log(pairs)
// тот же проход, но сравниваем длины соседей
const longer: boolean[] = []
for (let i = 0; i + 1 < letters.length; i++) {
longer.push(letters[i].length > letters[i + 1].length)
}
console.log(longer)