quick way to do def pair_sum() - python

Does anyone know how to do this in a simple and effective way?
Thanks
Define a function called pair_sum() which takes two inputs: a list of integers and a total.
The function should return a list of tuples, where each value in the tuple is a unique value from the input list, and where the sum of the tuple elements equals the total. Each pair of values in the input list that sums to the total should only appear once in the output list. For example, if the input list is [3, 2, 1] and the total is 4, then the output list will only contain the tuple (3, 1) and not the tuple (1, 3). In other words, if (i, j) is a tuple in the output list, then i should appear to the left of j in the input list.
For example:
Test Result
print(pair_sum([4, 6, 2, 7, 3], 10))
[(4, 6), (7, 3)]
print(pair_sum([4, 7, 8, 9, 3, 2, 6, 11, 1, 5, 10], 14))
[(4, 10), (8, 6), (9, 5), (3, 11)]

#xaovnumwsercz, I proposed this version.
def pair_sum(numbers, target):
answer = []
for i, num in enumerate(numbers):
if target-num in numbers[i+1:]:
answer.append((num,target-num))
return answer

def pair_sum (numbers, pairSum):
resultSet=[];
newNumbers = sorted(numbers);
i = 0;
j = len(newNumbers)-1;
while i < len(newNumbers) and j >= 0:
if newNumbers[i] + newNumbers[j] == pairSum and i != j:
if (newNumbers[j], newNumbers[i]) not in resultSet and numbers.index(newNumbers[i]) < numbers.index(newNumbers[j]):
resultSet.append((newNumbers[i], newNumbers[j]))
numbers.remove(newNumbers[i]);
numbers.remove(newNumbers[j])
i = i + 1;
j = j - 1;
elif newNumbers[i] + newNumbers[j] < pairSum:
i = i + 1;
else:
j = j - 1;
return (resultSet);

Related

Finding a pair of elements in a list that adds up to a sum using a dictionary

Context of the problem:
Find Pair with given Sum in the Array.
Given an unsorted list of ints, find a pair with a given sum in it.
EXAMPLE:
list = [8, 7, 2, 5, 3, 1]
sum = 10
OUTPUT = index 0 & 2 (8, 2) or 1 & 4 (7, 3)
This is what I have so far:
def find_pair_dict(ints: [int], sum_: int):
dict_ = dict()
# {
# element: index
# 8: 0,
# 7: 1,
# ...
# }
output = list()
for i in range(len(ints)):
diff = sum_ - ints[i]
# print(diff)
if diff not in dict_.keys():
# int: index
dict_[ints[i]] = i
else:
output.append((dict_[ints[i]], dict_[diff]))
if not output:
return "No pairs were found"
return output
I am calling this function with find_pair_dict([8, 7, 2, 5, 3, 1], 10) and am getting an error that I do not understand.
The Error
Traceback (most recent call last):
File "find_pair_sum.py", line 62, in <module>
print(find_pair_dict([8, 7, 2, 5, 3, 1], 10))
File "find_pair_sum.py", line 53, in find_pair_dict
output.append((dict_[ints[i]], dict_[diff]))
KeyError: 2
This sounds like the element of 2 cannot be added?
Almost a one-liner:
def find_pairs(ints: [int], sum_: int):
return {
tuple(sorted((n, ints.index(sum_-i)))): (i, sum_-i)
for n, i in enumerate(ints) if sum_ - i in ints
}
print(find_pairs([8, 7, 2, 5, 3, 1], 10))
Result:
{(0, 2): (2, 8), (1, 4): (3, 7), (3, 3): (5, 5)}
Note: the key to the dictionary is a sorted tuple. A tuple because a list isn't hashable and sorted to avoid both (0,2) and (2,0) showing up as keys (for example).
The error in your solution occurs because the first time this line is executed:
output.append((dict_[ints[i]], dict_[diff]))
The value if dict_ is {8: 0, 7: 1} and ints[i] is 2. Since there is no entry for 2 in the dict, you get this error.
Actually I think there should be three outputs:
(2,0) = 2,8
(4,1) = 3,7
(3,3) = 5,5
The problem has already been succinctly described by #Selcuk. I am suggesting you how to solve that. Many ways. Easiest for me is to use defaultdict from collections.
Below is the code with minimal changes
from collections import defaultdict
def find_pair_dict(ints: [int], sum_: int):
dict_ = defaultdict(list)
output = list()
for i in range(len(ints)):
diff = sum_ - ints[i]
dict_[ints[i]] = i
output.append((dict_[ints[i]], dict_[diff]))
if not output:
return "No pairs were found"
return output
print(find_pair_dict([8, 7, 2, 5, 3, 1], 10))
This prints out the following. I have not filtered for empty matches.
[(0, []), (1, []), (2, 0), (3, 3), (4, 1), (5, [])]
Is this what you wanted?

