Hone logo
Hone
Problems

Immutable List in JavaScript

Creating immutable data structures is a cornerstone of functional programming and helps prevent unexpected side effects in your code. This challenge asks you to implement a basic immutable list in JavaScript, ensuring that modifications to the list always return a new list rather than altering the original. This promotes predictability and simplifies debugging.

Problem Description

You are tasked with creating a JavaScript class called ImmutableList. This class should represent a list of values and guarantee immutability. The class should support the following operations:

  • constructor(initialValues): Takes an array of initial values as input and creates a new ImmutableList instance.
  • push(value): Returns a new ImmutableList instance with the given value added to the end. The original list should remain unchanged.
  • pop(): Returns a new ImmutableList instance with the last element removed. The original list should remain unchanged. If the list is empty, it should return a new empty ImmutableList.
  • get(index): Returns the value at the given index in the list. Should return undefined if the index is out of bounds.
  • toArray(): Returns a standard JavaScript array containing the elements of the immutable list.

Key Requirements:

  • Immutability: All operations (push, pop, get) must return a new ImmutableList instance. The original ImmutableList instance should never be modified directly.
  • Data Integrity: The list should maintain the order of elements.
  • Error Handling: get() should handle out-of-bounds indices gracefully. pop() should handle empty lists gracefully.

Examples

Example 1:

Input: const list = new ImmutableList([1, 2, 3]);
       const newList = list.push(4);
Output: list.toArray() === [1, 2, 3]
        newList.toArray() === [1, 2, 3, 4]
Explanation: `push(4)` returns a new list with 4 appended. The original list remains unchanged.

Example 2:

Input: const list = new ImmutableList([1, 2, 3]);
       const newList = list.pop();
Output: list.toArray() === [1, 2, 3]
        newList.toArray() === [1, 2]
Explanation: `pop()` returns a new list with the last element removed. The original list remains unchanged.

Example 3:

Input: const list = new ImmutableList([]);
       const newList = list.pop();
Output: list.toArray() === []
        newList.toArray() === []
Explanation: `pop()` on an empty list returns a new empty list.

Example 4:

Input: const list = new ImmutableList([1, 2, 3]);
       const value = list.get(1);
Output: value === 2
Explanation: `get(1)` returns the element at index 1.

Example 5:

Input: const list = new ImmutableList([1, 2, 3]);
       const value = list.get(5);
Output: value === undefined
Explanation: `get(5)` returns undefined because the index is out of bounds.

Constraints

  • The initial values array passed to the constructor can contain any JavaScript data types.
  • The push and pop operations should be reasonably efficient (avoiding unnecessary copying if possible, but immutability is the priority).
  • The get operation should have O(1) time complexity.
  • The toArray operation should have O(n) time complexity, where n is the number of elements in the list.

Notes

  • Consider using techniques like spreading (...) to create new arrays without modifying the original.
  • Think about how to efficiently manage the underlying data structure to support immutability. A simple array is a good starting point.
  • Focus on ensuring that all operations return new instances of ImmutableList. Testing this thoroughly is crucial.
  • While performance is a consideration, prioritize correctness and immutability. Optimizations can be explored later.
Loading editor...
javascript