Subtract Overlaps Between Two Ranges Without Sets - python

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

Related

Efficiency of the script (finding a pair of integers which have the same remainder)

I am trying to find a pair (x,y) in A such that x-y = 0 (mod n) where inputs are a positive integer n, a set A of m nonnegative integers and m > n. To run the code below I took an m and n just for the sake of running an example.
Below is the script I have written.
I wonder if there is a more efficient way to write the script
import numpy as np import sys
n = 10
m = 12
def functi(n, m):
A = [0] * m
for i in range(m):
A[i] = np.random.randint(0,34)
X = [-1] * n
for i in range(len(A)-1,-1,-1) : #loop backwards
a = A[i]
A.pop(i)
r = a % n
if X[r] == -1:
X[r] = a
else:
return(X[r], a)
pair = functi(n, m)
print(pair)
Note that your function doesn't have the parameters described by the problem -- it should take n and A as parameters, not take an m and generate its own A.
The problem is much easier if you look at it as simply "find a pair of numbers with the same value mod n". An simple approach to this is to bucket all of the numbers according to their value % n, and return a bucket once it has two numbers in it. That way you don't need to compare each pair of values individually to see if they match.
>>> import random
>>> def find_equal_pair_mod_n(n, A):
... assert len(A) > n
... mods = {}
... for i in A:
... xy = mods.setdefault(i % n, [])
... xy.append(i)
... if len(xy) > 1:
... return tuple(xy)
...
>>> find_equal_pair_mod_n(10, [random.randint(0, 34) for _ in range(12)])
(26, 6)
>>> find_equal_pair_mod_n(10, [random.randint(0, 34) for _ in range(12)])
(30, 10)
>>> find_equal_pair_mod_n(10, [random.randint(0, 34) for _ in range(12)])
(32, 32)
>>> find_equal_pair_mod_n(10, [random.randint(0, 34) for _ in range(12)])
(1, 1)
>>> find_equal_pair_mod_n(10, [random.randint(0, 34) for _ in range(12)])
(28, 8)

How to simplify a fraction in Python?

