Adding Values From Tuples of Same Length - python

In a graphical program I'm writing using pygame I use a tuple representing a coordinate like this: (50, 50).
Sometimes, I call a function which returns another tuple such as (3, -5), which represents the change in coordinate.
What is the best way to add the change value to the coordinate value. It would be nice if I could do something like coordinate += change, but it appears that would simply concatenate the two tuples to something like (50, 50, 3, -5). Rather than adding the 1st value to the 1st value and the 2nd to the 2nd, and returning a resulting tuple.
Until now I've been using this rather tiresome method:
coord = (coord[0] + change[0], coord[1] + change[1])
What is a better, more concise method to add together the values of two tuples of the same length. It seems especially important to know how to do it if the tuples are of an arbitrary length or a particularly long length that would make the previous method even more tiresome.

Well, one way would be
coord = tuple(sum(x) for x in zip(coord, change))
If you are doing a lot of math, you may want to investigate using NumPy, which has much more powerful array support and better performance.

List comprehension is probably more readable, but here's another way:
>>> a = (1,2)
>>> b = (3,4)
>>> tuple(map(sum,zip(a,b)))
(4,6)

As John Y mentions, this is pretty easy using numpy.
import numpy as np
x1 = (0,3)
x2 = (4,2)
tuple(np.add(x1,x2))

This is a work in progress as I am learning Python myself. Can we use classes here, could simplify some operations later. I propose to use a coord class to store the coordinates. It would override add and sub so you could do addition and subtraction by simply using operators + and -. You could get the tuple representation with a function built into it.
Class
class coord(object):
def __init__(self,x,y):
self.x = x
self.y = y
def __add__(self,c):
return coord(self.x + c.x, self.y + c.y)
def __sub__(self,c):
return coord(self.x - c.x, self.y - c.y)
def __eq__(self,c): #compares two coords
return self.x == c.x and self.y == c.y
def t(self): #return a tuple representation.
return (self.x,self.y)
Usage
c1 = coord(4,3) #init coords
c2 = coord(3,4)
c3 = c1 + c2 #summing two coordinates. calls the overload __add__
print c3.t() #prints (7, 7)
c3 = c3 - c1
print c3.t() #prints (3, 4)
print c3 == c2 #prints True
you could improve coord to extend other operators as well (less than, greater than ..).
In this version after doing your calculations you can call the pygame methods expecting tuples by just saying coord.t(). There might be a better way than have a function to return the tuple form though.

To get your "+" and "+=" behaviour you can define your own class and implement the __add__() method. The following is an incomplete sample:
# T.py
class T(object):
def __init__(self, *args):
self._t = args
def __add__(self, other):
return T(*([sum(x) for x in zip(self._t, other._t)]))
def __str__(self):
return str(self._t)
def __repr__(self):
return repr(self._t)
>>> from T import T
>>> a = T(50, 50)
>>> b = T(3, -5)
>>> a
(50, 50)
>>> b
(3, -5)
>>> a+b
(53, 45)
>>> a+=b
>>> a
(53, 45)
>>> a = T(50, 50, 50)
>>> b = T(10, -10, 10)
>>> a+b
(60, 40, 60)
>>> a+b+b
(70, 30, 70)
EDIT: I've found a better way...
Define class T as a subclass of tuple and override the __new__ and __add__ methods. This provides the same interface as class tuple (but with different behaviour for __add__), so instances of class T can be passed to anything that expects a tuple.
class T(tuple):
def __new__(cls, *args):
return tuple.__new__(cls, args)
def __add__(self, other):
return T(*([sum(x) for x in zip(self, other)]))
def __sub__(self, other):
return self.__add__(-i for i in other)
>>> a = T(50, 50)
>>> b = T(3, -5)
>>> a
(50, 50)
>>> b
(3, -5)
>>> a+b
(53, 45)
>>> a+=b
>>> a
(53, 45)
>>> a = T(50, 50, 50)
>>> b = T(10, -10, 10)
>>> a+b
(60, 40, 60)
>>> a+b+b
(70, 30, 70)
>>>
>>> c = a + b
>>> c[0]
60
>>> c[-1]
60
>>> for x in c:
... print x
...
60
40
60

My two cents, hope this helps
>>> coord = (50, 50)
>>> change = (3, -5)
>>> tuple(sum(item) for item in zip(coord, change))
(53, 45)

