JS - Iterables And Collections

Iterables

In JS iterables are objects, which implement iterable protocol and have @@iterator method. Those are arrays, linked lists, maps, sets, strings and so on.

There are also array-like objects - objects, that have a length and possibility to acces it's elements by index.

Array

To create an array in JS there are many possible ways:

const a1 = ['elem'];
const a2 = Array('elem');
const a3 = new Array('elem');
const a4 = Array.of('elem');
const a5 = Array.from('elem');

Note: new Array(5) or Array(5) will create new empty array with given length of 5

Array.from(iterable) - this method is meant to convert an array-like object or iterable to an actual array.

e.g.

const arr = Array.from('string');
// will contain ['s', 't','r', 'i', 'n', 'g'];

Looping Over Array

To loop over an iterable object we can use for .. of .. loop:

for (const data of iterable) {}

Array Methods

push:

To add an element to the end of the collection use push method:

const elems = [1, 2, 3];
elems.push(4);
// will contain [1,2,3,4]

unshift:

To add an element to the beginning of the collection use unshift method:

const elems = [1, 2, 3];
elems.unshift(0);
console.log(elems);
// will contain [0,1,2,3]

pop:

pop method removes last element of a collection and returns it:

const lastElem = elems.pop();

shift:

Method shift removes one element from the beginning of a collection:

const firstElem = elems.shift();

splice:

Method splice allows to remove arbitrary number of elements in the given array and replace them with any number of new elements. It is an array method an can't be called on other iterables.

arr.splice(<start position>, <amount of removed elements>, ...<collection of elements to insert>)

const elems = [1, 2, 3];
elems.splice(1, 2, ...[5, 6]);
// result elems => [1, 5, 6]

It is also possible to remove elements counting from the tail. To do this use less than 0 index:

elems.splice(-2, 1, 7);
// result elems => [1, 7, 6]

slice:

slice method allows to create a copy of some part of the array:

const elems1 = [1, 2, 3, 4, 5];
const elemsCopy = elems1.slice(2, 4);
// elemsCopy: [3, 4]

It is also possible to use negative indexes to count from the tail of the collection.

Not specifying range will copy the entire array:

const elems1 = [1, 2, 3, 4, 5];
const fullCopy = elems1.slice();

concat:

concat method accepts another collection and return as a result new array, where current and given array are concatenated together. It does not change original array.

indexOf:

indexOf allows to find index of specified element in the collection. Second argument allows to define starting position to search. If specified, any previous hits will not be counted. If nothing found result will be -1.

const elemsToSearch = [1, 2, 1, 1, 1, 2, 1];
const i = elemsToSearch.indexOf(2, 3);
// `i` will be 5

includes:

Allows to check wheather given element exists in collection:

const elems = [1, 2, 3, 4];
const exists3 = elems.includes(3);
// true

Important to notice, that it will work with value types, because equality of reference types words differently

lastIndexOf:

lastIndexOf has the same functionality as indexOf. The only difference - it searches starting from the end of the collection towards it's beginning.

find:

find allows to search for required element in the specified array by given predicate:

const complElemsToSearch = [{ key: 1 }, { key: 2 }, { key: 3 }];
const foundElem = complElemsToSearch
  .find((val, ind, arr) => val.key === 2);

Where val is a reference to the current iteration value, ind - index of current iteration and arr is a reference to the whole array.

It returns reference to the found object, but no it's index. To search for index use findIndex method with the same signature.

forEach:

This method allows to iterate over every item in the array and perform some action, specified with provided function.

const elements = [1, 2, 3, 4, 5];
elements.forEach((val, ind, arr) => {
  console.log(`current value is ${val}`);
})

map:

If there is a need to perform some action on every element of a collection and store results in a new collection, it can't acheived with map method:

const elements = [1, 2, 3, 4, 5];
const resultElements = elements
  .map((val, ind, arr) => val * 2);
// resultElements: [2, 4, 6, 8, 10]

filter:

To filter some collection with given predicate and return result as a new array, use filter:

const elemsToFilter = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const filtered = elemsToFilter
  .filter((val, ind, arr) => val % 2 === 0);
// filtered: [2, 4, 6, 8]

reduce:

When you need to calculate some single value from whole collection values(reduce) it is a usecase for reduce method. For example, when we need to calculate sum of all array numbers:

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const sum = numbers
  .reduce((prev, curr, ind, arra) => prev + curr, 0);
// sum: 45

It accepts a reducer function, which signature contains accumulation value and current value, as well as usual for some other collection methods index and whole array reference. And also reduce accepts second optional value - initial accumulation value.

Sorting

sort:

const elemsToSort = [1, 5, 2, 3, 10, 4, 6];
const sorted = elemsToSort.sort((a, b) => {
  if (a > b) {
    return 1;
  } else if (a < b) {
    return -1;
  }
  return 0;
});
// sorted: [1, 2, 3, 4, 5, 6, 10]

Set

Set is a data structure, which contains unordered and arbitrary number of unique elements. It is iterable, but also contains some unique methods. It does not guarantee order of stored elements and does not allow access to elements by index.

Creting Set:

const s1 = new Set([1, 2, 3]);
console.log(s1);

Iterating over Set;

for (const elem of s1.entries()) {
  console.log(elem);
}

Cheching existence and deleting of element inside Set:

if (s1.has(2)) {
  s1.delete(2);
}

Map

Map is a key-value data structure, where key must be a unique value. It is iterable with some additional methods. It does not allow acces by index. Instead values may be accessed only by it's keys. Unlike object keys may be not only of string, number or symbol type, but any type.

Maps are quite similar to js objects, but unlike them maps allow any data as key and also may be more performant on large amount of data sets. Also maps are better on frequent inserts/removes of data.

Creating of Map:

const m1 = new Map([
  ["key1", "value1"],
  ["key2", "value2"],
]);

Inserting and retrieving values:

m1.set("key3", "value3");
const value2 = m1.get("key2");

Iterating over Map:

for (const [key, value] of m1.entries()) {
  console.log(key, value);
}

entries() returns key/value pairs iterable of two elements arrays. But maps have also keys() methods, which returns iterable of only map's keys and values() method, which returns all values from map.

To get size of map:

const m1length = m1.size;