Monday, November 4, 2024

Algorithms Behind JavaScript Array Methods

concat()

join()

fill()

includes()

indexOf()

reverse()

sort()

splice()

at()

copyWithin()

flat()

Array.from()

findLastIndex()

forEach()

every()

entries()

values()

toReversed() (creates a reversed copy of the array without modifying the original)

toSorted() (creates a sorted copy of the array without modifying the original)

toSpliced() (creates a new array with elements added or removed without modifying the original)

with() (returns a copy of the array with a specific element replaced)

Array.fromAsync()

Array.of()

map()

flatMap()

reduce()

reduceRight()

some()

find()

findIndex()

findLast()

Let me break down the common algorithms used for each JavaScript array method:


1. concat()

Algorithm: Linear append/merge

Time Complexity: O(n) where n is total length of all arrays

Internally uses iteration to create new array and copy elements

// concat()

Array.prototype.myConcat = function(...arrays) {

  const result = [...this];

  for (const arr of arrays) {

    for (const item of arr) {

      result.push(item);

    }

  }

  return result;

};

2. join()

Algorithm: Linear traversal with string concatenation

Time Complexity: O(n)

Iterates through array elements and builds result string

// join()

Array.prototype.myJoin = function(separator = ',') {

  let result = '';

  for (let i = 0; i < this.length; i++) {

    result += this[i];

    if (i < this.length - 1) result += separator;

  }

  return result;

};

3. fill()

Algorithm: Linear traversal with assignment

Time Complexity: O(n)

Simple iteration with value assignment

// fill()

Array.prototype.myFill = function(value, start = 0, end = this.length) {

  for (let i = start; i < end; i++) {

    this[i] = value;

  }

  return this;

};

4. includes()

Algorithm: Linear search

Time Complexity: O(n)

Sequential scan until element found or end reached

// includes()

Array.prototype.myIncludes = function(searchElement, fromIndex = 0) {

  const startIndex = fromIndex >= 0 ? fromIndex : Math.max(0, this.length + fromIndex);

  for (let i = startIndex; i < this.length; i++) {

    if (this[i] === searchElement || (Number.isNaN(this[i]) && Number.isNaN(searchElement))) {

      return true;

    }

  }

  return false;

};

5. indexOf()

Algorithm: Linear search

Time Complexity: O(n)

Sequential scan from start until match found

// indexOf()

Array.prototype.myIndexOf = function(searchElement, fromIndex = 0) {

  const startIndex = fromIndex >= 0 ? fromIndex : Math.max(0, this.length + fromIndex);

  for (let i = startIndex; i < this.length; i++) {

    if (this[i] === searchElement) return i;

  }

  return -1;

};

6. reverse()

Algorithm: Two-pointer swap

Time Complexity: O(n/2)

Swaps elements from start/end moving inward

// reverse()

Array.prototype.myReverse = function() {

  let left = 0;

  let right = this.length - 1;


  while (left < right) {

    // Swap elements

    const temp = this[left];

    this[left] = this[right];

    this[right] = temp;

    left++;

    right--;

  }


  return this;

};

7. sort()

Algorithm: Typically TimSort (hybrid of merge sort and insertion sort)

Time Complexity: O(n log n)

Modern browsers use adaptive sorting algorithms

// sort()

Array.prototype.mySort = function(compareFn) {

  // Implementation of QuickSort for simplicity

  // Note: Actual JS engines typically use TimSort

  const quickSort = (arr, low, high) => {

    if (low < high) {

      const pi = partition(arr, low, high);

      quickSort(arr, low, pi - 1);

      quickSort(arr, pi + 1, high);

    }

  };


  const partition = (arr, low, high) => {

    const pivot = arr[high];

    let i = low - 1;


    for (let j = low; j < high; j++) {

      const compareResult = compareFn ? compareFn(arr[j], pivot) : String(arr[j]).localeCompare(String(pivot));

      if (compareResult <= 0) {

        i++;

        [arr[i], arr[j]] = [arr[j], arr[i]];

      }

    }

    [arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];

    return i + 1;

  };


  quickSort(this, 0, this.length - 1);

  return this;

};

8. splice()

Algorithm: Linear array modification

Time Complexity: O(n)

Shifts elements and modifies array in-place

// splice()

