Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a standard way to partition an interable into equivalence classes given a relation in python?

Say I have a finite iterable X and an equivalence relation ~ on X. We can define a function my_relation(x1, x2) that returns True if x1~x2 and returns False otherwise. I want to write a function that partitions X into equivalence classes. That is, my_function(X, my_relation) should return a list of the equivalence classes of ~.

Is there a standard way to do this in python? Better yet, is there a module designed to deal with equivalence relations?

like image 617
Brian Fitzpatrick Avatar asked Aug 12 '16 18:08

Brian Fitzpatrick


People also ask

What is equivalence partitioning technique?

Equivalence partitioning or equivalence class partitioning (ECP) is a software testing technique that divides the input data of a software unit into partitions of equivalent data from which test cases can be derived. In principle, test cases are designed to cover each partition at least once.

Is an equivalence class the same as a partition?

Equivalence Classes form a partition (idea of Theorem 6.3. 3) The overall idea in this section is that given an equivalence relation on set A, the collection of equivalence classes forms a partition of set A, (Theorem 6.3. 3).

Are all partitions equivalence relations?

Any partition P has a corresponding equivalence relation. Specifically, we define x ∼ y if and only if x and y are in the same element of P. This relation is obviously reflexive, symmetric, and transitive.


2 Answers

I found this Python recipe by John Reid. It was written in Python 2 and I adapted it to Python 3 to test it. The recipe includes a test to partition the set of integers [-3,5) into equivalence classes based on the relation lambda x, y: (x - y) % 4 == 0.

It seems to do what you want. Here's the adapted version I made in case you want it in Python 3:

def equivalence_partition(iterable, relation):
    """Partitions a set of objects into equivalence classes

    Args:
        iterable: collection of objects to be partitioned
        relation: equivalence relation. I.e. relation(o1,o2) evaluates to True
            if and only if o1 and o2 are equivalent

    Returns: classes, partitions
        classes: A sequence of sets. Each one is an equivalence class
        partitions: A dictionary mapping objects to equivalence classes
    """
    classes = []
    partitions = {}
    for o in iterable:  # for each object
        # find the class it is in
        found = False
        for c in classes:
            if relation(next(iter(c)), o):  # is it equivalent to this class?
                c.add(o)
                partitions[o] = c
                found = True
                break
        if not found:  # it is in a new class
            classes.append(set([o]))
            partitions[o] = classes[-1]
    return classes, partitions


def equivalence_enumeration(iterable, relation):
    """Partitions a set of objects into equivalence classes

    Same as equivalence_partition() but also numbers the classes.

    Args:
        iterable: collection of objects to be partitioned
        relation: equivalence relation. I.e. relation(o1,o2) evaluates to True
            if and only if o1 and o2 are equivalent

    Returns: classes, partitions, ids
        classes: A sequence of sets. Each one is an equivalence class
        partitions: A dictionary mapping objects to equivalence classes
        ids: A dictionary mapping objects to the indices of their equivalence classes
    """
    classes, partitions = equivalence_partition(iterable, relation)
    ids = {}
    for i, c in enumerate(classes):
        for o in c:
            ids[o] = i
    return classes, partitions, ids


def check_equivalence_partition(classes, partitions, relation):
    """Checks that a partition is consistent under the relationship"""
    for o, c in partitions.items():
        for _c in classes:
            assert (o in _c) ^ (not _c is c)
    for c1 in classes:
        for o1 in c1:
            for c2 in classes:
                for o2 in c2:
                    assert (c1 is c2) ^ (not relation(o1, o2))


def test_equivalence_partition():
    relation = lambda x, y: (x - y) % 4 == 0
    classes, partitions = equivalence_partition(
        range(-3, 5),
        relation
    )
    check_equivalence_partition(classes, partitions, relation)
    for c in classes: print(c)
    for o, c in partitions.items(): print(o, ':', c)


if __name__ == '__main__':
    test_equivalence_partition()
like image 51
Tagc Avatar answered Oct 19 '22 21:10

Tagc


The following function takes an iterable a, and an equivalence function equiv, and does what you ask:

def partition(a, equiv):
    partitions = [] # Found partitions
    for e in a: # Loop over each element
        found = False # Note it is not yet part of a know partition
        for p in partitions:
            if equiv(e, p[0]): # Found a partition for it!
                p.append(e)
                found = True
                break
        if not found: # Make a new partition for it.
            partitions.append([e])
    return partitions

Example:

def equiv_(lhs, rhs):
    return lhs % 3 == rhs % 3

a_ = range(10)

>>> partition(a_, equiv_)
[[0, 3, 6, 9], [1, 4, 7], [2, 5, 8]]
like image 33
Ami Tavory Avatar answered Oct 19 '22 22:10

Ami Tavory