Is it possible to redefine the equal operator for tuples? - python

I have some code in which edges are represented as tuple
(vertex_1, vertex_2)
and I have lists of edges that represent planar embedded faces, as for the example below.
I need to search if an edge is present in the list, but I need to return true both if a use (v1, v2) and (v2, v1):
f1 = [(6, 1), (1, 2), (2, 7), (7, 6)]
(6,1) in f1
(1,6) in f1
True
False

You cannot override the equality method for existing types, so you would have to create your own type which would then require you to replace all your existing tuples with your custom type.
If your main problem is just the (6,1) in f1 use case, then maybe you should just consider creating a method for that instead:
def contains(t, lst):
return (t[0], t[1]) in lst or (t[1], t[0]) in lst
And then you can just use it like this:
>>> f1 = [(6, 1), (1, 2), (2, 7), (7, 6)]
>>> contains((6, 1), f1)
True
>>> contains((1, 6), f1)
True
This essentially has the benefit that you don’t need to replace your tuples by a different type instead. So you can work with all your data sources the way they are.

You should make a tuple subclass and change it's equality method (__eq__):
class UnorderedTuple(tuple):
def __eq__(self, other):
return len(self) == len(other) and set(self) == set(other)
will work for your case with (tuple lengths == 2 if the tuple elements are hashable - that is immutable and have a well defined comparison)
To have your list of tuples converted to a list of Unordered tuples do:
f1 = [UnorderedTuple(f_) for f_ in f1]
To have a proper containment query (the in operator) over a list can be slow - so you'd better have a set than a list:
set_f1 = { UnorderedTuple(f_) for f_ in f1 }
(6,1) in set_f1
(1,6) in set_f1
This implementation will not be very performant, as it creates a new set for each comparison. So if your tuples will always be f two elements, it is more performant to have the __eq__ method unroled like:
def __eq__(self, other):
return super(UnordoredTuple, self).__eq__(other) or (self[0] == other[1] and self[1] == other[0])

"Is it possible to redefine the equal operator for tuples"
Sort of. You can't do it on the basic tuple type, but you can to it on a subclass:
class MyTuple(tuple):
def __eq__(self, other):
orig_eq = super(MyTuple, self).__eq__(other)
if orig_eq and orig_eq is not NotImplemented:
return True
else:
return super(MyTuple, self).__eq__(other[::-1])
Generally, this probably isn't the best approach. Depending on the constraints of the problem, you could try a set of frozenset:
f1_set = {frozenset(tup) for tup in f1}
frozenset((1, 6)) in f1_set
The advantage here is that if you're doing multiple membership tests on the same data, you'll likely get better runtime (Each membership test on the list is O(N) and you need to do up to two for each item you want to check whereas you only have a single O(N) step to build f1_set and then each membership test is O(1) afterward).

like others have posted you can use class to redefine the equal operator for tuples, but still you have to use that class you have to call it, so if you have
class new_tuple:
...
than you have to use:
tuple = (1,6)
tuple = new_tuple(tuple)
i think it's easier to use function to determine if tuple is in list:
def check(tuple_, list_):
v1, v2 = tuple_
if (v1, v2) in list_ or (v2, v1) in list_:
return True
return False
f1 = [(6, 1), (1, 2), (2, 7), (7, 6)]
print(check((6, 1), f1)) # this prints True
print(check((1, 6), f1)) # this prints True

The general solution to this problem is to use multisets, which are sets where an element may appear more than once. The collections module defines a Counter class, which is a subclass of dict, that implements multisets. The dict keys are the elements of the multiset, and the values are the number of times the keys occur.
This avoids limitations on the number of elements in the multiset, and is already available. The main shortcoming is that there is no "frozen", hashable version that I know of.
Examples:
>>> from collections import Counter
>>> Counter((3, 6, 2, 4, 2, 8)) == Counter((8, 4, 3, 6, 2, 2))
True
>>> Counter((3, 6, 2, 4, 2, 8)) == Counter((8, 4, 3, 6, 4, 2))
False
You can use the Counter class directly, which is probably simplest, but if you want to retain the underlying tuple representation, you can use the Counter class to implement a more general version of the tuple subclass that others have proposed:
class MultisetTuple(tuple):
def __eq__(self, other):
return Counter(self) == Counter(other)
Examples:
>>> MultisetTuple((3, 6, 2, 4, 2, 8)) == MultisetTuple((8, 4, 3, 6, 2, 2))
True
>>> MultisetTuple((3, 6, 2, 4, 2, 8)) == MultisetTuple((8, 4, 3, 6, 4, 2))
False

