+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 95 of 365

๐Ÿ“˜ Bisect: Maintaining Sorted Lists

Master bisect: maintaining sorted lists in Python with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Basic understanding of programming concepts ๐Ÿ“
  • Python installation (3.8+) ๐Ÿ
  • VS Code or preferred IDE ๐Ÿ’ป

What you'll learn

  • Understand the concept fundamentals ๐ŸŽฏ
  • Apply the concept in real projects ๐Ÿ—๏ธ
  • Debug common issues ๐Ÿ›
  • Write clean, Pythonic code โœจ

๐ŸŽฏ Introduction

Welcome to this exciting tutorial on the bisect module! ๐ŸŽ‰ Ever wondered how to keep your lists sorted without the hassle of manually inserting elements in the right place? Thatโ€™s exactly what weโ€™ll explore today!

Youโ€™ll discover how Pythonโ€™s bisect module can transform the way you work with sorted data. Whether youโ€™re building leaderboards ๐Ÿ†, managing inventory systems ๐Ÿ“ฆ, or optimizing search algorithms ๐Ÿ”, understanding bisect is essential for writing efficient, elegant code.

By the end of this tutorial, youโ€™ll feel confident using bisect to maintain sorted lists like a pro! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Bisect

๐Ÿค” What is Bisect?

The bisect module is like a smart librarian ๐Ÿ“š who knows exactly where to place each new book on an already organized shelf. Think of it as your personal assistant that helps you insert items into sorted lists while keeping them perfectly ordered!

In Python terms, bisect provides support for maintaining a list in sorted order without having to sort the list after each insertion. This means you can:

  • โœจ Insert elements at the correct position instantly
  • ๐Ÿš€ Search sorted sequences blazingly fast
  • ๐Ÿ›ก๏ธ Maintain order without expensive sorting operations

๐Ÿ’ก Why Use Bisect?

Hereโ€™s why developers love bisect:

  1. Performance โšก: O(log n) insertion vs O(n log n) for sort
  2. Simplicity ๐ŸŽฏ: Clean, readable code for sorted operations
  3. Memory Efficiency ๐Ÿ’พ: No need to create new sorted lists
  4. Real-time Updates ๐Ÿ”„: Keep data sorted as it arrives

Real-world example: Imagine managing a game leaderboard ๐ŸŽฎ. With bisect, you can instantly insert new scores in the right position without re-sorting the entire list!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Example

Letโ€™s start with a friendly example:

import bisect

# ๐Ÿ‘‹ Hello, bisect!
scores = [10, 30, 50, 70, 90]  # ๐ŸŽฏ Already sorted list
new_score = 45

# ๐ŸŽจ Find where to insert the new score
position = bisect.bisect_left(scores, new_score)
print(f"Insert {new_score} at position: {position}")  # Position: 2

# โœจ Insert the score
scores.insert(position, new_score)
print(f"Updated scores: {scores}")  # [10, 30, 45, 50, 70, 90]

๐Ÿ’ก Explanation: Notice how bisect_left finds the perfect spot for our new score! The list stays sorted without calling sort().

๐ŸŽฏ Common Patterns

Here are patterns youโ€™ll use daily:

import bisect

# ๐Ÿ—๏ธ Pattern 1: Using insort for direct insertion
numbers = [1, 3, 5, 7, 9]
bisect.insort(numbers, 4)  # ๐ŸŽฏ Inserts 4 at the right position
print(numbers)  # [1, 3, 4, 5, 7, 9]

# ๐ŸŽจ Pattern 2: Finding insertion points
grades = [60, 70, 80, 90]
student_grade = 75

# ๐Ÿ” bisect_left vs bisect_right
left_pos = bisect.bisect_left(grades, student_grade)   # Returns 2
right_pos = bisect.bisect_right(grades, student_grade)  # Returns 2

# ๐Ÿ”„ Pattern 3: Searching in sorted lists
def grade_rank(score, breakpoints=[60, 70, 80, 90]):
    # ๐Ÿ“Š Returns grade level based on score
    grades = ['F', 'D', 'C', 'B', 'A']
    i = bisect.bisect(breakpoints, score)
    return grades[i]