Array.prototype.mySplice = function(start, deleteCount, ...items) {

  const len = this.length;

  const actualStart = start < 0 ? Math.max(len + start, 0) : Math.min(start, len);

  const actualDeleteCount = Math.min(Math.max(deleteCount || 0, 0), len - actualStart);


  // Store deleted elements

  const deleted = [];

  for (let i = 0; i < actualDeleteCount; i++) {

    deleted[i] = this[actualStart + i];

  }


  // Shift elements if necessary

  const itemCount = items.length;

  const shiftCount = itemCount - actualDeleteCount;


  if (shiftCount > 0) {

    // Moving elements right

    for (let i = len - 1; i >= actualStart + actualDeleteCount; i--) {

      this[i + shiftCount] = this[i];

    }

  } else if (shiftCount < 0) {

    // Moving elements left

    for (let i = actualStart + actualDeleteCount; i < len; i++) {

      this[i + shiftCount] = this[i];

    }

  }


  // Insert new items

  for (let i = 0; i < itemCount; i++) {

    this[actualStart + i] = items[i];

  }


  this.length = len + shiftCount;

  return deleted;

};

9. at()

Algorithm: Direct index access

Time Complexity: O(1)

Simple array indexing with boundary checking

// at()

Array.prototype.myAt = function(index) {

  const actualIndex = index >= 0 ? index : this.length + index;

  return this[actualIndex];

};

10. copyWithin()

Algorithm: Block memory copy

Time Complexity: O(n)

Internal memory copy and shift operations

// copyWithin()

Array.prototype.myCopyWithin = function(target, start = 0, end = this.length) {

  const len = this.length;

  let to = target < 0 ? Math.max(len + target, 0) : Math.min(target, len);

  let from = start < 0 ? Math.max(len + start, 0) : Math.min(start, len);

  let final = end < 0 ? Math.max(len + end, 0) : Math.min(end, len);

  const count = Math.min(final - from, len - to);


  // Copy to temporary array to handle overlapping

  const temp = new Array(count);

  for (let i = 0; i < count; i++) {

    temp[i] = this[from + i];

  }


  for (let i = 0; i < count; i++) {

    this[to + i] = temp[i];

  }


  return this;

};


11. flat()

Algorithm: Recursive depth-first traversal

Time Complexity: O(n) for single level, O(d*n) for depth d

Recursively flattens nested arrays

// flat()

Array.prototype.myFlat = function(depth = 1) {

  const flatten = (arr, currentDepth) => {

    const result = [];

    for (const item of arr) {

      if (Array.isArray(item) && currentDepth < depth) {

        result.push(...flatten(item, currentDepth + 1));

      } else {

        result.push(item);

      }

    }

    return result;

  };


  return flatten(this, 0);

};

12. Array.from()

Algorithm: Iteration and copy

Time Complexity: O(n)

Creates new array from iterable

// Array.from()

Array.myFrom = function(arrayLike, mapFn) {

  const result = [];

  for (let i = 0; i < arrayLike.length; i++) {

    result[i] = mapFn ? mapFn(arrayLike[i], i) : arrayLike[i];

  }

  return result;

};

13. findLastIndex()

Algorithm: Reverse linear search

Time Complexity: O(n)

Sequential scan from end until match found

// findLastIndex()

Array.prototype.myFindLastIndex = function(predicate) {

  for (let i = this.length - 1; i >= 0; i--) {

    if (predicate(this[i], i, this)) return i;

  }

  return -1;

};

14. forEach()

Algorithm: Linear iteration

Time Complexity: O(n)

Simple iteration with callback execution

// forEach()

Array.prototype.myForEach = function(callback) {

  for (let i = 0; i < this.length; i++) {

    if (i in this) {  // Skip holes in sparse arrays

      callback(this[i], i, this);

    }

  }

};

15. every()

Algorithm: Short-circuit linear scan

Time Complexity: O(n)

Stops on first false condition


// every()

Array.prototype.myEvery = function(predicate) {

  for (let i = 0; i < this.length; i++) {

    if (i in this && !predicate(this[i], i, this)) {

      return false;

    }

  }

  return true;

};

16. entries()

Algorithm: Iterator protocol implementation

Time Complexity: O(1) for creation, O(n) for full iteration

Creates iterator object

// entries()

Array.prototype.myEntries = function() {

  let index = 0;

  const array = this;


  return {

    [Symbol.iterator]() {

      return this;

    },

    next() {

      if (index < array.length) {

        return { value: [index, array[index++]], done: false };

      }

      return { done: true };

    }

  };

};

17. values()

Algorithm: Iterator protocol implementation

Time Complexity: O(1) for creation, O(n) for full iteration

Creates iterator for values

// values()

Array.prototype.myValues = function() {

  let index = 0;

  const array = this;


  return {

    [Symbol.iterator]() {

      return this;

    },

    next() {

      if (index < array.length) {

        return { value: array[index++], done: false };

      }

      return { done: true };

    }

  };

};

18. toReversed()

Algorithm: Copy with reverse iteration

Time Complexity: O(n)

Creates new reversed array

// toReversed()

