Extra Python Collections - bags (multisets) and setlists (ordered sets)
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
RangeMaps provide efficient mappings from continuous ranges to values using interval-based storage. They support slice notation, range operations, and are ideal for time series data, version ranges, and any scenario requiring interval-to-value mappings.
Create range mappings from various input formats with optional default values.
class RangeMap:
def __init__(self, iterable=None, default_value=NOT_SET):
"""Create a RangeMap.
Args:
iterable: Mapping (start -> value) or iterable of (start, stop, value) tuples
default_value: Default value for unmapped ranges
"""
@classmethod
def from_mapping(cls, mapping):
"""Create RangeMap from mapping of range starts to values.
Args:
mapping: Dict mapping start points to values
Returns:
RangeMap: New range map
"""
@classmethod
def from_iterable(cls, iterable):
"""Create RangeMap from iterable of (start, stop, value) tuples.
Args:
iterable: Iterable of (start, stop, value) tuples
Returns:
RangeMap: New range map
"""Usage examples:
from collections_extended import RangeMap
from datetime import date
# Empty range map
rm = RangeMap()
# With default value for unmapped ranges
rm_default = RangeMap(default_value='unknown')
# From mapping of start points
starts = {0: 'low', 10: 'medium', 20: 'high'}
rm_starts = RangeMap.from_mapping(starts)
# From tuples of (start, stop, value)
ranges = [(0, 5, 'first'), (5, 10, 'second'), (10, 15, 'third')]
rm_ranges = RangeMap.from_iterable(ranges)Set and retrieve values for specific ranges using slice notation or method calls.
def set(self, value, start=None, stop=None):
"""Set range from start to stop to value.
Args:
value: Value to assign to the range
start: Start of range (inclusive, None for unbounded left)
stop: End of range (exclusive, None for unbounded right)
"""
def get(self, key, restval=None):
"""Get value for a specific key.
Args:
key: Point to look up
restval: Value to return if key not mapped
Returns:
Any: Value at key or restval if unmapped
"""
def __getitem__(self, key):
"""Get value for key or range slice.
Args:
key: Point to look up or slice for sub-range
Returns:
Any: Value at key or RangeMap for slice
Raises:
KeyError: If key not mapped and no default value
"""
def __setitem__(self, key, value):
"""Set range using slice notation.
Args:
key: slice object defining the range
value: Value to assign to the range
"""Usage examples:
rm = RangeMap()
# Set ranges using method calls
rm.set('morning', start=6, stop=12)
rm.set('afternoon', start=12, stop=18)
rm.set('evening', start=18, stop=22)
# Set ranges using slice notation
rm[0:6] = 'night'
rm[22:24] = 'late_night'
# Access specific points
print(rm[8]) # 'morning'
print(rm[15]) # 'afternoon'
print(rm.get(25, 'unmapped')) # 'unmapped'
# Access sub-ranges
sub_range = rm[10:20] # RangeMap with overlapping rangesQuery range information and iterate over mapped intervals.
def ranges(self, start=None, stop=None):
"""Generate MappedRange objects for ranges in the specified interval.
Args:
start: Start of query interval (None for unbounded)
stop: End of query interval (None for unbounded)
Yields:
MappedRange: Range objects with start, stop, and value
"""
def get_range(self, start=None, stop=None):
"""Return a RangeMap covering the specified range.
Args:
start: Start of range to extract
stop: End of range to extract
Returns:
RangeMap: New RangeMap covering the specified range
"""
def __contains__(self, key):
"""Check if key is mapped to a value.
Args:
key: Point to check
Returns:
bool: True if key is mapped
"""
def __iter__(self):
"""Iterate over range start points that have mappings."""
def __len__(self):
"""Return number of mapped ranges.
Returns:
int: Count of ranges with values
"""
def __bool__(self):
"""Return True if any ranges are mapped.
Returns:
bool: True if RangeMap contains mappings
"""Usage examples:
rm = RangeMap()
rm[0:10] = 'first'
rm[10:20] = 'second'
rm[30:40] = 'third'
# Iterate over all ranges
for range_obj in rm.ranges():
print(f"[{range_obj.start}:{range_obj.stop}) -> {range_obj.value}")
# Query specific interval
for range_obj in rm.ranges(5, 35):
print(f"Overlaps query: [{range_obj.start}:{range_obj.stop}) -> {range_obj.value}")
# Extract sub-range
partial = rm.get_range(5, 25) # Includes parts of 'first' and 'second'
# Basic queries
print(5 in rm) # True
print(25 in rm) # False
print(len(rm)) # 3
print(bool(rm)) # TrueModify, delete, and clear ranges efficiently.
def delete(self, start=None, stop=None):
"""Delete the specified range.
Args:
start: Start of range to delete (None for unbounded)
stop: End of range to delete (None for unbounded)
Raises:
KeyError: If any part of the range is not mapped
"""
def empty(self, start=None, stop=None):
"""Empty the specified range (like delete but doesn't raise errors).
Args:
start: Start of range to empty (None for unbounded)
stop: End of range to empty (None for unbounded)
"""
def clear(self):
"""Remove all mappings from the RangeMap."""
def __delitem__(self, key):
"""Delete range using slice notation.
Args:
key: slice object defining range to delete
"""Usage examples:
rm = RangeMap()
rm[0:50] = 'full_range'
rm[10:20] = 'middle'
rm[30:40] = 'end'
# Delete specific range
del rm[15:25] # Removes part of 'middle' and gap
# Empty range (no error if unmapped)
rm.empty(45, 60) # Removes part of range, no error for unmapped part
# Delete with error checking
try:
rm.delete(100, 110) # KeyError - range not mapped
except KeyError:
print("Range not fully mapped")
# Clear everything
rm.clear()
print(len(rm)) # 0Access information about range boundaries and extents.
@property
def start(self):
"""Get the start of the first mapped range.
Returns:
Any: Start point of first range or None if empty/unbounded
"""
@property
def end(self):
"""Get the end of the last mapped range.
Returns:
Any: End point of last range or None if empty/unbounded
"""Usage examples:
rm = RangeMap()
rm[10:20] = 'first'
rm[30:40] = 'second'
print(rm.start) # 10
print(rm.end) # 40
# Unbounded ranges
rm[:5] = 'prefix' # Unbounded left
rm[50:] = 'suffix' # Unbounded right
print(rm.start) # None (unbounded left)
print(rm.end) # None (unbounded right)Represent individual range mappings with start, stop, and value information.
class MappedRange:
def __init__(self, start, stop, value):
"""Create a mapped range.
Args:
start: Range start (inclusive)
stop: Range end (exclusive)
value: Associated value
"""
start: Any # Range start point (inclusive)
stop: Any # Range end point (exclusive)
value: Any # Associated value
def __iter__(self):
"""Allow unpacking: start, stop, value = mapped_range"""
def __eq__(self, other):
"""Check equality with another MappedRange."""
def __str__(self):
"""String representation: [start, stop) -> value"""
def __repr__(self):
"""Detailed representation for debugging."""Usage examples:
from collections_extended import MappedRange
# Create manually
mr = MappedRange(0, 10, 'first_range')
print(mr) # [0, 10) -> first_range
# Unpack from ranges() iteration
rm = RangeMap()
rm[0:10] = 'test'
for start, stop, value in rm.ranges():
print(f"Range from {start} to {stop}: {value}")
# Access attributes
for range_obj in rm.ranges():
print(f"Start: {range_obj.start}")
print(f"Stop: {range_obj.stop}")
print(f"Value: {range_obj.value}")Access keys, values, and items through view objects.
def keys(self):
"""Return view of range start keys.
Returns:
RangeMapKeysView: View iterating over range start points
"""
def values(self):
"""Return view of range values.
Returns:
RangeMapValuesView: View iterating over unique values
"""
def items(self):
"""Return view of (start, value) pairs.
Returns:
RangeMapItemsView: View iterating over start-value pairs
"""Usage examples:
rm = RangeMap()
rm[0:10] = 'first'
rm[10:20] = 'second'
rm[20:30] = 'first' # Same value as first range
print(list(rm.keys())) # [0, 10, 20]
print(list(rm.values())) # ['first', 'second'] - unique values only
print(list(rm.items())) # [(0, 'first'), (10, 'second'), (20, 'first')]
# Views support membership testing
print(15 in rm.keys()) # False (15 is not a start point)
print('first' in rm.values()) # True
print((10, 'second') in rm.items()) # TrueCommon patterns for effective RangeMap usage.
from datetime import date, timedelta
# Version ranges for software releases
versions = RangeMap()
versions[date(2020, 1, 1):date(2020, 6, 1)] = '1.0'
versions[date(2020, 6, 1):date(2021, 1, 1)] = '1.1'
versions[date(2021, 1, 1):] = '2.0'
current_version = versions[date.today()]
# Price tiers based on quantity
pricing = RangeMap(default_value=0.50) # Default unit price
pricing[100:500] = 0.45 # Volume discount
pricing[500:1000] = 0.40 # Larger volume discount
pricing[1000:] = 0.35 # Highest volume discount
unit_price = pricing[quantity]
# Time-based scheduling
schedule = RangeMap()
schedule[6:9] = 'morning_shift'
schedule[9:17] = 'day_shift'
schedule[17:22] = 'evening_shift'
schedule[22:6] = 'night_shift' # Wraps around (if using 24-hour periods)
current_shift = schedule.get(current_hour, 'off_duty')
# Overlapping range updates
temp_map = RangeMap()
temp_map[0:100] = 'baseline'
temp_map[20:30] = 'override' # Splits baseline into [0:20) and [30:100)
# Merging adjacent ranges with same value
data = RangeMap()
data[0:10] = 'same'
data[10:20] = 'same' # Automatically merged into [0:20) = 'same'Install with Tessl CLI
npx tessl i tessl/pypi-collections-extended