docs for muutils v0.6.17
View Source on GitHub

# muutils.group_equiv

group items by assuming that `eq_func` defines an equivalence relation

View Source
View on GitHub

     1"group items by assuming that `eq_func` defines an equivalence relation"
     2
     3from __future__ import annotations
     4
     5from itertools import chain
     6from typing import Callable, Sequence, TypeVar
     7
     8T = TypeVar("T")
     9
    10
    11def group_by_equivalence(
    12    items_in: Sequence[T],
    13    eq_func: Callable[[T, T], bool],
    14) -> list[list[T]]:
    15    """group items by assuming that `eq_func` implies an equivalence relation but might not be transitive
    16
    17    so, if f(a,b) and f(b,c) then f(a,c) might be false, but we still want to put [a,b,c] in the same class
    18
    19    note that lists are used to avoid the need for hashable items, and to allow for duplicates
    20
    21    # Arguments
    22     - `items_in: Sequence[T]` the items to group
    23     - `eq_func: Callable[[T, T], bool]` a function that returns true if two items are equivalent. need not be transitive
    24    """
    25
    26    items: list[T] = list(items_in)
    27    items.reverse()
    28    output: list[list[T]] = list()
    29
    30    while items:
    31        x: T = items.pop()
    32
    33        # try to add to an existing class
    34        found_classes: list[int] = list()
    35        for i, c in enumerate(output):
    36            if any(eq_func(x, y) for y in c):
    37                found_classes.append(i)
    38
    39        # if one class found, add to it
    40        if len(found_classes) == 1:
    41            output[found_classes.pop()].append(x)
    42
    43        elif len(found_classes) > 1:
    44            # if multiple classes found, merge the classes
    45
    46            # first sort the ones to be merged
    47            output_new: list[list[T]] = list()
    48            to_merge: list[list[T]] = list()
    49            for i, c in enumerate(output):
    50                if i in found_classes:
    51                    to_merge.append(c)
    52                else:
    53                    output_new.append(c)
    54
    55            # then merge them back in, along with the element `x`
    56            merged: list[T] = list(chain.from_iterable(to_merge))
    57            merged.append(x)
    58
    59            output_new.append(merged)
    60            output = output_new
    61
    62        # if no class found, make a new one
    63        else:
    64            output.append([x])
    65
    66    return output

def group_by_equivalence( items_in: Sequence[~T], eq_func: Callable[[~T, ~T], bool]) -> list[list[~T]]:
View Source
View on GitHub

    12def group_by_equivalence(
    13    items_in: Sequence[T],
    14    eq_func: Callable[[T, T], bool],
    15) -> list[list[T]]:
    16    """group items by assuming that `eq_func` implies an equivalence relation but might not be transitive
    17
    18    so, if f(a,b) and f(b,c) then f(a,c) might be false, but we still want to put [a,b,c] in the same class
    19
    20    note that lists are used to avoid the need for hashable items, and to allow for duplicates
    21
    22    # Arguments
    23     - `items_in: Sequence[T]` the items to group
    24     - `eq_func: Callable[[T, T], bool]` a function that returns true if two items are equivalent. need not be transitive
    25    """
    26
    27    items: list[T] = list(items_in)
    28    items.reverse()
    29    output: list[list[T]] = list()
    30
    31    while items:
    32        x: T = items.pop()
    33
    34        # try to add to an existing class
    35        found_classes: list[int] = list()
    36        for i, c in enumerate(output):
    37            if any(eq_func(x, y) for y in c):
    38                found_classes.append(i)
    39
    40        # if one class found, add to it
    41        if len(found_classes) == 1:
    42            output[found_classes.pop()].append(x)
    43
    44        elif len(found_classes) > 1:
    45            # if multiple classes found, merge the classes
    46
    47            # first sort the ones to be merged
    48            output_new: list[list[T]] = list()
    49            to_merge: list[list[T]] = list()
    50            for i, c in enumerate(output):
    51                if i in found_classes:
    52                    to_merge.append(c)
    53                else:
    54                    output_new.append(c)
    55
    56            # then merge them back in, along with the element `x`
    57            merged: list[T] = list(chain.from_iterable(to_merge))
    58            merged.append(x)
    59
    60            output_new.append(merged)
    61            output = output_new
    62
    63        # if no class found, make a new one
    64        else:
    65            output.append([x])
    66
    67    return output

group items by assuming that `eq_func` implies an equivalence relation but might not be transitive

so, if f(a,b) and f(b,c) then f(a,c) might be false, but we still want to put [a,b,c] in the same class

note that lists are used to avoid the need for hashable items, and to allow for duplicates

# Arguments

- `items_in: Sequence[T]` the items to group
- `eq_func: Callable[[T, T], bool]` a function that returns true if two items are equivalent. need not be transitive