Array.prototype.myToReversed = function() {

  const result = new Array(this.length);

  for (let i = 0; i < this.length; i++) {

    result[i] = this[this.length - 1 - i];

  }

  return result;

};

19. toSorted()

Algorithm: Copy then TimSort

Time Complexity: O(n log n)

Creates sorted copy using standard sort

// toSorted()

Array.prototype.myToSorted = function(compareFn) {

  return [...this].mySort(compareFn); // Reuse mySort implementation

};

20. toSpliced()

Algorithm: Copy with modification

Time Complexity: O(n)

Creates modified copy

// toSpliced()

Array.prototype.myToSpliced = function(start, deleteCount, ...items) {

  const result = [...this];

  result.mySplice(start, deleteCount, ...items);  // Reuse mySplice implementation

  return result;

};

21. with()

Algorithm: Shallow copy with single modification

Time Complexity: O(n)

Creates copy with one element changed

// with()

Array.prototype.myWith = function(index, value) {

  const result = [...this];

  result[index < 0 ? this.length + index : index] = value;

  return result;

};

22. Array.fromAsync()

Algorithm: Asynchronous iteration and collection

Time Complexity: O(n) + async operations

Handles promises and async iterables

// Array.fromAsync()

Array.myFromAsync = async function(asyncIterable) {

  const result = [];

  for await (const item of asyncIterable) {

    result.push(item);

  }

  return result;

};

23. Array.of()

Algorithm: Direct array creation

Time Complexity: O(n)

Creates array from arguments

// Array.of()

Array.myOf = function(...items) {

  return items;

};

24. map()

Algorithm: Transform iteration

Time Complexity: O(n)

Creates new array with transformed elements

// map()

Array.prototype.myMap = function(callback) {

  const result = new Array(this.length);

  for (let i = 0; i < this.length; i++) {

    if (i in this) {  // Skip holes in sparse arrays

      result[i] = callback(this[i], i, this);

    }

  }

  return result;

};

25. flatMap()

Algorithm: Map + flatten

Time Complexity: O(n*m) where m is average mapped array size

Combines mapping and flattening

// flatMap()

Array.prototype.myFlatMap = function(callback) {

  return this.myMap(callback).myFlat();

};

26. reduce()

Algorithm: Linear accumulation

Time Complexity: O(n)

Sequential accumulation with callback

// reduce()

Array.prototype.myReduce = function(callback, initialValue) {

  let accumulator = initialValue;

  let startIndex = 0;


  if (initialValue === undefined) {

    if (this.length === 0) throw new TypeError('Reduce of empty array with no initial value');

    accumulator = this[0];

    startIndex = 1;

  }


  for (let i = startIndex; i < this.length; i++) {

    if (i in this) {

      accumulator = callback(accumulator, this[i], i, this);

    }

  }


  return accumulator;

};

27. reduceRight()

Algorithm: Reverse linear accumulation

Time Complexity: O(n)

Right-to-left accumulation

// reduceRight()

Array.prototype.myReduceRight = function(callback, initialValue) {

  let accumulator = initialValue;

  let startIndex = this.length - 1;


  if (initialValue === undefined) {

    if (this.length === 0) throw new TypeError('Reduce of empty array with no initial value');

    accumulator = this[this.length - 1];

    startIndex = this.length - 2;

  }


  for (let i = startIndex; i >= 0; i--) {

    if (i in this) {

      accumulator = callback(accumulator, this[i], i, this);

    }

  }


  return accumulator;

};

28. some()

Algorithm: Short-circuit linear scan

Time Complexity: O(n)

Stops on first true condition

// some()

Array.prototype.mySome = function(predicate) {

  for (let i = 0; i < this.length; i++) {

    if (i in this && predicate(this[i], i, this)) {

      return true;

    }

  }

  return false;

};

29. find()

Algorithm: Linear search

Time Complexity: O(n)

Sequential scan until condition met

// find()

Array.prototype.myFind = function(predicate) {

  for (let i = 0; i < this.length; i++) {

    if (i in this && predicate(this[i], i, this)) {

      return this[i];

    }

  }

  return undefined;

};

30. findIndex()

Algorithm: Linear search

Time Complexity: O(n)

Sequential scan for matching condition

// findIndex()

Array.prototype.myFindIndex = function(predicate) {

  for (let i = 0; i < this.length; i++) {

    if (i in this && predicate(this[i], i, this)) {

      return i;

    }

  }

  return -1;

};

31. findLast()

Algorithm: Reverse linear search

Time Complexity: O(n)

Sequential scan from end

// findLast()

Array.prototype.myFindLast = function(predicate) {

  for (let i = this.length - 1; i >= 0; i--) {

    if (i in this && predicate(this[i], i, this)) {

      return this[i];

    }

  }

  return undefined;

};