An implementation of a multiset data structure with both mutable and immutable variants supporting all standard set operations.
—
Mutating operations available exclusively on Multiset instances for modifying multiset contents. These operations modify the multiset in-place and are not available on FrozenMultiset instances.
Add and remove individual elements with precise multiplicity control.
def add(self, element, multiplicity: int = 1) -> None:
"""
Add an element to the multiset with specified multiplicity.
Parameters:
- element: Element to add (must be hashable)
- multiplicity: Number of times to add element (default: 1)
Raises:
ValueError: If multiplicity is negative
"""
def remove(self, element, multiplicity: Optional[int] = None) -> int:
"""
Remove element from multiset and return removed multiplicity.
Parameters:
- element: Element to remove
- multiplicity: Number to remove (default: remove all)
Returns:
Number of elements actually removed
Raises:
KeyError: If element not present in multiset
"""
def discard(self, element, multiplicity: Optional[int] = None) -> int:
"""
Remove element from multiset without raising error if not present.
Parameters:
- element: Element to remove
- multiplicity: Number to remove (default: remove all)
Returns:
Number of elements actually removed (0 if not present)
"""
def pop(self, element, default: int) -> int:
"""
Remove element and return its multiplicity.
Parameters:
- element: Element to remove
- default: Value to return if element not present
Returns:
Multiplicity of removed element or default value
Raises:
KeyError: If element not present and no default provided
"""
def clear(self) -> None:
"""Remove all elements from the multiset."""Usage Examples:
from multiset import Multiset
ms = Multiset('aab') # {'a': 2, 'b': 1}
# Add elements
ms.add('c') # Add 'c' once: {'a': 2, 'b': 1, 'c': 1}
ms.add('a', 3) # Add 'a' three times: {'a': 5, 'b': 1, 'c': 1}
# Remove elements
removed = ms.remove('a', 2) # Remove 2 'a's, returns 2: {'a': 3, 'b': 1, 'c': 1}
removed = ms.discard('z', 1) # Try to remove 'z', returns 0: unchanged
removed = ms.remove('b') # Remove all 'b's, returns 1: {'a': 3, 'c': 1}
# Pop elements
count = ms.pop('c') # Remove and return count of 'c': 1
count = ms.pop('z', 0) # Pop 'z' with provided default: returns 0
# Clear all
ms.clear() # Empty multiset: {}Set element multiplicities directly using dictionary-like assignment.
def __setitem__(self, element, multiplicity: int) -> None:
"""
Set the multiplicity of an element directly.
Parameters:
- element: Element to set multiplicity for
- multiplicity: New multiplicity (removes element if 0)
Raises:
ValueError: If multiplicity is negative
"""
def __delitem__(self, element) -> None:
"""
Remove element completely from multiset.
Parameters:
- element: Element to remove
Raises:
KeyError: If element not present
"""
def setdefault(self, element, default: int = 1) -> int:
"""
Get multiplicity of element, setting to default if not present.
Parameters:
- element: Element to get or set
- default: Multiplicity to set if element not present
Returns:
Current or newly set multiplicity
"""Usage Examples:
ms = Multiset('ab') # {'a': 1, 'b': 1}
# Direct assignment
ms['c'] = 3 # Set 'c' to multiplicity 3: {'a': 1, 'b': 1, 'c': 3}
ms['a'] = 5 # Change 'a' to multiplicity 5: {'a': 5, 'b': 1, 'c': 3}
ms['b'] = 0 # Remove 'b' by setting to 0: {'a': 5, 'c': 3}
# Delete elements
del ms['c'] # Remove 'c' completely: {'a': 5}
# Set with default
count = ms.setdefault('x', 2) # Set 'x' to 2, returns 2: {'a': 5, 'x': 2}
count = ms.setdefault('a', 10) # 'a' exists, returns 5: unchangedUpdate multiset contents from other collections with various combination strategies.
def update(self, *others, **kwargs) -> None:
"""
Add elements from others to this multiset (sum multiplicities).
Parameters:
- *others: Iterables or mappings to add elements from
- **kwargs: Additional element-multiplicity pairs
"""
def union_update(self, *others) -> None:
"""
Update multiset with union of this multiset and others.
Takes maximum multiplicity for each element.
Parameters:
- *others: Iterables or mappings to union with
"""
def intersection_update(self, *others) -> None:
"""
Update multiset with intersection of this multiset and others.
Keeps only common elements with minimum multiplicities.
Parameters:
- *others: Iterables or mappings to intersect with
"""
def difference_update(self, *others) -> None:
"""
Remove elements of others from this multiset.
Parameters:
- *others: Iterables or mappings to subtract
"""
def symmetric_difference_update(self, other) -> None:
"""
Update multiset with symmetric difference.
Parameters:
- other: Iterable or mapping to compute symmetric difference with
"""
def times_update(self, factor: int) -> None:
"""
Scale all multiplicities by factor in-place.
Parameters:
- factor: Scaling factor (must be non-negative)
Raises:
ValueError: If factor is negative
"""Usage Examples:
ms = Multiset('aab') # {'a': 2, 'b': 1}
# Update (combine/add multiplicities)
ms.update('abc') # Add from string: {'a': 3, 'b': 2, 'c': 1}
ms.update({'x': 2, 'y': 1}) # Add from mapping: {'a': 3, 'b': 2, 'c': 1, 'x': 2, 'y': 1}
# Union update (maximum multiplicities)
ms2 = Multiset('aaax') # {'a': 3, 'x': 1}
ms.union_update(ms2) # Take max: {'a': 3, 'b': 2, 'c': 1, 'x': 2, 'y': 1}
# Intersection update (minimum multiplicities, common elements only)
ms.intersection_update('axyz') # Keep common: {'a': 1, 'x': 1, 'y': 1}
# Difference update (subtract)
ms.difference_update('ay') # Remove: {'x': 1}
# Times update (scale)
ms.times_update(3) # Scale by 3: {'x': 3}Convenient operators for in-place multiset operations.
def __ior__(self, other) -> Multiset:
"""In-place union using |= operator."""
def __iand__(self, other) -> Multiset:
"""In-place intersection using &= operator."""
def __isub__(self, other) -> Multiset:
"""In-place difference using -= operator."""
def __ixor__(self, other) -> Multiset:
"""In-place symmetric difference using ^= operator."""
def __imul__(self, factor: int) -> Multiset:
"""In-place scaling using *= operator."""Usage Examples:
ms1 = Multiset('aab') # {'a': 2, 'b': 1}
ms2 = Multiset('abc') # {'a': 1, 'b': 1, 'c': 1}
# In-place operations using operators
ms1 |= ms2 # Union: {'a': 2, 'b': 1, 'c': 1}
ms1 &= 'ab' # Intersection: {'a': 1, 'b': 1}
ms1 -= 'a' # Difference: {'b': 1}
ms1 *= 4 # Scale: {'b': 4}
# Note: += uses update() method for combination
ms1 += 'bb' # Combine: {'b': 6}Features that distinguish mutable multisets from their immutable counterparts.
Mutability Characteristics:
# Mutable multisets can be modified after creation
ms = Multiset('abc')
ms.add('d') # Allowed on Multiset
ms['e'] = 2 # Allowed on Multiset
# Immutable multisets cannot be modified
frozen_ms = FrozenMultiset('abc')
# frozen_ms.add('d') # AttributeError: not available
# frozen_ms['e'] = 2 # TypeError: not supported
# Mutable multisets are not hashable
# hash(ms) # TypeError: unhashable type
hash(frozen_ms) # Works: returns hash value
# Use in sets/dicts
my_set = {frozen_ms} # Allowed
# my_set = {ms} # TypeError: unhashable typeThread Safety Considerations:
# Mutable multisets are not thread-safe
# Concurrent modifications require external synchronization
import threading
ms = Multiset()
lock = threading.Lock()
def safe_add(element):
with lock:
ms.add(element)
# Use FrozenMultiset for read-only sharing between threads
shared_ms = FrozenMultiset('abc') # Safe to sharePerformance Notes:
# Bulk operations are more efficient than individual operations
ms = Multiset()
# Efficient: single bulk operation
ms.update('abcdefg' * 1000)
# Less efficient: many individual operations
# for char in 'abcdefg' * 1000:
# ms.add(char)
# In-place operations modify existing multiset
ms = Multiset('aaa')
original_id = id(ms)
ms += 'bbb' # Modifies existing multiset
assert id(ms) == original_id # Same object
# Regular operations create new multisets
result = ms + 'ccc' # Creates new multiset
assert id(result) != id(ms) # Different objectsInstall with Tessl CLI
npx tessl i tessl/pypi-multiset