print(f"Score 85 gets grade: {grade_rank(85)} ๐ŸŽ“")  # Grade: B

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: Smart Inventory System

Letโ€™s build something real:

import bisect
from dataclasses import dataclass
from typing import List

# ๐Ÿ›๏ธ Define our product
@dataclass
class Product:
    name: str
    price: float
    emoji: str
    
    def __lt__(self, other):
        # ๐Ÿ’ฐ Sort by price
        return self.price < other.price

class SmartInventory:
    def __init__(self):
        self.products: List[Product] = []
    
    # โž• Add product maintaining price order
    def add_product(self, product: Product):
        bisect.insort(self.products, product)
        print(f"Added {product.emoji} {product.name} at ${product.price}!")
    
    # ๐Ÿ” Find products in price range
    def find_in_range(self, min_price: float, max_price: float):
        # ๐Ÿ“ Find start and end positions
        start = bisect.bisect_left(self.products, 
                                  Product("", min_price, ""))
        end = bisect.bisect_right(self.products, 
                                 Product("", max_price, ""))
        
        print(f"\n๐Ÿ›’ Products between ${min_price}-${max_price}:")
        for product in self.products[start:end]:
            print(f"  {product.emoji} {product.name}: ${product.price}")
    
    # ๐Ÿ“‹ List all products
    def show_inventory(self):
        print("\n๐Ÿ“ฆ Current Inventory (sorted by price):")
        for p in self.products:
            print(f"  {p.emoji} {p.name}: ${p.price}")

# ๐ŸŽฎ Let's use it!
inventory = SmartInventory()
inventory.add_product(Product("Laptop", 999.99, "๐Ÿ’ป"))
inventory.add_product(Product("Mouse", 29.99, "๐Ÿ–ฑ๏ธ"))
inventory.add_product(Product("Keyboard", 79.99, "โŒจ๏ธ"))
inventory.add_product(Product("Monitor", 299.99, "๐Ÿ–ฅ๏ธ"))

inventory.show_inventory()
inventory.find_in_range(50, 300)  # Find mid-range products

๐ŸŽฏ Try it yourself: Add a method to find the cheapest n products or products nearest to a target price!

๐ŸŽฎ Example 2: Game Leaderboard System

Letโ€™s make it fun:

import bisect
from datetime import datetime
from typing import List, Tuple

# ๐Ÿ† High score entry
class ScoreEntry:
    def __init__(self, player: str, score: int, emoji: str = "๐ŸŽฎ"):
        self.player = player
        self.score = score
        self.emoji = emoji
        self.timestamp = datetime.now()
    
    def __lt__(self, other):
        # ๐Ÿ“ˆ Higher scores come first (reverse order)
        return self.score > other.score
    
    def __repr__(self):
        return f"{self.emoji} {self.player}: {self.score}"

class GameLeaderboard:
    def __init__(self, max_entries: int = 10):
        self.scores: List[ScoreEntry] = []
        self.max_entries = max_entries
    
    # ๐ŸŽฏ Add new score
    def add_score(self, player: str, score: int):
        entry = ScoreEntry(player, score)
        
        # ๐Ÿ… Find position for new score
        position = bisect.bisect_left(self.scores, entry)
        
        # โœจ Check if score makes the leaderboard
        if position < self.max_entries:
            self.scores.insert(position, entry)
            print(f"๐ŸŽ‰ {player} earned {score} points! Rank: #{position + 1}")
            
            # ๐Ÿ”„ Keep only top scores
            if len(self.scores) > self.max_entries:
                removed = self.scores.pop()
                print(f"๐Ÿ˜ข {removed.player} dropped off the leaderboard")
        else:
            print(f"๐Ÿ’ช {player} scored {score}, but didn't make top {self.max_entries}")
    
    # ๐Ÿ“Š Display leaderboard
    def show_leaderboard(self):
        print("\n๐Ÿ† LEADERBOARD ๐Ÿ†")
        print("=" * 30)
        for i, entry in enumerate(self.scores):
            medal = ["๐Ÿฅ‡", "๐Ÿฅˆ", "๐Ÿฅ‰"][i] if i < 3 else f"#{i+1}"
            print(f"{medal} {entry}")
    
    # ๐Ÿ” Check if score would make leaderboard
    def would_make_leaderboard(self, score: int) -> Tuple[bool, int]:
        if not self.scores or len(self.scores) < self.max_entries:
            return True, len(self.scores) + 1
        
        test_entry = ScoreEntry("test", score)
        position = bisect.bisect_left(self.scores, test_entry)
        
        if position < self.max_entries:
            return True, position + 1
        return False, -1