Related

How to do a Selective Cartesian Product of a List of Lists in Python

I am trying to get a list of lists that represent all possible ordered pairs from an existing list of lists.
import itertools
list_of_lists=[[0, 1, 2, 3, 4], [5], [6, 7],[8, 9],[10, 11],[12, 13],[14, 15],[16, 17],[18, 19],[20, 21],[22, 23],[24, 25],[26, 27],[28, 29],[30, 31],[32, 33],[34, 35],[36, 37],[38],[39]]
Ideally, we would just use itertools.product in order to get that list of ordered pairs.
scenarios_list=list(itertools.product(*list_of_lists))
However, if I were to do this for a larger list of lists I would get a memory error and so this solution is not scalable for larger lists of lists where there could be numerous different sets of ordered pairs.
So, is there a way to set up a process where we could iterate through these ordered pairs as they are produced where before appending the list to another list, we could test if the list satisfies a certain criteria (for example testing whether there are a certain number of even numbers, sum of list cannot be equal to the maximum, etc). If the criteria is not satisfied then the ordered pair would not be appended and thus not unnecessarily suck up memory when there are only certain ordered pairs that we care about.
Starting with a recursive base implementation of product:
def product(*lsts):
if not lsts:
yield ()
return
first_lst, *rest = lsts
for element in first_lst:
for rec_p in product(*rest):
p = (element,) + rec_p
yield p
[*product([1, 2], [3, 4, 5])]
# [(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5)]
Now, you could augment that with a condition by which you filter any p not meeting it:
def product(*lsts, condition=None):
if condition is None:
condition = lambda tpl: True
if not lsts:
yield ()
return
first_lst, *rest = lsts
for element in first_lst:
for rec_p in product(*rest, condition=condition):
p = (element,) + rec_p
if condition(p): # stop overproduction right where it happens
yield p
Now you can - for instance - restrict to only even elements:
[*product([1, 2], [3, 4, 5], condition=lambda tpl: not any(x%2 for x in tpl))]
# [(2, 4)]

python program 2 tuples as arguments and returns a sorted tuple containing elements that are found in both tuples

My code :
def commonElements(t1 ,t2):
t1 = sorted(t1)
t2 = sorted(t2)
t3 = set([])
for i in t1:
for j in t2:
if i == j:
t3.add(i)
return t3
print commonElements((1, 2, 3), (2, 5, 1))
For the above code am getting output as:
set([1, 2])
Expected should be:
(1, 2)
common elements are printing in the form of set. How to convert set to tuple??
I would convert the two parameters to Python's set type, and then use the set intersection operator between the two sets. After that, you can cast the result to a tuple as you wanted.
def commonElements(t1 ,t2):
return tuple(set(t1) & set(t2))
Per your example:
>>> commonElements((1, 2, 3), (2, 5, 1))
(1, 2)
In your example, you need to return sorted tuple. I would add to #gsi-frank's answer sorted() method.
def commonElements(t1, t2):
return tuple(sorted(set(t1) & set(t2)))

How to sort list of objects

