Skip to content

Latest commit

 

History

History
152 lines (108 loc) · 4.38 KB

File metadata and controls

152 lines (108 loc) · 4.38 KB

88. support negative Array index in JavaScript

Problem

https://bigfrontend.dev/problem/support-negative-Array-index

Problem Description

Python supports negative list index , while JavaScript doesn't.

Can you write a wrapper function to make negative array index possible?

const originalArr = [1, 2, 3];
const arr = wrap(originalArr);

arr[0]; // 1
arr[1]; // 2
arr[2]; // 3
arr[3]; // undefined
arr[-1]; // 3
arr[-2]; // 2
arr[-3]; // 1
arr[-4]; // undefined

All methods on arr should be applied to the original array, which means

arr.push(4);
arr[3] // 4
originalArr[3] // 4

arr.shift();
arr[0] // 2
originalArr[0] // 2

arr.bfe = 'bfe';
originalArr.bfe // 'bfe'

arr[-1] = 5;
arr // [2,3,5]
originalArr // [2,3,5]

originalArr[2] = 6;
arr // [2,3,6]
originalArr // [2,3,6]

Understanding the problem

I am asked to write a function that is going to wrap the input array to make it support negative array index, and return the result, which should be also an array.

const arr = wrap([1, 2, 3]);

Array.isArray(arr); // true

It should support the bracket notation, meaning we can update an element at a specific index using bracket notation:

  • If an element in the original array is updated using bracket notation, the change should also reflect in the wrapped array, and vice versa.

  • Placing an element at a negative index that is out of bounds of the input array should end up in an error, for instance:

    const arr = wrap([1, 2, 3]);
    
    arr[-5] = 'bfe'; // Error

In addition, all Array methods, i.e., Array.prototype.push() and Array.prototype.shift(), should be applied to the original array.

Approach

To solve the problem, I am going to create a Proxy for the original array and return the proxy.

When we create a Proxy for an object or an array, any changes in the original one will reflect in the proxy:

const obj = {
  a: 1,
  b: 2,
};

const objProxy = new Proxy(obj, {});
obj.c = 3;
console.log(objProxy.c); // 3

In addition, since a Proxy is an undetectable wrapper around an object/array, if we create a Proxy for an array, the Proxy is also going to be an array:

const arr = [1, 2, 3];

const arrProxy = new Proxy(arr, {});
console.log(Array.isArray(arrProxy)); // true

I would implement a get() handler to intercept attempts to access an element at a specific index in the input array and would also provide a set() handler to intercept attempts to update an element at a specific index.

In the get(target, prop, receiver) handler, I will first make sure that the prop can be converted to a number, because besides indices an array has other properties, i.e. .push. If the prop cannot convert to a number, then simply return the prop's value. Otherwise, check if the prop is negative, if it is, get the corresponding positive index by adding the prop to the length of the target and then return the element at that positive index; otherwise, simply return the element.

The set(target, prop, value, receiver) is going to be similar to the get() handler, however, rather than returning the element, I am going to update the prop's value to the new value. In addition, after successfully converting the prop to a number and getting the corresponding positive index, I am going to make sure the index is within bounds. If the resulting positive index is greater than the length of the target, since I need to support the Array,prototype.push method, or is still negative, then it means the index is out of bounds, so throw an error.

Solution

/**
 * @param {any[]} arr
 * @returns {?} - sorry no type hint for this
 */
function wrap(arr) {
  const handler = {
    get(target, prop, receiver) {
      prop = getPositiveIdx(target.length, prop);

      return Reflect.get(target, prop, receiver);
    },
    set(target, prop, value, receiver) {
      prop = getPositiveIdx(target.length, prop);

      const idxIsOutOfBounds = prop > arr.length || prop < 0;
      if (idxIsOutOfBounds) {
        throw new Error('Array Index Overflow');
      }

      Reflect.set(target, prop, value, receiver);
      return true;
    },
  };

  return new Proxy(arr, handler);
}

function getPositiveIdx(arrLength, propName) {
  try {
    let idx = Number(propName);
    if (isNaN(idx)) return propName;

    return idx < 0 ? arrLength + idx : idx;
  } catch (err) {
    return propName;
  }
}