Number Container 📦: Efficient Index-Element Management with Sorted Lists
Intuition 🤔The core idea is to build a data structure that allows us to quickly:
Update the element at a given index.
Find the smallest index associated with a
# Intuition 🤔
The core idea is to build a data structure that allows us to quickly:
1. **Update the element at a given index.**
2. **Find the smallest index associated with a given element.**

A simple dictionary could handle the index-to-element mapping. However, finding the smallest index for a given element efficiently requires a bit more thought. That's where the SortedList comes in! By storing the indices of each element in a sorted list, we can instantly grab the smallest index.

# Approach 🚀
We use two dictionaries to achieve this:
1. **mpidx:** This dictionary stores the mapping from index to element. It allows us to quickly determine what element is currently at a particular index.

2. **mpele:** This dictionary stores the mapping from element to a SortedList of indices. The SortedList holds all the indices where that element appears, and it keeps them in ascending order. This allows us to easily find the smallest index for a given element.

# Method Breakdown 🛠️
1. **__init__(self):**
    - Initializes mpidx and mpele as empty dictionaries. mpidx will store index -> element, and mpele will store element -> SortedList of indices.
2. **change(self, idx: int, ele: int) -> None:**
    - **Step 1:** **Handle Existing Index 🗑️:** If the idx already exists in mpidx, it means we're updating an existing entry.
        - Get the old_ele from mpidx[idx].
        - Remove idx from the SortedList associated with old_ele in mpele.
        - If the SortedList for old_ele becomes empty after removing idx, remove the old_ele entry from mpele to conserve space.
    - **Step 2: Update the Mappings 📝:**
        - Update mpidx[idx] with the new ele.
        - If ele is not already in mpele, create a new SortedList for it.
        - Add idx to the SortedList associated with ele in mpele. The SortedList automatically maintains the sorted order.
3. **find(self, ele: int) -> int:**
    - **Step 1: Check Existence ✅:** If ele is not in mpele, or if the SortedList for ele is empty, it means the element doesn't exist in our container. Return -1.
    - **Step 2: Return Smallest Index 🥇:** Otherwise, return the first element (self.mpele[ele]) of the SortedList associated with ele. Because it's a SortedList, the first element is guaranteed to be the smallest index.

# Complexities ⏱️
1. **Time Complexity:**
    - **__init__(self):** O(1) - Constant time to initialize dictionaries.
    - **change(self, idx: int, ele: int):**
        - O(log N) - Dominated by the remove and add operations on the SortedList, where N is the number of indices associated with a particular element.
        - O(1) on average for dictionary lookups and updates.
    - **find(self, ele: int):**
        - O(1) - Constant time to check dictionary existence and      retrieve the first element from the SortedList.

2. **Space Complexity: 💾**
    - O(K) - Where K is the number of unique indices stored in the container. This is due to the space used by mpidx and mpele. In the worst case, if all indices have unique elements, K could be equal to the total number of change operations.

# Why SortedList is a Good Choice 👍
1. **Efficient remove and add:** SortedList provides logarithmic time complexity for these operations, which is crucial for the change method.

2. **Maintained Order:** It automatically keeps the indices in sorted order, making it trivial to find the smallest index.

# Code
```python3 []
from sortedcontainers import SortedList

class NumberContainers:

    def __init__(self):
        self.mpidx = {} 
        self.mpele = {}  

    def change(self, idx: int, ele: int) -> None:
        if idx in self.mpidx:
            old_ele = self.mpidx[idx]
            self.mpele[old_ele].remove(idx)
            if not self.mpele[old_ele]:
                del self.mpele[old_ele]

        self.mpidx[idx] = ele
        if ele not in self.mpele:
            self.mpele[ele] = SortedList()
        self.mpele[ele].add(idx)

    def find(self,ele: int) -> int:
        if ele in self.mpele and self.mpele[ele]:
            return self.mpele[ele][0]
        return -1



# Your NumberContainers object will be instantiated and called as such:
# obj = NumberContainers()
# obj.change(index,number)
# param_2 = obj.find(number)
```
['Python3']