docs for muutils v0.8.8
View Source on GitHub

muutils.group_equiv

group items by assuming that eq_func defines an equivalence relation


 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]]:
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