Snapshot Array

Updated on 09 July, 2025
Snapshot Array header image

Problem Statement

The task is to implement a data structure called SnapshotArray. This array-like structure supports special operations beyond normal array functionalities:

  1. Initialization:

    • SnapshotArray(int length): Constructs the SnapshotArray with specified length. Each element in the array is initialized to 0.
  2. Setting Values:

    • void set(index, val): Assigns the value val to the element at the specified index.
  3. Snapshot Creation:

    • int snap(): This operation captures the current state of the array and saves it as a snapshot. It then returns a unique identifier for this snapshot, termed as snap_id, which is the count of total snapshots taken so far minus one.
  4. Value Retrieval:

    • int get(index, snap_id): Fetches the value at a specific index from the snapshot identified by snap_id.

The purpose of this data structure is to efficiently track changes over time and enable the retrieval of past versions of the data array without altering the current state.

Examples

Example 1

Input:

["SnapshotArray", "set", "snap", "set", "get"]
[[3], [0, 5], [], [0, 6], [0, 0]]

Output:

[null, null, 0, null, 5]

Explanation:

SnapshotArray snapshotArr = new SnapshotArray(3); // Set the length to be 3
snapshotArr.set(0, 5); // Set array[0] = 5
snapshotArr.snap(); // Take a snapshot, return snap_id = 0
snapshotArr.set(0, 6);
snapshotArr.get(0, 0); // Get the value of array[0] with snap_id = 0, return 5

Constraints

  • 1 <= length <= 5 * 10^4
  • 0 <= index < length
  • 0 <= val <= 10^9
  • 0 <= snap_id < (total number of calls to snap())
  • At most 5 * 10^4 calls will be made to set, snap, and get.

Approach and Intuition

The SnapshotArray functions as a version-controlled array, enabling not only modification of current values but also access to past configurations. The main challenge is to efficiently implement the set and get operations as they relate to snapshot history.

Efficient Implementation Strategy

  • Initialization:

    • Start with an internal structure to represent each index using a sorted map (e.g., a dictionary of snap_id → value).
    • Maintain a snap_id counter initialized to 0.
  • Setting Values:

    • For every set(index, val), update the internal map for the current snap_id only at that index.
  • Snapshot Creation:

    • For snap(), increment the snap_id counter and return the previous value.
    • No need to duplicate the array; we only track deltas.
  • Value Retrieval:

    • To get(index, snap_id), use binary search (or bisect) to find the closest snapshot at or before snap_id in that index’s history.

This approach ensures O(log N) retrieval and O(1) mutation in practice, and memory is efficiently used by storing only changes rather than full snapshots.

Solutions

  • C++
cpp
class SnapshotManager {
public:
    int currentSnapId;
    vector<vector<pair<int, int>>> snapshots;
    SnapshotManager(int length) {
        currentSnapId = 0;
        snapshots.resize(length);
        for (int i = 0; i < length; ++i) {
            snapshots[i].push_back({0, 0});
        }
    }
        
    void assign(int index, int value) {
        snapshots[index].push_back({currentSnapId, value});
    }
        
    int snapshot() {
        return currentSnapId++;
    }
        
    int fetch(int index, int snap_id) {
        auto iter = upper_bound(snapshots[index].begin(), snapshots[index].end(), make_pair(snap_id, INT_MAX));
        return prev(iter)->second;
    }
};

The provided C++ solution implements a snapshot array system using a class named SnapshotManager. This class provides functionalities to manage snapshot versions of array values. Here's an overview:

  • The constructor SnapshotManager(int length) initializes a 2D vector 'snapshots' with a given length. Each element in the array starts with an initial snapshot of the value zero.

  • The assign(int index, int value) function adds a pair consisting of the current snapshot ID and the value to be assigned to the snapshots vector at the specified index.

  • The snapshot() function increments and returns the current snapshot ID, signaling the creation of a new snapshot.

  • The fetch(int index, int snap_id) function retrieves a value from the snapshots at a given index corresponding to a specified snapshot ID. It employs binary search via upper_bound to efficiently find the closest earlier snapshot and return its value.