# ๐ŸŽฎ Game time!
leaderboard = GameLeaderboard(max_entries=5)

# ๐ŸŽฏ Players submit scores
players_scores = [
    ("Alice", 1000, "๐Ÿ‘ธ"),
    ("Bob", 850, "๐Ÿคด"),
    ("Charlie", 1200, "๐Ÿง™"),
    ("Diana", 950, "๐Ÿฆธ"),
    ("Eve", 1100, "๐Ÿฅท"),
    ("Frank", 800, "๐Ÿค–"),
    ("Grace", 1150, "๐Ÿš€")
]

for player, score, emoji in players_scores:
    entry = ScoreEntry(player, score, emoji)
    entry.emoji = emoji
    leaderboard.add_score(player, score)

leaderboard.show_leaderboard()

# ๐Ÿค” Check if a score would make it
score_to_beat = 900
makes_it, rank = leaderboard.would_make_leaderboard(score_to_beat)
if makes_it:
    print(f"\n๐Ÿ’ก A score of {score_to_beat} would rank #{rank}!")

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Custom Key Functions

When youโ€™re ready to level up, try this advanced pattern:

import bisect
from functools import partial

# ๐ŸŽฏ Using key functions (Python 3.10+)
class Event:
    def __init__(self, name: str, timestamp: float, emoji: str):
        self.name = name
        self.timestamp = timestamp
        self.emoji = emoji
    
    def __repr__(self):
        return f"{self.emoji} {self.name} @ {self.timestamp}"

# ๐Ÿช„ Custom comparison for bisect
events = [
    Event("Login", 100.0, "๐Ÿ”‘"),
    Event("Purchase", 150.0, "๐Ÿ’ณ"),
    Event("Logout", 200.0, "๐Ÿšช")
]

# ๐Ÿ“ Find events after timestamp 125
timestamps = [e.timestamp for e in events]
index = bisect.bisect_left(timestamps, 125.0)
print(f"Events after 125.0: {events[index:]}")

# โœจ Alternative: Using a key function wrapper
class KeyWrapper:
    def __init__(self, iterable, key):
        self.it = iterable
        self.key = key
    
    def __getitem__(self, i):
        return self.key(self.it[i])
    
    def __len__(self):
        return len(self.it)

# ๐ŸŽจ Use the wrapper
new_event = Event("Click", 175.0, "๐Ÿ–ฑ๏ธ")
wrapped = KeyWrapper(events, key=lambda e: e.timestamp)
position = bisect.bisect_left(wrapped, 175.0)
events.insert(position, new_event)
print(f"After insertion: {events}")

๐Ÿ—๏ธ Advanced Topic 2: Efficient Range Queries

For the brave developers:

import bisect
from typing import List, Tuple, Optional