I am learning python, and I do not know what is the best way to sort a list of objects using many attributes. Now I have this
class Example:
def __init__(self, a,b,c):
self.a = a
self.b = b
self.c = c
List = [Example(3,1,5), Example(2,1,2), Example(2,2,2), Example(1,4,1),Example(1,4,5), Example(1,4,2)]
I do not know how to sort is. Is there any tool in Python to help with this or need to write some custom function?
You need to implement rich comparison methods like __lt__ and __ne__ in your class in order to be able to sort a list of instances of your class. Rather than implementing all six comparisons, though, we can get away with only implementing two of them (__eq__ and one of the inequalities) if we decorate with functools.total_ordering.
If you want a lexicographic sort, so that you first compare on a, and then if tied, compare on b, and if still tied, compare on c, see below:
import functools
#functools.total_ordering
class Example:
def __init__(self, a,b,c):
self.a = a
self.b = b
self.c = c
def __eq__(self, other):
if self.a == other.a and self.b == other.b and self.c == other.c:
return True
else:
return False
def __lt__(self, other):
if self.a < other.a:
return True
elif self.a == other.a and self.b < other.b:
return True
elif self.a == other.a and self.b == other.b and self.c < other.c:
return True
else:
return False
def __repr__(self): # included for readability in an interactive session
return 'Example({}, {}, {})'.format(self.a, self.b, self.c)
Now, we can do the following:
>>> lst = [Example(3,1,5), Example(2,1,2), Example(2,2,2), Example(1,4,1),Example(1,4,5), Example(1,4,2)]
>>> lst
[Example(3, 1, 5), Example(2, 1, 2), Example(2, 2, 2), Example(1, 4, 1), Example(1, 4, 5), Example(1, 4, 2)]
>>> lst.sort()
>>> lst
[Example(1, 4, 1), Example(1, 4, 2), Example(1, 4, 5), Example(2, 1, 2), Example(2, 2, 2), Example(3, 1, 5)]
You can sort by multiple items as follows:
List.sort(key=lambda e: [e.a, e.b, e.c])
# or
List.sort(key=operator.attrgetter('a', 'b', 'c'))
This all depends on what you are planning on sorting by. However, whatever that may be you are probably looking for a lambda function. Say you wanted to sort by the self.a attribute you would write your sort as such
#[Example(3, 1, 5), Example(2, 1, 2), Example(2, 2, 2), Example(1, 4, 1), Example(1, 4, 5), Example(1, 4, 2)]
List.sort(key=lambda x: x.a, reverse=False)
#[Example(1, 4, 1), Example(1, 4, 2), Example(1, 4, 5), Example(2, 1, 2), Example(2, 2, 2), Example(3, 1, 5)]
One way would be, as #senshin already explained, to make the object ordered. That works if Example is ordered inherently and that ordering can be also used e.g. to compare standalone objects. However, if your sorting order may vary, then sorted or list.sort with key argument is what you need, and operator module functions can make it more elegant:
from operator import attrgetter
sorted(alist, key=attrgetter('a')) # sort just by a
sorted(alist, key=attrgetter('c', 'b')) # sort by c then by b

Remove duplicate tuples from a list if they are exactly the same including order of items

I know questions similar to this have been asked many, many times on Stack Overflow, but I need to remove duplicate tuples from a list, but not just if their elements match up, their elements have to be in the same order. In other words, (4,3,5) and (3,4,5) would both be present in the output, while if there were both(3,3,5) and (3,3,5), only one would be in the output.
Specifically, my code is:
import itertools
x = [1,1,1,2,2,2,3,3,3,4,4,5]
y = []
for x in itertools.combinations(x,3):
y.append(x)
print(y)
of which the output is quite lengthy. For example, in the output, there should be both (1,2,1) and (1,1,2). But there should only be one (1,2,2).
set will take care of that:
>>> a = [(1,2,2), (2,2,1), (1,2,2), (4,3,5), (3,3,5), (3,3,5), (3,4,5)]
>>> set(a)
set([(1, 2, 2), (2, 2, 1), (3, 4, 5), (3, 3, 5), (4, 3, 5)])
>>> list(set(a))
[(1, 2, 2), (2, 2, 1), (3, 4, 5), (3, 3, 5), (4, 3, 5)]
>>>
set will remove only exact duplicates.
What you need is unique permutations rather than combinations:
y = list(set(itertools.permutations(x,3)))
That is, (1,2,2) and (2,1,2) will be considered as same combination and only one of them will be returned. They are, however, different permutations. Use set() to remove duplicates.
If afterwards you want to sort elements within each tuple and also have the whole list sorted, you can do:
y = [tuple(sorted(q)) for q in y]
y.sort()
No need to do for loop, combinations gives a generator.
x = [1,1,1,2,2,2,3,3,3,4,4,5]
y = list(set(itertools.combinations(x,3)))
This will probably do what you want, but it's vast overkill. It's a low-level prototype for a generator that may be added to itertools some day. It's low level to ease re-implementing it in C. Where N is the length of the iterable input, it requires worst-case space O(N) and does at most N*(N-1)//2 element comparisons, regardless of how many anagrams are generated. Both of those are optimal ;-)
You'd use it like so:
>>> x = [1,1,1,2,2,2,3,3,3,4,4,5]
>>> for t in anagrams(x, 3):
... print(t)
(1, 1, 1)
(1, 1, 2)
(1, 1, 3)
(1, 1, 4)
(1, 1, 5)
(1, 2, 1)
...
There will be no duplicates in the output. Note: this is Python 3 code. It needs a few changes to run under Python 2.
import operator
class ENode:
def __init__(self, initial_index=None):
self.indices = [initial_index]
self.current = 0
self.prev = self.next = self
def index(self):
"Return current index."
return self.indices[self.current]
def unlink(self):
"Remove self from list."
self.prev.next = self.next
self.next.prev = self.prev
def insert_after(self, x):
"Insert node x after self."
x.prev = self
x.next = self.next
self.next.prev = x
self.next = x
def advance(self):
"""Advance the current index.
If we're already at the end, remove self from list.
.restore() undoes everything .advance() did."""
assert self.current < len(self.indices)
self.current += 1
if self.current == len(self.indices):
self.unlink()
def restore(self):
"Undo what .advance() did."
assert self.current <= len(self.indices)
if self.current == len(self.indices):
self.prev.insert_after(self)
self.current -= 1
def build_equivalence_classes(items, equal):
ehead = ENode()
for i, elt in enumerate(items):
e = ehead.next
while e is not ehead:
if equal(elt, items[e.indices[0]]):
# Add (index of) elt to this equivalence class.
e.indices.append(i)
break
e = e.next
else:
# elt not equal to anything seen so far: append
# new equivalence class.
e = ENode(i)
ehead.prev.insert_after(e)
return ehead
def anagrams(iterable, count=None, equal=operator.__eq__):
def perm(i):
if i:
e = ehead.next
assert e is not ehead
while e is not ehead:
result[count - i] = e.index()
e.advance()
yield from perm(i-1)
e.restore()
e = e.next
else:
yield tuple(items[j] for j in result)
items = tuple(iterable)
if count is None:
count = len(items)
if count > len(items):
return
ehead = build_equivalence_classes(items, equal)
result = [None] * count
yield from perm(count)
You were really close. Just get permutations, not combinations. Order matters in permutations, and it does not in combinations. Thus (1, 2, 2) is a distinct permutation from (2, 2, 1). However (1, 2, 2) is considered a singular combination of one 1 and two 2s. Therefore (2, 2, 1) is not considered a distinct combination from (1, 2, 2).
You can convert your list y to a set so that you remove duplicates...
import itertools
x = [1,1,1,2,2,2,3,3,3,4,4,5]
y = []
for x in itertools.permutations(x,3):
y.append(x)
print(set(y))
And voila, you are done. :)
Using a set should probably work. A set is basically a container that doesn't contain any duplicated elements.
Python also includes a data type for sets. A set is an unordered
collection with no duplicate elements. Basic uses include membership
testing and eliminating duplicate entries. Set objects also support
mathematical operations like union, intersection, difference, and
symmetric difference.
import itertools
x = [1,1,1,2,2,2,3,3,3,4,4,5]
y = set()
for x in itertools.combinations(x,3):
y.add(x)
print(y)

