Hone logo
Hone
Problems

Reimplementing Array.prototype.map

JavaScript's Array.prototype.map() is a fundamental method for transforming arrays. It creates a new array populated with the results of calling a provided function on every element in the calling array. This challenge asks you to recreate this functionality from scratch, enhancing your understanding of array manipulation and higher-order functions.

Problem Description

Your task is to implement a function named myMap that mimics the behavior of Array.prototype.map(). This function will be added to the Array.prototype so it can be called directly on any array instance.

Requirements:

  1. The myMap function should accept two arguments:
    • callback: A function to execute for each element in the array.
    • thisArg (optional): A value to use as this when executing the callback function.
  2. The callback function will be called with three arguments:
    • currentValue: The current element being processed in the array.
    • index: The index of the currentValue in the array.
    • array: The array myMap was called upon.
  3. myMap should return a new array. It should not modify the original array.
  4. The myMap function should iterate over all elements of the array, even if some elements are undefined or have been deleted (sparse arrays).
  5. If thisArg is provided, it should be used as the this context for the callback function.
  6. If the callback function is not provided or is not a function, a TypeError should be thrown.

Expected Behavior:

  • For each element in the original array, myMap will call the callback function.
  • The return value of each callback invocation will be placed into the corresponding position in the new array.
  • If an element in the original array is undefined, the callback should still be invoked for that element.
  • If the array is empty, myMap should return an empty array.

Examples

Example 1:

const numbers = [1, 2, 3, 4];
const doubledNumbers = numbers.myMap(function(num) {
  return num * 2;
});
// doubledNumbers is [2, 4, 6, 8]
// numbers is still [1, 2, 3, 4]

Explanation: The callback function (num) => num * 2 is applied to each element of the numbers array. The results are collected into a new array doubledNumbers.

Example 2:

const words = ["hello", "world"];
const lengths = words.myMap((word, index) => `${index}: ${word.length}`);
// lengths is ["0: 5", "1: 5"]

Explanation: The callback uses both the word and its index to create a new string for each element.

Example 3:

const sparseArray = [1, , 3];
const mappedSparse = sparseArray.myMap(function(element, index) {
  console.log(`Processing index ${index} with value: ${element}`);
  return element === undefined ? 'empty' : element * 10;
});
// Output to console:
// Processing index 0 with value: 1
// Processing index 2 with value: 3
// mappedSparse is [10, undefined, 30] (Note: the empty slot results in undefined for callback, and the new array preserves the slot's nature conceptually)

Explanation: Even though there's an empty slot (which translates to undefined when accessed), myMap iterates over it and calls the callback. The callback's return value determines what goes into the new array. For the empty slot, the callback receives undefined and returns 'empty' in this specific example's callback, but the new array at that index would technically be empty/undefined if the callback returned undefined. This example shows that the iteration occurs. A more accurate representation for a truly sparse result would be if the callback explicitly returned undefined for the empty slot. Let's adjust the example for clarity on sparse output.

Example 3 (Revised for Sparse Output):

const sparseArray = [1, , 3];
const mappedSparse = sparseArray.myMap(function(element, index) {
  // The callback will be called for index 0, index 1, and index 2
  // 'element' will be undefined for index 1
  return element !== undefined ? element * 10 : undefined;
});
// mappedSparse is [10, undefined, 30]

Explanation: The callback is invoked for all indices. For the empty slot (index 1), element is undefined. The callback checks for undefined and returns undefined for that position in the new array.

Example 4 (with thisArg):

const multiplier = 5;
const numbers = [1, 2, 3];
const multiplied = numbers.myMap(function(num) {
  return num * this.multiplier;
}, { multiplier: multiplier });
// multiplied is [5, 10, 15]

Explanation: The thisArg object is used as the this context within the callback function, allowing access to its multiplier property.

Constraints

  • The implementation should not use the built-in Array.prototype.map.
  • The implementation should handle arrays of any size, from empty to very large.
  • The callback function should be invoked with the correct thisArg.
  • The myMap function should be added directly to Array.prototype.

Notes

  • Remember to handle the case where callback is not a function.
  • Consider how you would iterate over sparse arrays to ensure every potential index is accounted for, even if it doesn't hold a value.
  • The thisArg is optional. If it's not provided, this inside the callback will be undefined in strict mode, or the global object in non-strict mode, consistent with Array.prototype.map.
  • Think about the return value of the callback. It can be any data type.
  • The new array should have the same length as the original array.
Loading editor...
javascript