class TimeSeriesData:
    """๐Ÿš€ Efficient time series with range queries"""
    
    def __init__(self):
        self.timestamps: List[float] = []
        self.values: List[float] = []
        self.emojis: List[str] = []
    
    def add_point(self, timestamp: float, value: float, emoji: str = "๐Ÿ“Š"):
        # ๐ŸŽฏ Insert maintaining time order
        idx = bisect.bisect_left(self.timestamps, timestamp)
        self.timestamps.insert(idx, timestamp)
        self.values.insert(idx, value)
        self.emojis.insert(idx, emoji)
    
    def get_range(self, start: float, end: float) -> List[Tuple[float, float, str]]:
        # ๐Ÿ” Efficient range query using bisect
        start_idx = bisect.bisect_left(self.timestamps, start)
        end_idx = bisect.bisect_right(self.timestamps, end)
        
        return list(zip(
            self.timestamps[start_idx:end_idx],
            self.values[start_idx:end_idx],
            self.emojis[start_idx:end_idx]
        ))
    
    def get_nearest(self, target: float) -> Optional[Tuple[float, float, str]]:
        # ๐ŸŽฏ Find nearest timestamp
        if not self.timestamps:
            return None
        
        idx = bisect.bisect_left(self.timestamps, target)
        
        # ๐Ÿ”„ Check neighbors
        candidates = []
        if idx > 0:
            candidates.append(idx - 1)
        if idx < len(self.timestamps):
            candidates.append(idx)
        
        # ๐Ÿ† Find closest
        if candidates:
            closest_idx = min(candidates, 
                            key=lambda i: abs(self.timestamps[i] - target))
            return (self.timestamps[closest_idx], 
                   self.values[closest_idx],
                   self.emojis[closest_idx])
        return None

# ๐ŸŽฎ Usage example
series = TimeSeriesData()

# ๐Ÿ“ˆ Add temperature readings
readings = [
    (8.0, 15.2, "๐ŸŒ…"),   # Morning
    (12.0, 22.5, "โ˜€๏ธ"),  # Noon
    (16.0, 20.1, "๐ŸŒค๏ธ"),  # Afternoon
    (20.0, 16.8, "๐ŸŒ™"),  # Evening
    (10.0, 18.3, "โ›…"),  # Late morning
]

for time, temp, emoji in readings:
    series.add_point(time, temp, emoji)

# ๐Ÿ” Query ranges
print("๐ŸŒก๏ธ Temperatures between 10:00-16:00:")
for time, temp, emoji in series.get_range(10.0, 16.0):
    print(f"  {emoji} {time:.1f}:00 - {temp}ยฐC")

# ๐ŸŽฏ Find nearest reading
nearest = series.get_nearest(14.5)
if nearest:
    time, temp, emoji = nearest
    print(f"\n๐ŸŽฏ Nearest to 14:30 is {emoji} {time:.1f}:00 - {temp}ยฐC")

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Unsorted Lists

# โŒ Wrong way - bisect needs sorted lists!
numbers = [5, 2, 8, 1, 9]  # ๐Ÿ˜ฐ Not sorted!
bisect.insort(numbers, 6)
print(numbers)  # [5, 2, 6, 8, 1, 9] - Wrong position!

# โœ… Correct way - ensure list is sorted first!
numbers = [5, 2, 8, 1, 9]
numbers.sort()  # ๐Ÿ›ก๏ธ Sort first!
bisect.insort(numbers, 6)
print(numbers)  # [1, 2, 5, 6, 8, 9] - Perfect!

๐Ÿคฏ Pitfall 2: bisect_left vs bisect_right

# โŒ Confusion with duplicates
scores = [10, 20, 20, 20, 30]

# ๐Ÿค” Where does 20 go?
left = bisect.bisect_left(scores, 20)   # Returns 1 (before all 20s)
right = bisect.bisect_right(scores, 20)  # Returns 4 (after all 20s)

print(f"bisect_left: {left}, bisect_right: {right}")

# โœ… Choose based on your needs!
# Use bisect_left for: "insert before duplicates"
# Use bisect_right for: "insert after duplicates"

# ๐Ÿ’ก Example: Stable sorting (preserving order)
data = [(20, "first"), (20, "second"), (20, "third")]
values = [item[0] for item in data]

# ๐ŸŽฏ To maintain order, use bisect_right
new_item = (20, "fourth")
pos = bisect.bisect_right(values, new_item[0])
print(f"Insert 'fourth' at position {pos} to maintain order")

๐Ÿ› Pitfall 3: Performance with Large Lists

# โŒ Inefficient - using insert() on large lists
import time

large_list = list(range(0, 1000000, 2))  # Even numbers