still learning Python so any help is welcome.
I have written a funtion to get a fraction, but more often than not the fraction can be simplified. My code does not do this yet. Any ideas how to simplify the fraction without using any import functions?
code:
def add_frac(n1, d1, n2, d2):
frac = (((n1 * d1) + (n2 * d2)), (d1 * d2))
return frac
example:
add_frac(1, 2, 1, 4) gives (6, 8) in stead of (3, 4)
Any help is welcome!
A pretty naive approach to get you started, you can add some checks to make it more efficient.
def simplify_frac(n, d):
i = 2
while i < min(n, d) + 1:
if n % i == 0 and d % i == 0:
n = n // i
d = d // i
else:
i += 1
return n, d
# Some examples
In [105]: simplify_frac(2, 4)
Out[105]: (1, 2)
In [106]: simplify_frac(16, 36)
Out[106]: (4, 9)
In [107]: simplify_frac(7, 3)
Out[107]: (7, 3)
In [108]: simplify_frac(10, 1)
Out[108]: (10, 1)
In [109]: simplify_frac(1, 10)
Out[109]: (1, 10)
In [110]: simplify_frac(102, 10)
Out[110]: (51, 5)
In [111]: simplify_frac(110, 10)
Out[111]: (11, 1)
We use a modulo operator % to check the remainder from integer division by i, if both n and d have a remainder of 0 we know i divides them.
In addition to above answers, if you cannot import any other libraries, here's is the implementation of fractions.gcd for python 2.7 (copied from https://stackoverflow.com/a/11175154/10155740)
>>> print inspect.getsource(gcd)
def gcd(a, b):
"""Calculate the Greatest Common Divisor of a and b.
Unless b==0, the result will have the same sign as b (so that when
b is divided by it, the result comes out positive).
"""
while b:
a, b = b, a%b
return a
so if you'd incorporate this in your code, you should get something like:
def gcd(a, b):
while b:
a, b = b, a%b
return a
def add_frac(n1, d1, n2, d2):
frac = (((n1 * d1) + (n2 * d2)), (d1 * d2))
return tuple(i//gcd(*frac) for i in frac)
print(add_frac(1,2,1,4))
# OUTPUT: (3, 4)
The naive form of adding two fractions returns a correct answer, just not the most reduced correct answer. For that, you need to divide the numerator and denominator of the result by the greatest common denominator (GCD) of the original denominators. In this case, the GCD of 2 and 4 is 2, so dividing 6 and 8 by 2 gives the desired answer (3,4)
math.gcd can be used:
from math import gcd
def add_frac(n1, d1, n2, d2):
x = gcd(d1, d2)
frac = (((n1 * d2) + (n2 * d1)) / x, (d1 * d2) / x)
return frac
though it sounds like you are expected to define your own implementation of gcd.
Here is a solution with a recursive implementation of gcd, using Euclid Algorithm; it also works on negative numbers :
def mygcd(a, b) :
return gcd_rec(abs(a), abs(b))
def gcd_rec(a,b) :
if a*b == 0 : return a+b
if a <= b : return gcd_rec(a, b % a)
return gcd_rec(a % b, b)
def add_frac(n1, d1, n2, d2):
n = n1*d2 + n2*d1
d = d1*d2
g = mygcd(n, d)
return n//g, d//g
You need to divide it by GCD.
import math
def add_frac(n1, d1, n2, d2):
nume = (n1 * d1) + (n2 * d2)
deno = d1 * d2
gcd = math.gcd(nume,deno)
nume /= gcd
deno /= gcd
return (nume,deno)
>>> add_frac(1,2,1,4)
>>> (3.0, 4.0)

Minimum moves to reach k

Given two numbers m and n, in one move you can get two new pairs:
m+n, n
m, n+m
Let's intially set m = n = 1 find the minimum number of moves so that at least one of the numbers equals k
it's guaranteed there's a solution (i.e. there exist a sequence of moves that leads to k)
For example:
given k = 5
the minimum number of moves so that m or n is equal to k is 3
1, 1
1, 2
3, 2
3, 5
Total of 3 moves.
I have come up with a solution using recursion in python, but it doesn't seem to work on big number (i.e 10^6)
def calc(m, n, k):
if n > k or m > k:
return 10**6
elif n == k or m == k:
return 0
else:
return min(1+calc(m+n, n, k), 1+calc(m, m+n, k))
k = int(input())
print(calc(1, 1, k))
How can I improve the performance so it works for big numbers?
Non-Recursive Algorithm based on Priority Queue (using Heap)
State: (sum_, m, n, path)
sum_ is current sum (i.e. m + n)
m and n are the first and second numbers
path is the sequence of (m, n) pairs to get to the current sum
In each step there are two possible moves
Replace first number by the sum
Replace second number by the sum
Thus each state generates two new states. States are prioritized by:
moves: states with a lower number of have higher priority
sum: States with higher sums have higher priority
We use a Priority Queue (Heap in this case) to process states by priority.
Code
from heapq import heappush, heappop
def calc1(k):
if k < 1:
return None, None # No solution
m, n, moves = 1, 1, 0
if m == k or n == k:
return moves, [(m, n)]
h = [] # Priority queue (heap)
path = [(m, n)]
sum_ = m + n
# Python's heapq acts as a min queue.
# We can order thing by max by using -value rather than value
# Thus Tuple (moves+1, -sum_, ...) prioritizes by 1) min moves, and 2) max sum
heappush(h, (moves+1, -sum_, sum_, n, path))
heappush(h, (moves+1, -sum_, m, sum_, path))
while h:
# Get state with lowest sum
moves, sum_, m, n, path = heappop(h)
sum_ = - sum_
if sum_ == k:
return moves, path # Found solution
if sum_ < k:
sum_ = m + n # new sum
# Replace first number with sum
heappush(h, (moves+1, -sum_, sum_, n, path + [(sum_, n)]))
# Replace second number with sum
heappush(h, (moves+1, -sum_, m, sum_, path + [(m, sum_)]))
# else:
# so just continues since sum_ > k
# Exhausted all options, so no solution
return None, None
Test
Test Code
for k in [5, 100, 1000]:
moves, path = calc1(k)
print(f'k: {k}, Moves: {moves}, Path: {path}')
Output
k: 5, Moves: 3, Path: [(1, 1), (2, 3), (2, 5)]
k: 100, Moves: 10, Path: [(1, 1), (2, 3), (5, 3), (8, 3), (8, 11),
(8, 19), (27, 19), (27, 46), (27, 73), (27, 100)]
k: 1000, Moves: 15, Path: [(1, 1), (2, 3), (5, 3), (8, 3), (8, 11),
(19, 11), (19, 30), (49, 30), (79, 30), (79, 109),
(188, 109), (297, 109), (297, 406), (297, 703), (297, 1000)]
Performance Improvement
Following two adjustments to improve performance
Not including path just number of steps (providing 3X speedup for k = 10,000
Not using symmetric pairs (provided 2x additional with k = 10, 000
By symmetric pairs, mean pairs of m, n which are the same forward and backwards, such as (1, 2) and (2, 1).
We don't need to branch on both of these since they will provide the same solution step count.
Improved Code
from heapq import heappush, heappop
def calc(k):
if k < 1:
return None, None
m, n, moves = 1, 1, 0
if m == k or n == k:
return moves
h = [] # Priority queue (heap)
sum_ = m + n
heappush(h, (moves+1, -sum_, sum_, n))
while h:
moves, sum_, m, n = heappop(h)
sum_ = - sum_
if sum_ == k:
return moves
if sum_ < k:
sum_ = m + n
steps = [(sum_, n), (m, sum_)]
heappush(h, (moves+1, -sum_, *steps[0]))
if steps[0] != steps[-1]: # not same tuple in reverse (i.e. not symmetric)
heappush(h, (moves+1, -sum_, *steps[1]))
Performance
Tested up to k = 100, 000 which took ~2 minutes.
Update
Converted solution by #גלעדברקן from JavaScript to Python to test
def g(m, n, memo):
key = (m, n)
if key in memo:
return memo[key]
if m == 1 or n == 1:
memo[key] = max(m, n) - 1
elif m == 0 or n == 0:
memo[key] = float("inf")
elif m > n:
memo[key] = (m // n) + g(m % n, n, memo)
else:
memo[key] = (n // m) + g(m, n % m, memo)
return memo[key]
def f(k, memo={}):
if k == 1:
return 0
return min(g(k, n, memo) for n in range((k // 2) + 1))
Performance of #גלעדברקן Code
Completed 100K in ~1 second
This is 120X faster than my above heap based solution.
This is an interesting problem in number theory, including linear Diophantine equations. Since there are solutions available on line, I gather that you want help in deriving the algorithm yourself.
Restate the problem: you start with two numbers characterized as 1*m+0*n, 0*m+1*n. Use the shorthand (1, 0) and (0, 1). You are looking for the shortest path to any solution to the linear Diophantine equation
a*m + b*n = k
where (a, b) is reached from starting values (1, 1) a.k.a. ( (1, 0), (0, 1) ).
So ... starting from (1, 1), how can you characterize the paths you reach from various permutations of the binary enhancement. At each step, you have two choices: a += b or b += a. Your existing algorithm already recognizes this binary search tree.
These graph transitions -- edges along a lattice -- can be characterized, in terms of which (a, b) pairs you can reach on a given step. Is that enough of a hint to move you along? That characterization is the key to converting this problem into something close to a direct computation.
We can do much better than the queue even with brute force, trying each possible n when setting m to k. Here's JavaScript code, very close to Python syntax:
function g(m, n, memo){
const key = m + ',' + n;
if (memo[key])
return memo[key];
if (m == 1 || n == 1)
return Math.max(m, n) - 1;
if (m == 0 || n == 0)
return Infinity;
let answer;
if (m > n)
answer = Math.floor(m / n) + g(m % n, n, memo);
else
answer = Math.floor(n / m) + g(m, n % m, memo);
memo[key] = answer;
return answer;
}
function f(k, memo={}){
if (k == 1)
return 0;
let best = Infinity;
for (let n=1; n<=Math.floor(k/2); n++)
best = Math.min(best, g(k, n, memo));
return best;
}
var memo = {};
var ks = [1, 2, 5, 6, 10, 100, 1000, 100000];
for (let k of ks)
console.log(`${ k }: ${ f(k, memo) }`);
By default, in Python, for a recursive function the recursion limit is set to 10^4. You can change it using sys module:
import sys
sys.setrecursionlimit(10**6)

Find number of pairs that add up to a specific number from two different lists?

a = [1,2,3,4,5,6,7]
b = [56,59,62,65,67,69]
def sumOfTwo(a,b,v):
for i in range (len(a)):
val_needed = v - a[i]
for j in range (len(b)):
if b[j] == val_needed:
x = b[j]
y = a[i]
print(x,y)
sumOfTwo(a,b,v=70)
Output: 5 65
What if more pairs are possible from the given lists in the problem, how do I do that?
Help.
What are more ways to achieve this?
If you just want to print matched values, you just have to indent the print statement to be inside theif, as stated below. Also, you should use a more pythonic approach to for loops and also for variable assignments.
a = [1,2,3,4,5,6,7]
b = [56,59,62,65,67,69]
def sumOfTwo(a,b,v):
for i in a:
val_needed = v - i
for j in b:
if j == val_needed:
x, y = j, i
print(x,y)
sumOfTwo(a,b,v=70)
Using a list comprehension:
a = [1,2,3,4,5,6,7]
b = [56,59,62,65,67,69]
c = [(x, y)
for x in a
for y in b
if x + y == 70]
print(c)
This yields
[(1, 69), (3, 67), (5, 65)]

Adding Values From Tuples of Same Length

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)

Categories

Resources