Retrieving Collection Parts
Sometimes you do not need a whole array. You need a part of it: a range of elements, the first or last few, pieces of equal size, or a moving window of neighbours.
Arrays have one method for all of this: slice(). words.slice(low, high) gives the elements from index low up to, but not including, index high. Leave out high to go to the end. A negative index counts back from the end.
arr.slice(low, high)— from indexlowup to, but not including,higharr.slice(0, n)— the firstnelementsarr.slice(n)— everything from indexnto the endarr.slice(-n)— the lastnelements
slice() always builds a new array and copies the chosen elements into it. Changing the copy does not change the original, and changing the original does not change the copy. In some other languages the same operation gives a view that shares memory with the original — here it never does.
There is no built-in take, drop, chunked or windowed. For those jobs this lesson uses slice() with computed bounds, a for loop, or a small generic helper function.
Most examples on this page use the same data: the words one two three four five six.
Slice
Slicing means taking the elements at some chosen positions.
For a run of neighbouring positions, use slice(): words.slice(1, 4) gives the elements at indexes 1, 2 and 3. Remember that the end index is excluded: to get the elements at indexes 1 to 3 you write slice(1, 4).
slice() cannot skip elements. To take every second element, or elements at positions you list yourself, use a for loop, or map() over an array of indexes.
Slice a range
words.slice(1, 4) starts at index 1 and stops before index 4. The result has 3 elements: [ 'two', 'three', 'four' ]. It is a new array, so a change to it would not touch words.
Output: TypeScript file and captured output
const words = ["one", "two", "three", "four", "five", "six"]
// indexes 1, 2 and 3 - the end index 4 is not included
const part = words.slice(1, 4)
console.log(part)
Slice with a step
To take every second element, a for loop with i += 2 visits indexes 0, 2 and 4 and collects the words. words.filter((_, i) => i % 2 === 0) gives the same result, but it looks at every element instead of jumping over them.
Output: TypeScript file and captured output
const words = ["one", "two", "three", "four", "five", "six"]
// take every second element from index 0 up to index 4
const part: string[] = []
for (let i = 0; i <= 4; i += 2) {
part.push(words[i])
}
console.log(part)
Slice by a list of indices
The positions 3, 5 and 0 are stored in an array of numbers. map() picks the word at each position, so the result comes out in the order of the indexes.
Output: TypeScript file and captured output
const words = ["one", "two", "three", "four", "five", "six"]
const indices = [3, 5, 0]
// pick the elements at the given positions, in that order
const part = indices.map(i => words[i])
console.log(part)
Take and Drop
Take means keep a number of elements from one end. Drop means throw away a number of elements from one end and keep the rest.
Both are slice() calls. words.slice(0, 3) takes the first 3 elements and words.slice(-3) takes the last 3. words.slice(1) drops the first element and words.slice(0, -5) drops the last 5.
There are also conditional versions: takeWhile, takeLastWhile, dropWhile and dropLastWhile. These do not count elements. They walk from one end and stop at the first element that fails a condition. There are no built-in versions, so a small generic helper does the job: it finds the index where the condition stops holding and returns slice() from that index.
Take the first or last n elements
words.slice(0, 3) keeps the first 3 words. words.slice(-3) keeps the last 3, because a negative start counts back from the end.
Output: TypeScript file and captured output
const words = ["one", "two", "three", "four", "five", "six"]
const first3 = words.slice(0, 3) // the first 3 elements
const last3 = words.slice(-3) // the last 3 elements
console.log(first3)
console.log(last3)
Drop the first or last n elements
words.slice(1) skips the first word. words.slice(0, -5) skips the last 5 words, so only one is left.
Output: TypeScript file and captured output
const words = ["one", "two", "three", "four", "five", "six"]
const withoutFirst = words.slice(1) // drop the first element
const withoutLast5 = words.slice(0, -5) // drop the last 5 elements
console.log(withoutFirst)
console.log(withoutLast5)
Take while a condition holds
takeWhile keeps words from the start until it meets a word that starts with f. takeLastWhile keeps words from the end until it meets three. startsWith() checks the start of a string.
Output: TypeScript file and captured output
// takeWhile keeps elements from the start while keep() says yes, then stops.
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 keeps elements from the end while keep() says yes, then stops.
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)
Drop while a condition holds
dropWhile skips the 3-letter words at the start and keeps everything from three on. dropLastWhile skips words from the end while they contain the letter i. includes() checks whether a string holds a piece of text.
Output: TypeScript file and captured output
// dropWhile skips elements from the start while skip() says yes, and keeps the rest.
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 skips elements from the end while skip() says yes, and keeps the rest.
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)
Chunked
Chunking means cutting an array into pieces of the same size. The last piece may be shorter when the elements do not divide evenly.
There is no built-in chunk method, but the helper is short: step through the array with i += size and push items.slice(i, i + size) each time. slice() never reads past the end of the array, so the short last piece comes out right on its own.
The result is an array of arrays, and every piece keeps its own brackets. Node.js prints this one on a single line: [ [ 0, 1, 2 ], [ 3, 4, 5 ], [ 6, 7, 8 ], [ 9, 10, 11 ], [ 12, 13 ] ].
To work on each piece right away, chain a map() onto the chunks.
Split into chunks of 3
The numbers 0 to 13 are cut into pieces of 3. The last piece has only 2 elements: [ 12, 13 ].
Output: TypeScript file and captured output
// chunked cuts an array into pieces of size elements; the last piece may be shorter.
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)
Sum each chunk
The same chunks, but each one is added up with reduce(). Only the sums are kept: [ 3, 12, 21, 30, 25 ].
Output: TypeScript file and captured output
// chunked cuts an array into pieces of size elements; the last piece may be shorter.
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]
// add up each chunk of 3
const sums = chunked(numbers, 3).map(chunk => chunk.reduce((a, b) => a + b, 0))
console.log(sums)
Windowed
A window is a group of neighbouring elements. Windowing slides the group along the array and collects every position. Unlike chunks, windows overlap.
There is no built-in windowed. A for loop with items.slice(i, i + size) does it. A small generic windowed helper makes it reusable. With a step, the window moves several positions at a time. With partial set to true, a shorter window at the end is kept instead of being thrown away.
A window of 2 that moves 1 step is the same as pairing each element with the next one. That is a loop that stops one element before the end.
Windows of 3
The window has 3 words and moves 1 position each time. The loop stops when a full window no longer fits, so there are 3 windows. The result is an array of arrays, and Node.js prints this one over several lines, one window per line.
Output: TypeScript file and captured output
// windowed returns every group of size elements, moving one step at a time.
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))
Windows with a step and partial windows
The window has 3 numbers and moves 2 positions each time. Because partial is true, the last window [ 9, 10 ] is kept even though it is short.
Output: TypeScript file and captured output
// windowed returns groups of size elements, moving step positions each time.
// With partial = true, a shorter group at the end is kept too.
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))
Sum each window
Every window of 3 neighbours is added up with reduce(). Only the sums are kept: [ 6, 9, 12 ].
Output: TypeScript file and captured output
const numbers = [1, 2, 3, 4, 5]
// add up every window of 3 neighbours
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)
Pair each element with the next one
The first loop builds a pair from each word and its neighbour. A pair is a tuple, the type [string, string]. The second loop compares the same neighbours and records whether the first word is longer than the second.
Output: TypeScript file and captured output
// A pair holds two neighbouring elements.
type Pair = [string, string]
const letters = ["one", "two", "three", "four", "five"]
// each element together with the one after it
const pairs: Pair[] = []
for (let i = 0; i + 1 < letters.length; i++) {
pairs.push([letters[i], letters[i + 1]])
}
console.log(pairs)
// the same walk, but compare the lengths of the neighbours
const longer: boolean[] = []
for (let i = 0; i + 1 < letters.length; i++) {
longer.push(letters[i].length > letters[i + 1].length)
}
console.log(longer)