# ๐Ÿ˜ฐ Slow insertion
start = time.time()
pos = bisect.bisect_left(large_list, 500001)
large_list.insert(pos, 500001)  # O(n) operation!
print(f"Insert took: {time.time() - start:.4f} seconds")

# โœ… Better approach - use deque for frequent insertions
from collections import deque

# ๐Ÿš€ Or consider alternative data structures
# - heapq for priority queues
# - SortedList from sortedcontainers
# - Binary search trees for complex operations

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Always Verify Sorting: Check that your list is sorted before using bisect
  2. ๐Ÿ“ Choose the Right Function: bisect_left for unique insertions, bisect_right for stability
  3. ๐Ÿ›ก๏ธ Handle Edge Cases: Empty lists, single elements, duplicates
  4. ๐ŸŽจ Use Type Hints: Make your code self-documenting
  5. โœจ Consider Alternatives: For frequent modifications, consider sortedcontainers

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Movie Recommendation System

Create a movie recommendation system using bisect:

๐Ÿ“‹ Requirements:

  • โœ… Movies with ratings (1-10) maintained in sorted order
  • ๐Ÿท๏ธ Find movies within a rating range
  • ๐Ÿ‘ค Track user watch history with timestamps
  • ๐Ÿ“… Find movies watched in a time period
  • ๐ŸŽจ Each movie needs a genre emoji!

๐Ÿš€ Bonus Points:

  • Add personalized recommendations based on rating patterns
  • Implement โ€œsimilar moviesโ€ feature
  • Create a โ€œtrending nowโ€ list with time decay

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
import bisect
from datetime import datetime, timedelta
from dataclasses import dataclass
from typing import List, Tuple, Optional
import random

# ๐ŸŽฌ Movie recommendation system!
@dataclass
class Movie:
    title: str
    rating: float
    genre: str
    emoji: str
    
    def __lt__(self, other):
        return self.rating < other.rating

@dataclass 
class WatchEntry:
    movie_title: str
    timestamp: datetime
    user_rating: float
    
    def __lt__(self, other):
        return self.timestamp < other.timestamp

class MovieRecommender:
    def __init__(self):
        self.movies: List[Movie] = []
        self.watch_history: List[WatchEntry] = []
        self.genre_emojis = {
            "Action": "๐Ÿ’ฅ", "Comedy": "๐Ÿ˜‚", "Drama": "๐ŸŽญ",
            "Horror": "๐Ÿ‘ป", "Sci-Fi": "๐Ÿš€", "Romance": "๐Ÿ’•"
        }
    
    # โž• Add movie to catalog
    def add_movie(self, title: str, rating: float, genre: str):
        emoji = self.genre_emojis.get(genre, "๐ŸŽฌ")
        movie = Movie(title, rating, genre, emoji)
        bisect.insort(self.movies, movie)
        print(f"โœ… Added: {emoji} {title} (โ˜…{rating})")
    
    # ๐Ÿ” Find movies in rating range
    def find_by_rating(self, min_rating: float, max_rating: float) -> List[Movie]:
        start = bisect.bisect_left(self.movies, Movie("", min_rating, "", ""))
        end = bisect.bisect_right(self.movies, Movie("", max_rating, "", ""))
        return self.movies[start:end]
    
    # ๐Ÿ‘ค Add to watch history
    def watch_movie(self, title: str, user_rating: float):
        entry = WatchEntry(title, datetime.now(), user_rating)
        bisect.insort(self.watch_history, entry)
        print(f"๐Ÿ‘๏ธ Watched: {title} - You rated it โ˜…{user_rating}")
    
    # ๐Ÿ“… Get movies watched in time period
    def get_watch_history(self, hours_ago: int) -> List[WatchEntry]:
        cutoff = datetime.now() - timedelta(hours=hours_ago)
        dummy = WatchEntry("", cutoff, 0)
        start = bisect.bisect_left(self.watch_history, dummy)
        return self.watch_history[start:]
    
    # ๐ŸŽฏ Get personalized recommendations
    def get_recommendations(self, count: int = 5) -> List[Movie]:
        if not self.watch_history:
            # ๐ŸŒŸ New user - recommend top rated
            return self.movies[-count:][::-1]
        
        # ๐Ÿ“Š Calculate average user rating
        avg_rating = sum(w.user_rating for w in self.watch_history) / len(self.watch_history)
        
        # ๐ŸŽฏ Find movies near user's average preference
        target = avg_rating + 0.5  # Slightly above their average
        recommendations = []
        
        # ๐Ÿ” Search around target rating
        idx = bisect.bisect_left(self.movies, Movie("", target, "", ""))
        
        # ๐Ÿ“ˆ Get movies from both directions
        left, right = idx - 1, idx
        while len(recommendations) < count and (left >= 0 or right < len(self.movies)):
            if right < len(self.movies):
                movie = self.movies[right]
                if movie.title not in [w.movie_title for w in self.watch_history]:
                    recommendations.append(movie)
                right += 1
            
            if left >= 0 and len(recommendations) < count:
                movie = self.movies[left]
                if movie.title not in [w.movie_title for w in self.watch_history]:
                    recommendations.append(movie)
                left -= 1
        
        return recommendations
    
    # ๐Ÿ“Š Show stats
    def show_stats(self):
        print("\n๐Ÿ“Š Movie Stats:")
        print(f"  ๐ŸŽฌ Total movies: {len(self.movies)}")
        print(f"  ๐Ÿ‘๏ธ Movies watched: {len(self.watch_history)}")
        if self.movies:
            avg = sum(m.rating for m in self.movies) / len(self.movies)
            print(f"  โญ Average rating: {avg:.1f}")

