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?
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.
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).
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.
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()
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]]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With