Convert a list of numbers to ranges

I have a bunch of numbers, say the following:
1 2 3 4 6 7 8 20 24 28 32
The information presented there could be represented in Python as ranges:
[range(1, 5), range(6, 9), range(20, 33, 4)]
In my output I'd write 1..4, 6..8, 20..32..4, but that is just a matter of presentation.
Another answer shows how one can do this for contiguous ranges. I don't see how I can easily do this for strided ranges like above. Is there a similar trick for this?
Here's a straight forward approach at the problem.
def get_ranges(ls):
N = len(ls)
while ls:
# single element remains, yield the trivial range
if N == 1:
yield range(ls[0], ls[0] + 1)
break
diff = ls[1] - ls[0]
# find the last index that satisfies the determined difference
i = next(i for i in range(1, N) if i + 1 == N or ls[i+1] - ls[i] != diff)
yield range(ls[0], ls[i] + 1, diff)
# update variables
ls = ls[i+1:]
N -= i + 1
def ranges(data):
result = []
if not data:
return result
idata = iter(data)
first = prev = next(idata)
for following in idata:
if following - prev == 1:
prev = following
else:
result.append((first, prev + 1))
first = prev = following
# There was either exactly 1 element and the loop never ran,
# or the loop just normally ended and we need to account
# for the last remaining range.
result.append((first, prev+1))
return result
Test:
>>> data = range(1, 5) + range(6, 9) + range(20, 24)
>>> print ranges(data)
[(1, 5), (6, 9), (20, 24)]
You can use groupby and count from itertools module along with Counter from collections module like this example:
Update: See the comments in order to understand the logic behind this solution and its limitations.
from itertools import groupby, count
from collections import Counter
def ranges_list(data=list, func=range, min_condition=1):
# Sort in place the ranges list
data.sort()
# Find all the steps between the ranges's elements
steps = [v-k for k,v in zip(data, data[1:])]
# Find the repeated items's steps based on condition.
# Default: repeated more than once (min_condition = 1)
repeated = [item for item, count in Counter(steps).items() if count > min_condition]
# Group the items in to a dict based on the repeated steps
groups = {k:[list(v) for _,v in groupby(data, lambda n, c = count(step = k): n-next(c))] for k in repeated}
# Create a dict:
# - keys are the steps
# - values are the grouped elements
sub = {k:[j for j in v if len(j) > 1] for k,v in groups.items()}
# Those two lines are for pretty printing purpose:
# They are meant to have a sorted output.
# You can replace them by:
# return [func(j[0], j[-1]+1,k) for k,v in sub.items() for j in v]
# Otherwise:
final = [(j[0], j[-1]+1,k) for k,v in sub.items() for j in v]
return [func(*k) for k in sorted(final, key = lambda x: x[0])]
ranges1 = [1, 2, 3, 4, 6, 7, 8, 20, 24, 28, 32]
ranges2 = [1, 2, 3, 4, 6, 7, 10, 20, 24, 28, 50,51,59,60]
print(ranges_list(ranges1))
print(ranges_list(ranges2))
Output:
[range(1, 5), range(6, 9), range(20, 33, 4)]
[range(1, 5), range(6, 8), range(20, 29, 4), range(50, 52), range(59, 61)]
Limitations:
With this kind of intput:
ranges3 = [1,3,6,10]
print(ranges_list(ranges3)
print(ranges_list(ranges3, min_condition=0))
Will output:
# Steps are repeated <= 1 with the condition: min_condition = 1
# Will output an empty list
[]
# With min_condition = 0
# Will output the ranges using: zip(data, data[1:])
[range(1, 4, 2), range(3, 7, 3), range(6, 11, 4)]
Feel free to use this solution and adopt it or modify it in order to fill your needs.
It might not be super short or elegant, but it seems to work:
def ranges(ls):
li = iter(ls)
first = next(li)
while True:
try:
element = next(li)
except StopIteration:
yield range(first, first+1)
return
step = element - first
last = element
while True:
try:
element = next(li)
except StopIteration:
yield range(first, last+step, step)
return
if element - last != step:
yield range(first, last+step, step)
first = element
break
last = element
This iterates over an iterator of the list, and yields range objects:
>>> list(ranges([1, 2, 3, 4, 6, 7, 8, 20, 24, 28, 32]))
[range(1, 5), range(6, 9), range(20, 33, 4)]
It also handles negative ranges, and ranges that have just one element:
>>> list(ranges([9,8,7, 1,3,5, 99])
[range(9, 6, -1), range(1, 7, 2), range(99, 100)]

Efficiently searching a pair of ordered lists with noise

Assuming two data sets are in order and that they contain pairwise matches, what is an efficient way to discover the pairs? There can be noise in either list.
From sets A,B the set C will consist of pairs (A[X1],B[Y1]),(A[X2],B[Y2]),...,(A[Xn],B[Yn]) such that X1 < X2 < ... < Xn and Y1 < Y2 < ... < Yn.
The problem can be demonstrated with the simplified Python block, where the specifics of how a successful pair is validated is irrelevant.
Because the validation condition is irrelevant, the condition return_pairs(A, B, validate) == return_pairs(B, A, validate) is not required to hold, given that the data in A,B need not be the same, just that there must exist a validation function for (A[x],B[y])
A = [0,0,0,1,2,0,3,4,0,5,6,0,7,0,0,8,0,0,9]
B = [1,2,0,0,0,0,0,3,0,0,4,0,5,6,0,0,7,0,0,8,0,9]
B1 = [1,2,0,0,0,0,0,3,0,0,4,0,5,6,0,0,7,7,7,0,0,8,0,9]
def validate(a,b):
return a and b and a==b
def return_pairs(A,B, validation):
ret = []
x,y = 0,0
# Do loops and index changes...
if validation(A[x], B[y]):
ret.append((A[x], B[y]))
return ret
assert zip(range(1,10), range(1,10)) == return_pairs(A,B,validate)
assert zip(range(1,10), range(1,10)) == return_pairs(A,B1,validate)
Instead of iterating each list in two nested loops you can first remove the noise according to your own criteria, then create a third list with the filtered elements and run each item (being a newly formed tuple) of the list against your validation. This is assuming I understood the question correctly, which I think I didn't really:
Demo
A = [0,0,0,1,2,0,3,4,0,5,6,0,7,0,0,8,0,0,9]
B = [1,2,0,0,0,0,0,3,0,0,4,0,5,6,0,0,7,0,0,8,0,9]
def clean(oldList):
newList = []
for item in oldList:
if 0<item and (not newList or item>newList[-1]):
newList.append(item)
return newList
def validate(C):
for item in C:
if item[0] != item[1]:
return False
return True
C = zip(clean(A),clean(B))
#clean(A):[1, 2, 3, 4, 5, 6, 7, 8, 9]
#clean(B):[1, 2, 3, 4, 5, 6, 7, 8, 9]
#list(C):[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9)]
#validate(C): True
A solution. O(n2)
def return_pairs(A,B, validation):
ret = []
used_x, used_y = -1,-1
for x, _x in enumerate(A):
for y, _y in enumerate(B):
if x <= used_x or y <= used_y:
continue
if validation(A[x], B[y]):
used_x,used_y = x,y
ret.append((A[x], B[y]))
return ret

Python - iterating beginning with the middle of the list and then checking either side

Really not sure where this fits. Say, I have a list:
>>>a = [1, 2, 3, 4, 5, 6, 7]
How can I iterate it in such a way, that it will check 4 first, then 5, then 3, then 6, and then 2(and so on for bigger lists)? I have only been able to work out the middle which is
>>>middle = [len(a)/2 if len(a) % 2 = 0 else ((len(a)+1)/2)]
I'm really not sure how to apply this, nor am I sure that my way of working out the middle is the best way. I've thought of grabbing two indexes and after each iteration, adding 1 and subtracting 1 from each respective index but have no idea how to make a for loop abide by these rules.
With regards as to why I need this; it's for analysing a valid play in a card game and will check from the middle card of a given hand up to each end until a valid card can be played.
You can just keep removing from the middle of list:
lst = range(1, 8)
while lst:
print lst.pop(len(lst)/2)
This is not the best solution performance-wise (removing item from list is expensive), but it is simple - good enough for a simple game.
EDIT:
More performance stable solution would be a generator, that calculates element position:
def iter_from_middle(lst):
try:
middle = len(lst)/2
yield lst[middle]
for shift in range(1, middle+1):
# order is important!
yield lst[middle - shift]
yield lst[middle + shift]
except IndexError: # occures on lst[len(lst)] or for empty list
raise StopIteration
To begin with, here is a very useful general purpose utility to interleave two sequences:
def imerge(a, b):
for i, j in itertools.izip_longest(a,b):
yield i
if j is not None:
yield j
with that, you just need to imerge
a[len(a) / 2: ]
with
reversed(a[: len(a) / 2])
You could also play index games, for example:
>>> a = [1, 2, 3, 4, 5, 6, 7]
>>> [a[(len(a) + (~i, i)[i%2]) // 2] for i in range(len(a))]
[4, 5, 3, 6, 2, 7, 1]
>>> a = [1, 2, 3, 4, 5, 6, 7, 8]
>>> [a[(len(a) + (~i, i)[i%2]) // 2] for i in range(len(a))]
[4, 5, 3, 6, 2, 7, 1, 8]
Here's a generator that yields alternating indexes for any given provided length. It could probably be improved/shorter, but it works.
def backNforth(length):
if length == 0:
return
else:
middle = length//2
yield middle
for ind in range(1, middle + 1):
if length > (2 * ind - 1):
yield middle - ind
if length > (2 * ind):
yield middle + ind
# for testing:
if __name__ == '__main__':
r = range(9)
for _ in backNforth(len(r)):
print(r[_])
Using that, you can just do this to produce a list of items in the order you want:
a = [1, 2, 3, 4, 5, 6, 7]
a_prime = [a[_] for _ in backNforth(len(a))]
In addition to the middle elements, I needed their index as well. I found Wasowski's answer very helpful, and modified it:
def iter_from_middle(lst):
index = len(lst)//2
for i in range(len(lst)):
index = index+i*(-1)**i
yield index, lst[index]
>>> my_list = [10, 11, 12, 13, 14, 15]
>>> [(index, item) for index, item in iter_from_middle(my_list)]
[(3, 13), (2, 12), (4, 14), (1, 11), (5, 15), (0, 10)]

Subset Sum with Backtracking on Python

So I want to print out all the subsets of the initial set that will add up to 21. So far I've only come up with this
def twentyone(array, num=21):
if len(array) == 0:
return None
else:
if array[0] == num:
return [array[0]]
else:
with_v = twentyone(array[1:], (num - array[0]))
if with_v:
return [array[0]] + with_v
else:
return twentyone(array[1:], num)
It does give the solution, but only the first. How do I change it so that it will give me every possible subset. I've tried making a few changes but it only gives me nested lists. Any help would be nice.
You can create a recursive generator:
def twentyone(array, num=21):
if num < 0:
return
if len(array) == 0:
if num == 0:
yield []
return
for solution in twentyone(array[1:], num):
yield solution
for solution in twentyone(array[1:], num - array[0]):
yield [array[0]] + solution
Example:
>>> list(twentyone([5, 16, 3, 2]))
[[16, 3, 2], [5, 16]]
If you are allowed to use standard Python libraries, here is a shorter solution:
import itertools
import operator
def twentyone(array, num=21):
subsets = reduce(operator.add, [list(itertools.combinations(array, r)) for r in range(1, 1 + len(array))])
return [subset for subset in subsets if sum(subset) == num]
print twentyone([1, 2, 5, 6, 8, 9, 10])
result:
[(2, 9, 10), (5, 6, 10), (1, 2, 8, 10), (1, 5, 6, 9), (2, 5, 6, 8)]

Categories