The design demonstrates how to efficiently deal with a mutable state that needs to be versioned and accessed in constant snapshot states. The use of pairs to store values along with their respective snapshot IDs, combined with vector resizing and binary search, highlights a balance between time complexity and functionality in managing snapshot states of a dynamically changing array.

  • Java
java
class SnapshotArray {
    int snapshotId = 0;
    TreeMap<Integer, Integer>[] stateHistory;
        
    public SnapshotArray(int size) {
        stateHistory = new TreeMap[size];
        for (int i = 0; i < size; i++) {
            stateHistory[i] = new TreeMap<Integer, Integer>();
            stateHistory[i].put(0, 0);
        }
    }
    
    public void set(int index, int value) {
        stateHistory[index].put(snapshotId, value);
    }
    
    public int snap() {
        return snapshotId++;
    }
    
    public int get(int index, int snapId) {
        return stateHistory[index].floorEntry(snapId).getValue();
    }
}

The provided Java solution demonstrates how to implement the Snapshot Array operation. The core data structure used is an array of TreeMap<Integer, Integer> where each TreeMap maps a snapshot ID to a value.

Here are the essential functions and how they operate:

  1. Constructor SnapshotArray(int size): This initializes an array of TreeMap, one for each index in the snapshot array. Initially, each map contains an entry setting the 0th snapshot value to 0.

  2. Method set(int index, int value): Updates the value at a specific index for the current snapshot ID. This means appending the current value to the TreeMap at the given index.

  3. Method snap(): Increases the snapshot ID by one and returns the previous snapshot ID. Each snap() call represents a recording point for all preceding set() operations.

  4. Method get(int index, int snapId): Retrieves the value from the snapshot array at a specific index for a specific snapshot ID. The method uses the floorEntry(snapId) method on the TreeMap to find the closest snapshot entry that is less than or equal to the given snapshot ID.

This solution effectively allows tracking the history of values at different indices over multiple snapshots without having to copy the entire array's content with each snapshot, resulting in a more space-efficient implementation. Each method in the class provides functionality that synchronizes with operations distributed over time and snapshots, allowing temporal navigation and mutation state recall at specific points.

  • Python
python
class SnapshotArray:
    
    def __init__(self, length: int):
        self.snapshot_id = 0
        self.snap_history = [[[0, 0]] for _ in range(length)]
            
    def set(self, index: int, val: int) -> None:
        self.snap_history[index].append([self.snapshot_id, val])
    
    def snap(self) -> int:
        self.snapshot_id += 1
        return self.snapshot_id - 1
    
    def get(self, index: int, snap_id: int) -> int:
        history_index = bisect.bisect_right(self.snap_history[index], [snap_id, 10 ** 9])
        return self.snap_history[index][history_index - 1][1]

The provided Python code implements a class called SnapshotArray which simulates a snapshot array data structure. Here's how each component functions:

  • The initialization method __init__ sets up the array. Each entry in the array starts with a snapshot history containing a single snapshot with ID 0 and initial value 0.

  • The set method adds or updates the value at a specific index for the current snapshot. Every time you update an index, it records this value with the current snapshot ID.

  • The snap method increments the snapshot ID and returns the previous snapshot ID. This method effectively simulates taking a snapshot of the array by making the next set of modifications trackable under a new snapshot ID.

  • The get method fetches the value at a given index as of the specified snapshot ID. It uses binary search to find the closest value that doesn't exceed the requested snapshot ID.

Use this class to trace changes in the array state over discrete snapshots, preserving the history up to the most recent snapshot taken. It enables access to any past state without affecting current insertion and access speeds significantly. This system is particularly useful in scenarios where historical data retrieval is as important as current data modifications.

Comments

No comments yet.