# ๐ŸŽฎ Test it out!
recommender = MovieRecommender()

# ๐ŸŽฌ Add movies
movies_data = [
    ("The Matrix", 8.7, "Sci-Fi"),
    ("Inception", 8.8, "Sci-Fi"),
    ("The Godfather", 9.2, "Drama"),
    ("Pulp Fiction", 8.9, "Drama"),
    ("The Dark Knight", 9.0, "Action"),
    ("Forrest Gump", 8.8, "Drama"),
    ("Interstellar", 8.6, "Sci-Fi"),
    ("The Shawshank Redemption", 9.3, "Drama"),
    ("Fight Club", 8.8, "Drama"),
    ("Avengers: Endgame", 8.4, "Action")
]

for title, rating, genre in movies_data:
    recommender.add_movie(title, rating, genre)

# ๐Ÿ‘ค Simulate watching
recommender.watch_movie("The Matrix", 9.0)
recommender.watch_movie("Inception", 8.5)

# ๐ŸŽฏ Get recommendations
print("\n๐ŸŽฏ Recommended for you:")
for movie in recommender.get_recommendations(3):
    print(f"  {movie.emoji} {movie.title} (โ˜…{movie.rating})")

# ๐Ÿ” Find highly rated movies
print("\n๐ŸŒŸ Highly rated movies (8.8+):")
for movie in recommender.find_by_rating(8.8, 10.0):
    print(f"  {movie.emoji} {movie.title} (โ˜…{movie.rating})")

recommender.show_stats()

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much! Hereโ€™s what you can now do:

  • โœ… Use bisect to maintain sorted lists efficiently ๐Ÿ’ช
  • โœ… Choose between bisect_left and bisect_right appropriately ๐Ÿ›ก๏ธ
  • โœ… Build efficient search and insertion algorithms ๐ŸŽฏ
  • โœ… Implement real-world systems with sorted data ๐Ÿ›
  • โœ… Optimize performance for large datasets! ๐Ÿš€

Remember: bisect is your friend when working with sorted data! Itโ€™s fast, efficient, and makes your code cleaner. ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered the bisect module!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the movie recommendation exercise
  2. ๐Ÿ—๏ธ Use bisect in your next project that needs sorted data
  3. ๐Ÿ“š Explore the heapq module for priority queues
  4. ๐ŸŒŸ Check out sortedcontainers for more advanced sorted collections

Remember: Every Python expert started where you are now. Keep coding, keep learning, and most importantly, have fun with sorted data! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿš€โœจ