Solution: Snapshot Array

Statement

In this challenge, you have to implement a Snapshot Array with the following properties:

  • Constructor (length): This is the constructor and it initializes the data structure to hold the specified number of indexes.

  • Set Value (idx, val): This property sets the value at a given index idx to value val.

  • Snapshot(): This method takes no parameters and returns the Snap ID. Snap ID is the number of times that the snapshot function was called, less 11, as we start the count at 00. The first time this function is called, it saves a snapshot and returns 00. The nthn^{th} time it is called, after saving the snapshot, it returns n−1n-1.

  • Get Value (idx, Snap ID) method returns the value at the index in the snapshot with the given Snap ID.

Suppose that we have three nodes whose values we wish to track in the snapshot array. Initially, the value of all the nodes will be 00. After calling the Set Value (1, 4) function, the value of node 1 will change to 44. If we take a snapshot at this point, the current values of all the nodes will be saved with Snap ID 00. Now, if we call Set Value (1, 7), the current value for node 1 will change to 77. Now, if we call the Get Value (1, 0) function, we will get the value of node 1 from snapshot 00, that is, 44.

Constraints:

  • 1≤1 \leq length ≤1000\leq 1000
  • 0≤0 \leq idx << length
  • 0≤0 \leq val ≤109\leq 10^9
  • 0≤0 \leq snapid << (the total number of times we call Snapshot)
  • At most 5×1035 \times 10^3 calls will be made to Set Value, Snapshot, and Get Value.

Solution

So far, you have probably brainstormed some approaches and have an idea of how to solve this problem. Let’s explore some of these approaches and figure out which one to follow based on considerations such as time complexity and any implementation constraints.

Naive approach

A naive approach to create an array-like data structure that supports snapping its values ​​at different times would be to create a list to store the array values ​​in each snapshot. We will increment the counter to keep track of the current snapshot ID. To take a snapshot, we will copy the current list and add it to the list of snapshots. To get the value on a given index and the snapshot ID, we access the given snapshot ID and the corresponding element in the snapshot list for the index.

For example, we want to create an array of length 5 and take three snapshots of its values at different times. We will create a list to store the values of the array at each snapshot, as well as a counter to keep track of the current snapshot ID:

arr = [0, 0, 0, 0, 0]

To set a value at a given index, we can update the corresponding element in the list:

arr[2] = 4

To take a snapshot, we will create a copy of the current list and add it to the list of snapshots. After taking three snapshots, the snapshots list will contain the following:

snapshots = [[0, 0, 0, 0, 0], [0, 0, 4, 0, 0], [0, 0, 4, 0, 0], [0, 0, 4, 0, 0]]

This naive approach will work for small arrays and a small number of snapshots. When the size of the array and the number of snapshots increases, this approach can become inefficient, since it requires copying the entire list for each snapshot. Let’s see if we can use the Custom Data Structures pattern to reduce the complexity of our solution.

Optimized approach using nested dictionaries

To solve this problem, we will start by creating a dictionary named Node Value. The node value will hold all the values, along with the nodes, at different times in further subdictionaries. The keys to the Node Value dictionary are the Snapshot IDs. For a given key, the values are also dictionaries. These inner dictionaries have node IDs as keys and the node’s value as values.

The Node Value dictionary keeps track of values that are set until the latest snapshot, rather than storing the entire array for each snapshot, as in the naive approach. This allows for more efficient memory usage and faster performance, especially for large arrays and a large number of snapshots.