Related

Summation overloading

I want the summation of 3 different objects like:
`2 3 4
2 3 4
2 3 4
6 9 12`the summation must be like this
And tried to do this
`
class mymath:
def __init__(self,x,y,z):
self.x=x
self.y=y
self.z=z
def __add__(self,other):
return self.x+other.x, self.y+other.y, self.z+other.z
x=mymath(2,7,6)
y=mymath(4,3,8)
z=mymath(2,4,6)
print(x+y+z)
You are returning a tuple, which doesn't have the __add__() method overloaded. You should return a mymath object instead:
class mymath:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __add__(self, other):
return mymath(self.x + other.x, self.y + other.y, self.z + other.z)
def __str__(self):
return "({}, {}, {})".format(self.x, self.y, self.z)
x = mymath(2, 7, 6)
y = mymath(4, 3, 8)
z = mymath(2, 4, 6)
print(x + y + z) # Result: (8, 14, 20)
Edit: clarification of the solution added after comment
Each + sign translates to an __add__() call. In your example, the operation x + y + z is actually performing two calls to __add__(): x.__add__(y).__add__(z).
Adding parentheses to the expression may help: x + y + z actually translates to (x.__add__(y)).__add__(z).
The problem happens in the second call to __add__(), since your method is returning self.x + other.x, self.y + other.y, self.z + other.z which is the tuple (self.x + other.x, self.y + other.y, self.z + other.z) (you can omit the parentheses in your code, and it's more pythonic, but it's equivalent and it's actually a tuple).
A tuple is a fixed-length list of elements and it's a basic class of python language. You can read more about it here.
The result of x.__add__(y) is the summation you would expect from x + y, but of type tuple. In the example, (6, 10, 14) == (2 + 4, 7 + 3, 6 + 8)
You can check this running your code but printing just x + y
print(x + y) # Prints (6, 10, 14)
And also:
print(type(x + y)) # Prints <class 'tuple'>
The second addition, though, fails, because the result of the first one is a tuple and not a mymath object. So (x + y) + z actually is calling the __add__() method of the tuple, which exists but has other meaning than the one you want. Therefore, you are obtaining the error TypeError: can only concatenate tuple (not "mymath") to tuple
Note that adding two tuples is just appending them and not adding the coordinates element-wise: (1, 2, 3) + (4, 5, 6) ==> (1, 2, 3, 4, 5, 6)
The solution to this problem is to return a mymath object as the result of the __add__() operation, allowing to concatenate more than one addition operation.
I added the __str__() method to your class because otherwise the print is just showing a default representation of the class like <__main__.mymath object at 0x7f8654657390>.

Complex operations with Python (pygame.math.Vector2)

I'm learning Python and came across a complex expression that derives from pygame.Vector2:
import pygame
x = pygame.math.Vector2 (1,2)
b = x * 5 - (1, 2)
print (x)
print (b)
Result:
[1,2]
[4,8]
In the above case, the same x * 5 operation is performed both for the 1 and 2 values of Vector2, resulting in (5, 10) respectively; and then both results are subtracted from the tuple (1, 2), resulting in [4,8]
However if I do assign a simple tuple to x: x = (1, 2), instead of Vector2, I get the error:
TypeError: unsupported operand type (s) for -: 'tuple' and 'tuple'
My question is: At what times in Python I can perform these complex operations?
Can do something like (see comments too):
x = (1,2) # create a `tuple`
b = map(lambda x: x * 5,x) # do a `map` object for multiplying 5 to all of them
print(x) # print the `tuple`
t=iter((1,2)) # do an generator object using `iter`, so able to use `next` to access every next element
print(tuple(map(lambda x: x-next(t),b))) # do the `next` and another `map`, to subtract as you wanted
Best thing is still to create a class:
from __future__ import division
class MyClass:
def __init__(self,t):
self.t=t
def __mul__(self,other):
return MyClass(tuple(map(lambda x: x*other,self.t)))
def __truediv__(self,other):
return MyClass(tuple(map(lambda x: x/other,self.t)))
def __sub__(self,other):
gen=iter(other)
return MyClass(tuple(map(lambda x: x-next(gen),self.t)))
def __add__(self,other):
gen=iter(other)
return MyClass(tuple(map(lambda x: x+next(gen),self.t)))
def __repr__(self):
return str(tuple(self.t))
Then now can do anything:
x = MyClass((1,2))
b = x*5
print(b)
print(b-(1,2))
Output:
(5, 10)
(4, 8)
Also can do addition:
x = MyClass((1,2))
b = x*5
print(b)
print(b-(1,2)+(3,4))
Output:
(5, 10)
(7, 12)
Also division:
x = MyClass((1,2))
b = x*5
print(b)
print((b-(1,2)+(3,4))/2)
Output:
(5, 10)
(3.5, 6.0)

How can I manipulate cartesian coordinates in Python?

I have a collection of basic cartesian coordinates and I'd like to manipulate them with Python. For example, I have the following box (with coordinates show as the corners):
0,4---4,4
0,0---4,0
I'd like to be able to find a row that starts with (0,2) and goes to (4,2). Do I need to break up each coordinate into separate X and Y values and then use basic math, or is there a way to process coordinates as an (x,y) pair? For example, I'd like to say:
New_Row_Start_Coordinate = (0,2) + (0,0)
New_Row_End_Coordinate = New_Row_Start_Coordinate + (0,4)
Sounds like you're looking for a Point class. Here's a simple one:
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return "{}, {}".format(self.x, self.y)
def __neg__(self):
return Point(-self.x, -self.y)
def __add__(self, point):
return Point(self.x+point.x, self.y+point.y)
def __sub__(self, point):
return self + -point
You can then do things like this:
>>> p1 = Point(1,1)
>>> p2 = Point(3,4)
>>> print p1 + p2
4, 5
You can add as many other operations as you need. For a list of all of the methods you can implement, see the Python docs.
depending on what you want to do with the coordinates, you can also misuse the complex numbers:
import cmath
New_Row_Start_Coordinate = (0+2j) + (0+0j)
New_Row_End_Coordinate = New_Row_Start_Coordinate + (4+0j)
print New_Row_End_Coordinate.real
print New_Row_End_Coordinate.imag
Python doesn't natively support elementwise operations on lists; you could do it via list comprehensions or map but that's a little clunky for this use case. If you're doing a lot of this kind of thing, I'd suggest looking at NumPy.
For a = (0,2) and b = (0,0) a + b will yield (0, 2, 0, 0), which is probably not what you want. I suggest to use numpy's add function: http://docs.scipy.org/doc/numpy/reference/generated/numpy.add.html
Parameters : x1, x2 : array_like
Returns: The sum of x1 and x2, element-wise. (...)

why is my function not always returning the correct list?

I have a function that takes two inputs, and will return an array of tuples where the two numbers in a given tuple have the exact same ratio as the two numbers given to the function!
So everything was working fine, but for some reason in some instances, it is not picking up every tuple. Here is an example of it, and I don't know why:
In [52]: def find_r(num1,num2):
....: ratio = num1/float(num2)
....: ratio = 1/ratio
....: my_list = [(a,int(a * ratio)) for a in range(1,num1) if float(a * ratio).is_integer()] #and a * 1/float(ratio) + a <= num1]
....: return my_list
....:
In [53]: find_r(100,364)
Out[53]: [(75, 273)]
so it returned just one tuple, but if you divide both 75 and 273 by 3, you get a tuple of 25 and 91, which have the same ratio! Why did my function not pick up this instance?
If it helps, I do suspect it has something to do with the is_integer() method, but I am not too sure.
Thanks!
It is due to the imprecision of floating point arithmetic:
>>> ((100/364)*364).is_integer()
False
>>> ((25/91)*91).is_integer()
False
Instead of doing what you're doing, you should check for equivalence of fractions by cross-multiplying. That is, given a fraction a/b, to check if it is equivalent to another c/d, check whether ad == bc. This will avoid division and keep everything as integers.
You can do this:
def find_r(num1,num2):
return [(a, a*num2//num1) for a in range(1, num1) if (a*num2) % num1 == 0]
>>> find_r(100, 364)
[(25, 91), (50, 182), (75, 273)]
(There are other ways to accomplish your task, but this is the most similar to your original approach.)
I think that you get the answer you expect
>>> r=100/float(364)
>>> r
0.27472527472527475
>>> r=1/r
>>> r
3.6399999999999997
>>> r*25
90.99999999999999
>>> r*75
273.0
To make your integer check, you can use
if(int(a*ratio) == a*ratio) like in
def find_r(num1,num2):
ratio = num1/float(num2)
ratio = 1/ratio
my_list = [(a,int(a * ratio)) for a in range(1,num1) if int(a * ratio) == a * ratio]
for a in range(1,num1):
if int(a * ratio) == a * ratio:
print a * ratio
return my_list
print find_r(100,364)

Subtract Overlaps Between Two Ranges Without Sets

NO SETS!
I can't use Sets because:
The ranges will be too long.
They will take up too much memory
The creation of the sets themselves will take too long.
Using only the endpoints of the of the ranges, is there an optimal way to subtract two lists of ranges?
Example:
r1 = (1, 1000), (1100, 1200)
r2 = (30, 50), (60, 200), (1150, 1300)
r1 - r2 = (1, 29), (51, 59), (201, 1000), (1100, 1149)
Other info:
r2 does not have to overlap r1
r1 and r2 will not have pairs that overlap other pairs. For instance, r1 will not have both (0,30) and (10, 25)
Thanks.
The interval package may provide all that you need.
from interval import Interval, IntervalSet
r1 = IntervalSet([Interval(1, 1000), Interval(1100, 1200)])
r2 = IntervalSet([Interval(30, 50), Interval(60, 200), Interval(1150, 1300)])
print(r1 - r2)
>>> [1..30),(50..60),(200..1000],[1100..1150)
This was an interesting problem!
I think this is right, and it's fairly compact. It should work with overlapping ranges of all kinds, but it assumes well-formed ranges (i.e. [x, y) where x < y). It uses [x, y) style ranges for simplicity. It's based on the observation that there are really only six possible arrangements (with results in ()):
Edit: I found a more compact representation:
(s1 e1) s2 e2
(s1 s2) e1 e2
(s1 s2) (e2 e1)
s2 e2 (s1 e1)
s2 s1 (e2 e1)
s2 s1 e1 e2 ()
Given a sorted list of endpoints, if endpoints[0] == s1 then the first two endpoints should be in the result. If endpoints[3] == e1 then the last two endpoints should be in the result. If neither, then there should be no result.
I haven't tested it a great deal, so it's entirely possible that something is wrong. Please let me know if you find a mistake!
import itertools
def range_diff(r1, r2):
s1, e1 = r1
s2, e2 = r2
endpoints = sorted((s1, s2, e1, e2))
result = []
if endpoints[0] == s1 and endpoints[1] != s1:
result.append((endpoints[0], endpoints[1]))
if endpoints[3] == e1 and endpoints[2] != e1:
result.append((endpoints[2], endpoints[3]))
return result
def multirange_diff(r1_list, r2_list):
for r2 in r2_list:
r1_list = list(itertools.chain(*[range_diff(r1, r2) for r1 in r1_list]))
return r1_list
Tested:
>>> r1_list = [(1, 1001), (1100, 1201)]
>>> r2_list = [(30, 51), (60, 201), (1150, 1301)]
>>> print multirange_diff(r1_list, r2_list)
[(1, 30), (51, 60), (201, 1001), (1100, 1150)]
One solution (in addition to all the other different solutions that have been presented here) is to use an interval/segment tree (they are really the same thing):
http://en.wikipedia.org/wiki/Segment_tree
http://en.wikipedia.org/wiki/Interval_tree
One big advantage to doing it this way is that it is trivial to do arbitrary boolean operations (not just subtraction) using the same piece of code. There is a standard treatment of this data structure in de Berg. To perform any boolean operation on a pair of interval trees, (including subtraction) you just merge them together. Here is some (admittedly naive) Python code for doing this with unbalanced range trees. The fact that they are unbalanced has no effect on the time taken to merge the trees, however the tree construction here is the really dumb part which ends up being quadratic (unless the reduce is executed by partitioning, which I somehow doubt). Anyway here you go:
class IntervalTree:
def __init__(self, h, left, right):
self.h = h
self.left = left
self.right = right
def merge(A, B, op, l=-float("inf"), u=float("inf")):
if l > u:
return None
if not isinstance(A, IntervalTree):
if isinstance(B, IntervalTree):
opT = op
A, B, op = B, A, (lambda x, y : opT(y,x))
else:
return op(A, B)
left = merge(A.left, B, op, l, min(A.h, u))
right = merge(A.right, B, op, max(A.h, l), u)
if left is None:
return right
elif right is None or left == right:
return left
return IntervalTree(A.h, left, right)
def to_range_list(T, l=-float("inf"), u=float("inf")):
if isinstance(T, IntervalTree):
return to_range_list(T.left, l, T.h) + to_range_list(T.right, T.h, u)
return [(l, u-1)] if T else []
def range_list_to_tree(L):
return reduce(lambda x, y : merge(x, y, lambda a, b: a or b),
[ IntervalTree(R[0], False, IntervalTree(R[1]+1, True, False)) for R in L ])
I wrote this kind of quickly and didn't test it that much, so there could be bugs. Also note that this code will work with arbitrary boolean operations, not just differences (you simply pass them as the argument to op in merge). The time complexity of evaluating any of these is linear on the size of the output tree (which is also the same as the number of intervals in the result). As an example, I ran it on the case you provided:
#Example:
r1 = range_list_to_tree([ (1, 1000), (1100, 1200) ])
r2 = range_list_to_tree([ (30, 50), (60, 200), (1150, 1300) ])
diff = merge(r1, r2, lambda a, b : a and not b)
print to_range_list(diff)
And I got the following output:
[(1, 29), (51, 59), (201, 1000), (1100, 1149)]
Which seems to be in agreement with what you would expect. Now if you want to do some other boolean operations here is how it would work using the same function:
#Intersection
merge(r1, r2, lambda a, b : a and b)
#Union
merge(r1, r2, lambda a, b : a or b)
#Xor
merge(r1, r2, lambda a, b : a != b)
I think I misunderstood the question, but this code works if r2 is a subset of r1
class RangeSet:
def __init__(self, elements):
self.ranges = list(elements)
def __iter__(self):
return iter(self.ranges)
def __repr__(self):
return 'RangeSet: %r' % self.ranges
def has(self, tup):
for pos, i in enumerate(self.ranges):
if i[0] <= tup[0] and i[1] >= tup[1]:
return pos, i
raise ValueError('Invalid range or overlapping range')
def minus(self, tup):
pos, (x,y) = self.has(tup)
out = []
if x < tup[0]:
out.append((x, tup[0]-1))
if y > tup[1]:
out.append((tup[1]+1, y))
self.ranges[pos:pos+1] = out
def __sub__(self, r):
r1 = RangeSet(self)
for i in r: r1.minus(i)
return r1
def sub(self, r): #inplace subtraction
for i in r:
self.minus(i)
then, you do:
Update: Note the last interval of r2 is different to work the way I meant.
>>> r1 = RangeSet(((1, 1000), (1100, 1200)))
>>> r2 = RangeSet([(30, 50), (60, 200), (1150, 1200)])
>>> r1 - r2
RangeSet: [(1, 29), (51, 59), (201, 1000), (1100, 1149)]
>>> r1.sub(r2)
>>> r1
RangeSet: [(1, 29), (51, 59), (201, 1000), (1100, 1149)]
Here's a quick python function that does the subtraction, regardless of whether the initial lists are well-formed (i.e. turns the lists into the smallest list of equivalent ranges, sorted, before doing the subtraction):
def condense(l):
l = sorted(l)
temp = [l.pop(0)]
for t in l:
if t[0] <= temp[-1][1]:
t2 = temp.pop()
temp.append((t2[0], max(t[1], t2[1])))
else:
temp.append(t)
return temp
def setSubtract(l1, l2):
l1 = condense(l1)
l2 = condense(l2)
i = 0
for t in l2:
while t[0] > l1[i][1]:
i += 1
if i >= len(l1):
break
if t[1] < l1[i][1] and t[0] > l1[i][0]:
#t cuts l1[i] in 2 pieces
l1 = l1[:i] + [(l1[i][0], t[0] - 1), (t[1] + 1, l1[i][1])] + l1[i + 1:]
elif t[1] >= l1[i][1] and t[0] <= l1[i][0]:
#t eliminates l1[i]
l1.pop(i)
elif t[1] >= l1[i][1]:
#t cuts off the top end of l1[i]
l1[i] = (l1[i][0], t[0] - 1)
elif t[0] <= l1[i][0]:
#t cuts off the bottom end of l1[i]
l1[i] = (t[1] + 1, l1[i][1])
else:
print "This shouldn't happen..."
exit()
return l1
r1 = (1, 1000), (1100, 1200)
r2 = (30, 50), (60, 200), (1150, 1300)
setSubtract(r1, r2) #yields [(1, 29), (51, 59), (201, 1000), (1100, 1149)]
Fun question! Another implementation, though you already have plenty. It was interesting to do!
Involves some extra 'decoration' to make what I'm doing more explicit.
import itertools
def flatten_range_to_labeled_points(input_range,label):
range_with_labels = [((start,'start_%s'%label),(end,'end_%s'%label)) for (start,end) in input_range]
flattened_range = list(reduce(itertools.chain,range_with_labels))
return flattened_range
def unflatten_range_remove_labels(input_range):
without_labels = [x for (x,y) in input_range]
grouped_into_pairs = itertools.izip(without_labels[::2], without_labels[1::2])
return grouped_into_pairs
def subtract_ranges(range1, range2):
range1_labeled = flatten_range_to_labeled_points(range1,1)
range2_labeled = flatten_range_to_labeled_points(range2,2)
all_starts_ends_together = sorted(range1_labeled + range2_labeled)
in_range1, in_range2 = False, False
new_starts_ends = []
for (position,label) in all_starts_ends_together:
if label=='start_1':
in_range1 = True
if not in_range2:
new_starts_ends.append((position,'start'))
elif label=='end_1':
in_range1 = False
if not in_range2:
new_starts_ends.append((position,'end'))
elif label=='start_2':
in_range2 = True
if in_range1:
new_starts_ends.append((position-1,'end'))
elif label=='end_2':
in_range2 = False
if in_range1:
new_starts_ends.append((position+1,'start'))
# strip the start/end labels, they're not used, I just appended them for clarity
return unflatten_range_remove_labels(new_starts_ends)
I get the right output:
r1 = (1, 1000), (1100, 1200)
r2 = (30, 50), (60, 200), (1150, 1300)
>>> subtract_ranges(r1,r2)
[(1, 29), (51, 59), (201, 1000), (1100, 1149)]
this is rather ugly but it does work for the given example
def minus1(a,b):
if (b[0] < a[0] and b[1] < a[0]) or (a[1] < b[0] and a[1] < b[1]):
return [a] # doesn't overlap
if a[0]==b[0] and a[1]==b[1]:
return [] # overlaps exactly
if b[0] < a[0] and a[1] < b[1]:
return [] # overlaps completely
if a[0]==b[0]:
return [(b[1]+1,a[1])] # overlaps exactly on the left
if a[1]==b[1]:
return [(a[0],b[0]-1)] # overlaps exactly on the right
if a[0] < b[0] and b[0] < a[1] and a[1] < b[1]:
return [(a[0],b[0]-1)] # overlaps the end
if a[0] < b[1] and b[1] < a[1] and b[0] < a[0]:
return [(b[1]+1,a[1])] # overlaps the start
else:
return [(a[0],b[0]-1),(b[1]+1,a[1])] # somewhere in the middle
def minus(r1, r2):
# assume r1 and r2 are already sorted
r1 = r1[:]
r2 = r2[:]
l = []
v = r1.pop(0)
b = r2.pop(0)
while True:
r = minus1(v,b)
if r:
if len(r)==1:
if r[0] == v:
if v[1] < b[0] and v[1] < b[1]:
l.append(r[0])
if r1:
v = r1.pop(0)
else:
break
else:
if r2:
b = r2.pop(0)
else:
break
else:
v = r[0]
else:
l.append(r[0])
v = r[1]
if r2:
b = r2.pop(0)
else:
l.append(v)
break
else:
if r1:
v = r1.pop(0)
else:
break
if r2:
b = r2.pop(0)
else:
l.append(v)
l.extend(r1)
break
return l
r1 = [(1, 1000), (1100, 1200)]
r2 = [(30, 50), (60, 200), (1150, 1300)]
print minus(r1,r2)
prints:
[(1, 29), (51, 59), (201, 1000), (1100, 1149)]

Categories

Resources