Add get_equivalence_class util method

This way, we can partition songs into equivalence classes based on identicalness
This commit is contained in:
Jakob Moser 2025-05-21 09:33:14 +02:00
parent 8988bb820c
commit d3d28777e7
Signed by: jakob
GPG Key ID: 3EF2BA2851B3F53C
2 changed files with 36 additions and 0 deletions

View File

@ -0,0 +1,4 @@
"""
Utility functions completely independent of the root project (i.e., you could copy
them to any other code project and they'd immediately work).
"""

View File

@ -0,0 +1,32 @@
from collections.abc import Iterable, Sequence, Callable
type EquivalenceClass[T] = Sequence[T]
def get_equivalence_classes[T](
items: Iterable[T], is_equivalent: Callable[[T, T], bool]
) -> Sequence[EquivalenceClass[T]]:
"""
Partition an iterable of items into equivalence classes under the given equivalence relation.
The order of `items` is kept within the equivalence classes.
>>> from math import floor
>>> get_equivalence_classes([1.0, 2.1, 1.1, 1.0, 2.2, 2.1, 3.3], lambda a, b: floor(a) == floor(b))
((1.0, 1.1, 1.0), (2.1, 2.2, 2.1), (3.3,))
"""
equivalence_classes: list[list[T]] = []
for item in items:
# Check if the item belongs into an already existing equivalence class
for equivalence_class in equivalence_classes:
# Pick an arbitrary representative of the equivalence class (we are allowed to do this
# because it is an equivalence class)
representative = equivalence_class[0]
if is_equivalent(item, representative):
equivalence_class.append(item)
break
else:
# This item forms a new equivalence class, so we create one containing only the item and append it
equivalence_classes.append([item])
return tuple(tuple(equivalence_class) for equivalence_class in equivalence_classes)