Sorting a dictionary of tuples in Python

I know there's tonnes of questions on python sorting lists/dictionaries already, but I can't seem to find one which helps in my case, and i'm looking for the most efficient solution as I'm going to be sorting a rather large dataset.
My data basically looks like this at the moment:
a = {'a': (1, 2, 3), 'b': (3, 2, 1)}
I'm basically creating a word list in which I store each word along with some stats about it (n, Sigma(x), Sigma(x^2) )
I want to sort it based on a particular stat. So far I've been trying something along the lines of:
b = a.items()
b.sort(key = itemgetter(1), reverse=True)
I'm not sure how to control which index it is sorted based on when its effectively a list of tuples of tuples? I guess I effectively need to nest two itemgetter operations but not really sure how to do this.
If there's a better data structure I should be using instead please let me know. Should I perhaps create a small class/struct and then use a lambda function to access a member of the class?
Many Thanks
Something like this?
>>> a = {'a': (1, 2, 3), 'b': (3, 2, 1)}
>>> b = a.items()
>>> b
[('a', (1, 2, 3)), ('b', (3, 2, 1))]
>>> b.sort(key=lambda x:x[1][2]) # sorting by the third item in the tuple
>>> b
[('b', (3, 2, 1)), ('a', (1, 2, 3))]
Names are easier to work with and remember that indices, so I would go with a class:
class Word(object): # don't need `object` in Python 3
def __init__(self, word):
self.word = word
self.sigma = (some calculation)
self.sigma_sq = (some other calculation)
def __repr__(self):
return "Word(%r)" % self.word
def __str__(self):
return self.word
#property
def sigma(self):
return self._sigma
#sigma.setter # requires python 2.6+
def sigma(self, value):
if not value:
raise ValueError("sigma must be ...")
self._sigma = value
word_list = [Word('python'), Word('totally'), Word('rocks')]
word_list.sort(key=lambda w: w.sigma_sq)

Categories

Resources