Finding two numbers that add to a given total
up vote
2
down vote
favorite
Given there's a list of random number and the amount I want to find.
For example:
[80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
And I want two numbers that add up to a given total in this case it's 181. So, it's going to be (92+89). The list could be really big.
Here's the solution I come up with. But it's kinds brute force and I was wondering if there's a better way of doing this.
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
total_of_two_items = numbers[i] + numbers[j]
if(total_of_two_items == total_number):
print '{first_item} {second_item}'.format(first_item=i+1, second_item=j+1)
print 'n'
python algorithm
add a comment |
up vote
2
down vote
favorite
Given there's a list of random number and the amount I want to find.
For example:
[80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
And I want two numbers that add up to a given total in this case it's 181. So, it's going to be (92+89). The list could be really big.
Here's the solution I come up with. But it's kinds brute force and I was wondering if there's a better way of doing this.
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
total_of_two_items = numbers[i] + numbers[j]
if(total_of_two_items == total_number):
print '{first_item} {second_item}'.format(first_item=i+1, second_item=j+1)
print 'n'
python algorithm
2
Sort the numbers in ascending order (for example) and then iteratively compare the head and tail of the resulting list. There are only three options: The head is too small, the tail is too big, or they are just right ;-)
– twohundredping
Jun 15 '15 at 18:34
Agree with @twohundredping Also for any given number, you know what its pair would have to be, so you might check if performance is better to search for a specific number int he ordered list. Also once you have chosen a number, you don't have to search the entire list for its pair. Also a pair is the same whether you choose the lower or higher number first, so even for the top for loop, you should probably only search half of an ordered list.
– sunny
Jun 16 '15 at 16:18
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
Given there's a list of random number and the amount I want to find.
For example:
[80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
And I want two numbers that add up to a given total in this case it's 181. So, it's going to be (92+89). The list could be really big.
Here's the solution I come up with. But it's kinds brute force and I was wondering if there's a better way of doing this.
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
total_of_two_items = numbers[i] + numbers[j]
if(total_of_two_items == total_number):
print '{first_item} {second_item}'.format(first_item=i+1, second_item=j+1)
print 'n'
python algorithm
Given there's a list of random number and the amount I want to find.
For example:
[80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
And I want two numbers that add up to a given total in this case it's 181. So, it's going to be (92+89). The list could be really big.
Here's the solution I come up with. But it's kinds brute force and I was wondering if there's a better way of doing this.
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
total_of_two_items = numbers[i] + numbers[j]
if(total_of_two_items == total_number):
print '{first_item} {second_item}'.format(first_item=i+1, second_item=j+1)
print 'n'
python algorithm
python algorithm
edited Jun 15 '15 at 18:45
Jamal♦
30.2k11115226
30.2k11115226
asked Jun 15 '15 at 17:52
toy
4681716
4681716
2
Sort the numbers in ascending order (for example) and then iteratively compare the head and tail of the resulting list. There are only three options: The head is too small, the tail is too big, or they are just right ;-)
– twohundredping
Jun 15 '15 at 18:34
Agree with @twohundredping Also for any given number, you know what its pair would have to be, so you might check if performance is better to search for a specific number int he ordered list. Also once you have chosen a number, you don't have to search the entire list for its pair. Also a pair is the same whether you choose the lower or higher number first, so even for the top for loop, you should probably only search half of an ordered list.
– sunny
Jun 16 '15 at 16:18
add a comment |
2
Sort the numbers in ascending order (for example) and then iteratively compare the head and tail of the resulting list. There are only three options: The head is too small, the tail is too big, or they are just right ;-)
– twohundredping
Jun 15 '15 at 18:34
Agree with @twohundredping Also for any given number, you know what its pair would have to be, so you might check if performance is better to search for a specific number int he ordered list. Also once you have chosen a number, you don't have to search the entire list for its pair. Also a pair is the same whether you choose the lower or higher number first, so even for the top for loop, you should probably only search half of an ordered list.
– sunny
Jun 16 '15 at 16:18
2
2
Sort the numbers in ascending order (for example) and then iteratively compare the head and tail of the resulting list. There are only three options: The head is too small, the tail is too big, or they are just right ;-)
– twohundredping
Jun 15 '15 at 18:34
Sort the numbers in ascending order (for example) and then iteratively compare the head and tail of the resulting list. There are only three options: The head is too small, the tail is too big, or they are just right ;-)
– twohundredping
Jun 15 '15 at 18:34
Agree with @twohundredping Also for any given number, you know what its pair would have to be, so you might check if performance is better to search for a specific number int he ordered list. Also once you have chosen a number, you don't have to search the entire list for its pair. Also a pair is the same whether you choose the lower or higher number first, so even for the top for loop, you should probably only search half of an ordered list.
– sunny
Jun 16 '15 at 16:18
Agree with @twohundredping Also for any given number, you know what its pair would have to be, so you might check if performance is better to search for a specific number int he ordered list. Also once you have chosen a number, you don't have to search the entire list for its pair. Also a pair is the same whether you choose the lower or higher number first, so even for the top for loop, you should probably only search half of an ordered list.
– sunny
Jun 16 '15 at 16:18
add a comment |
5 Answers
5
active
oldest
votes
up vote
1
down vote
accepted
Here is an alternative approach using set logic, which is O(n)
in the average case:
n = 181
n2 = n//2
numbers = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
goodnums = {n-x for x in numbers if x<=n2} & {x for x in numbers if x>n2}
pairs = {(n-x, x) for x in goodnums}
What this does is first filter out values that are greater than 1/2 the target value, since one number in each pair must be that way. Then it subtracts the remaining numbers from the target (in this case 181
). This gets the other value from the pair. Then it uses set logic to extract only those values where the other value is present in the original list of numbers.
So to put it more briefly, it finds all values x
such that 181-x
is also present in the list.
Edit: If you don't want to include cases where both members of the pair are equal and it only exists once, such as n=2
and numbers = [1]
, as Gareth pointed out, add this to the end:
if not n%2 and (n2, n2) in pairs and numbers.count(n2) == 1:
pairs.remove((n2, n2))
This will check if n
is even and, if so, if there is exactly one value where x==n//2
, if so remove (n//2, n//2)
from the results.
This goes wrong in the casen = 2
andnumbers = [1]
.
– Gareth Rees
Jun 15 '15 at 19:17
It returns [(1,1)]. Is this not the expect value in such a case?
– TheBlackCat
Jun 15 '15 at 19:18
numbers
has only one element: it can't possibly contain two numbers that add up to anything. In other words: the items have to be chosen without replacement. See the original post, which is careful only to consider items at different indexes in the list.
– Gareth Rees
Jun 15 '15 at 19:25
@GarethRees I added an optional bit of code to handle this case.
– TheBlackCat
Jun 15 '15 at 19:33
1
@user2023861 I am not using arrays, I am using sets. Yes, for arrays it isO(n*n)
. But for sets the same operation isO(n)
. This is because it uses a hash table for value lookups. Looking up a value in an array is aO(n)
, but looking up a value in a well-structured hash table isO(1)
(in CPython, hash tables of integers are always well-structured). Since it is doing aO(1)
operation on each element of one of the sets, it is aO(1*n)
operation overall, orO(n)
. You can see this in the official python Time Complexity page.
– TheBlackCat
Jun 16 '15 at 13:39
|
show 6 more comments
up vote
0
down vote
We first sort the list and then keep comparing the ends of the sorted list to get those pairs of numbers which sum to a given number. Merge sort has been used here, however any other sorting algorithm can also be used. The main logic is in the find_pairs function.
def mergeSort(A):
if len(A) > 1:
mid = len(A)//2
lefthalf = A[:mid]
righthalf = A[mid:]
mergeSort(lefthalf)
mergeSort(righthalf)
# Merge the halves
i,j,k=0,0,0
while i<len(lefthalf) and j<len(righthalf):
if lefthalf[i] < righthalf[j]:
A[k] = lefthalf[i]
i = i + 1
else:
A[k] = righthalf[j]
j = j + 1
k = k + 1
while i < len(lefthalf):
A[k] = lefthalf[i]
k = k +1
i = i + 1
while j < len(righthalf):
A[k] = righthalf[j]
k = k + 1
j = j + 1
def find_pairs(alist, item):
# We take two flags left and right to point to the ends of the sorted list
left = 0
right = len(alist) - 1
pairs =
while(left<right):
# We find the sum of the numbers in at these two points.
# If the sum is equal to our number for which we are finding pairs, we consider this pair and add it to our results
# If the sum is greater than expected then we move the right pointer one step back to a smaller number and then compute sum again
# If the sum is smaller than expected then we move the left pointer a step ahead and check the sum with a greater number
sum = alist[left] + alist[right]
if sum == item:
pairs += [(alist[left],alist[right])]
# Move the pointers to next elements in the list and find more pairs
right -= 1
left += 1
elif sum > item:
right -= 1
else:
left += 1
return pairs
l1 = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
mergeSort(l1)
print l1
print find_pairs(l1,181)
l2 = [-5,-2, -23, 34,21,90,1,0,65,8,-10]
mergeSort(l2)
print l2
print find_pairs(l2,-2)
The output of the above program is:
[1, 37, 38, 54, 58, 80, 83, 89, 92, 98]
[(83, 98), (89, 92)]
[-23, -10, -5, -2, 0, 1, 8, 21, 34, 65, 90]
[(-23, 21), (-10, 8), (-2, 0)]
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
add a comment |
up vote
0
down vote
I came up with the following, which ends up using a dictionary as a helper to avoid the set operations and extra testing:
def sum_of_pairs_matches(K, arr):
uniques = {i: True for i in arr}
pairs = set()
for val in arr:
k = -val + K if val<K else -K - val
if(uniques.get(k, False)):
pairs.add(tuple(sorted([k,val])))
return pairs
Running:
sum_of_pairs_matches(5, [-5, -4, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
will yield
{(-5, 10), (-4, 9), (-1, 6), (0, 5), (1, 4), (2, 3)}
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
add a comment |
up vote
0
down vote
It's really worth writing this as a function. That will make it easier to test, and it's an opportunity to give it a sensible name and a docstring describing what it does (it's not obvious from your description that you wanted to output the positions of the found numbers, rather than the numbers themselves):
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
total_of_two_items = numbers[i] + numbers[j]
if(total_of_two_items == target):
print('{first_item} {second_item}'.format(first_item=i+1, second_item=j+1))
print('n')
if __name__ == '__main__':
find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89])
We can improve the interface by returning the index pairs instead of printing them. That pares the function down to its essential responsibility, instead of it finding and printing the results:
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
if numbers[i] + numbers[j] == target:
yield [i, j]
if __name__ == '__main__':
for i,j in find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]):
print('{} {}'.format(i+1, j+1))
Now, let's look at the implementation. The first thing that strikes me is that we enumerate numbers
, but never use the item
we obtain. We might as well have written
for i in range(len(numbers)):
Perhaps we could enumerate()
the list once, and use it for both augends:
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
numbers = list(enumerate(numbers))
while numbers:
i, first = numbers.pop(0)
for j, second in numbers:
if first + second == target:
yield [i, j]
We still have an efficiency problem, in that we're adding every possible pair and testing it against the target sum. We can avoid the addition by using a single subtraction outside the loop, but this still requires looking at all pairs, so still scales as O(n²):
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
numbers = list(enumerate(numbers))
while numbers:
i, first = numbers.pop(0)
difference = target - first
for j, second in numbers:
if second == difference:
yield [i, j]
What we really need to do now is to improve our search for difference
. We'll need to use an additional data structure that can locate it in sub-linear time. The obvious choice would be a dict
that maps from value to index; for a general solution, we'll need it to map to a list of indexes, because any number may appear multiple times. We can build such a map quite easily:
index_map = collections.defaultdict(list)
for i, item in enumerate(numbers):
index_map[item].append(i)
The reading is a bit more involved: once we find two values that sum to the target, we need to form all combinations of the first value's indexes and the second value's indexes, like this:
for first, indices in index_map.items():
difference = target - first
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
If we do this, we'll see that we produce every pair twice, once in each order. We can fix this by ignoring the cases where the first is bigger than the second:
for first, indices in index_map.items():
difference = target - first
if first < difference:
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
There's another case we missed, and we can demonstrate with a simple test case:
for i,j in find_2sum((6, [2, 2, 3, 3, 3, 4, 4]):
print('{} {}'.format(i+1, j+1))
Because 3
is exactly half of 6
, we need to enumerate all the combinations of these:
if first == difference:
while indices:
i = indices.pop()
for j in indices:
yield [i, j]
We produce results in somewhat arbitrary order, as we're using an unsorted dict
. If we want a consistent order to the results, the best way is to sort them after they are generated:
for i,j in sorted(sorted(x) for x in find_2sum(6, [2, 2, 3, 3, 3, 4, 4])):
print('{} {}'.format(i+1, j+1))
Full program
import collections
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
index_map = collections.defaultdict(list)
for i, item in enumerate(numbers):
index_map[item].append(i)
# now read from index_map
for first, indices in index_map.items():
difference = target - first
if first == difference:
# return all distinct pairs from indices (we won't need it again)
while indices:
i = indices.pop()
for j in indices:
yield [i, j]
elif first < difference:
# normal case - return all combinations of first and second
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
if __name__ == '__main__':
for i,j in find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]):
print('{} {}'.format(i+1, j+1))
print()
for i,j in sorted(sorted(x) for x in find_2sum(6, [2, 2, 3, 3, 3, 4, 4])):
print('{} {}'.format(i+1, j+1))
add a comment |
up vote
0
down vote
Its faster to check what your desired total (e.g. 181) minus every Listelement is and then see if the answer is also in the list.
def addup(List, K):
for index,item in enumerate(List):
if K - item in List[:index] + List[index+1:]:
return True
return False
X = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
Y = 181
print addup(X,Y)
2
That sounds identical to the approach presented by TheBlackCat..."So to put it more briefly, it finds all values x such that 181-x is also present in the list."
– Sᴀᴍ Onᴇᴌᴀ
Nov 16 at 22:28
Yes, you are right. I didn't check it properly.
– Raini Kandi
Nov 17 at 23:36
add a comment |
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
Here is an alternative approach using set logic, which is O(n)
in the average case:
n = 181
n2 = n//2
numbers = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
goodnums = {n-x for x in numbers if x<=n2} & {x for x in numbers if x>n2}
pairs = {(n-x, x) for x in goodnums}
What this does is first filter out values that are greater than 1/2 the target value, since one number in each pair must be that way. Then it subtracts the remaining numbers from the target (in this case 181
). This gets the other value from the pair. Then it uses set logic to extract only those values where the other value is present in the original list of numbers.
So to put it more briefly, it finds all values x
such that 181-x
is also present in the list.
Edit: If you don't want to include cases where both members of the pair are equal and it only exists once, such as n=2
and numbers = [1]
, as Gareth pointed out, add this to the end:
if not n%2 and (n2, n2) in pairs and numbers.count(n2) == 1:
pairs.remove((n2, n2))
This will check if n
is even and, if so, if there is exactly one value where x==n//2
, if so remove (n//2, n//2)
from the results.
This goes wrong in the casen = 2
andnumbers = [1]
.
– Gareth Rees
Jun 15 '15 at 19:17
It returns [(1,1)]. Is this not the expect value in such a case?
– TheBlackCat
Jun 15 '15 at 19:18
numbers
has only one element: it can't possibly contain two numbers that add up to anything. In other words: the items have to be chosen without replacement. See the original post, which is careful only to consider items at different indexes in the list.
– Gareth Rees
Jun 15 '15 at 19:25
@GarethRees I added an optional bit of code to handle this case.
– TheBlackCat
Jun 15 '15 at 19:33
1
@user2023861 I am not using arrays, I am using sets. Yes, for arrays it isO(n*n)
. But for sets the same operation isO(n)
. This is because it uses a hash table for value lookups. Looking up a value in an array is aO(n)
, but looking up a value in a well-structured hash table isO(1)
(in CPython, hash tables of integers are always well-structured). Since it is doing aO(1)
operation on each element of one of the sets, it is aO(1*n)
operation overall, orO(n)
. You can see this in the official python Time Complexity page.
– TheBlackCat
Jun 16 '15 at 13:39
|
show 6 more comments
up vote
1
down vote
accepted
Here is an alternative approach using set logic, which is O(n)
in the average case:
n = 181
n2 = n//2
numbers = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
goodnums = {n-x for x in numbers if x<=n2} & {x for x in numbers if x>n2}
pairs = {(n-x, x) for x in goodnums}
What this does is first filter out values that are greater than 1/2 the target value, since one number in each pair must be that way. Then it subtracts the remaining numbers from the target (in this case 181
). This gets the other value from the pair. Then it uses set logic to extract only those values where the other value is present in the original list of numbers.
So to put it more briefly, it finds all values x
such that 181-x
is also present in the list.
Edit: If you don't want to include cases where both members of the pair are equal and it only exists once, such as n=2
and numbers = [1]
, as Gareth pointed out, add this to the end:
if not n%2 and (n2, n2) in pairs and numbers.count(n2) == 1:
pairs.remove((n2, n2))
This will check if n
is even and, if so, if there is exactly one value where x==n//2
, if so remove (n//2, n//2)
from the results.
This goes wrong in the casen = 2
andnumbers = [1]
.
– Gareth Rees
Jun 15 '15 at 19:17
It returns [(1,1)]. Is this not the expect value in such a case?
– TheBlackCat
Jun 15 '15 at 19:18
numbers
has only one element: it can't possibly contain two numbers that add up to anything. In other words: the items have to be chosen without replacement. See the original post, which is careful only to consider items at different indexes in the list.
– Gareth Rees
Jun 15 '15 at 19:25
@GarethRees I added an optional bit of code to handle this case.
– TheBlackCat
Jun 15 '15 at 19:33
1
@user2023861 I am not using arrays, I am using sets. Yes, for arrays it isO(n*n)
. But for sets the same operation isO(n)
. This is because it uses a hash table for value lookups. Looking up a value in an array is aO(n)
, but looking up a value in a well-structured hash table isO(1)
(in CPython, hash tables of integers are always well-structured). Since it is doing aO(1)
operation on each element of one of the sets, it is aO(1*n)
operation overall, orO(n)
. You can see this in the official python Time Complexity page.
– TheBlackCat
Jun 16 '15 at 13:39
|
show 6 more comments
up vote
1
down vote
accepted
up vote
1
down vote
accepted
Here is an alternative approach using set logic, which is O(n)
in the average case:
n = 181
n2 = n//2
numbers = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
goodnums = {n-x for x in numbers if x<=n2} & {x for x in numbers if x>n2}
pairs = {(n-x, x) for x in goodnums}
What this does is first filter out values that are greater than 1/2 the target value, since one number in each pair must be that way. Then it subtracts the remaining numbers from the target (in this case 181
). This gets the other value from the pair. Then it uses set logic to extract only those values where the other value is present in the original list of numbers.
So to put it more briefly, it finds all values x
such that 181-x
is also present in the list.
Edit: If you don't want to include cases where both members of the pair are equal and it only exists once, such as n=2
and numbers = [1]
, as Gareth pointed out, add this to the end:
if not n%2 and (n2, n2) in pairs and numbers.count(n2) == 1:
pairs.remove((n2, n2))
This will check if n
is even and, if so, if there is exactly one value where x==n//2
, if so remove (n//2, n//2)
from the results.
Here is an alternative approach using set logic, which is O(n)
in the average case:
n = 181
n2 = n//2
numbers = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
goodnums = {n-x for x in numbers if x<=n2} & {x for x in numbers if x>n2}
pairs = {(n-x, x) for x in goodnums}
What this does is first filter out values that are greater than 1/2 the target value, since one number in each pair must be that way. Then it subtracts the remaining numbers from the target (in this case 181
). This gets the other value from the pair. Then it uses set logic to extract only those values where the other value is present in the original list of numbers.
So to put it more briefly, it finds all values x
such that 181-x
is also present in the list.
Edit: If you don't want to include cases where both members of the pair are equal and it only exists once, such as n=2
and numbers = [1]
, as Gareth pointed out, add this to the end:
if not n%2 and (n2, n2) in pairs and numbers.count(n2) == 1:
pairs.remove((n2, n2))
This will check if n
is even and, if so, if there is exactly one value where x==n//2
, if so remove (n//2, n//2)
from the results.
edited Jun 15 '15 at 20:33
answered Jun 15 '15 at 19:13
TheBlackCat
2,20449
2,20449
This goes wrong in the casen = 2
andnumbers = [1]
.
– Gareth Rees
Jun 15 '15 at 19:17
It returns [(1,1)]. Is this not the expect value in such a case?
– TheBlackCat
Jun 15 '15 at 19:18
numbers
has only one element: it can't possibly contain two numbers that add up to anything. In other words: the items have to be chosen without replacement. See the original post, which is careful only to consider items at different indexes in the list.
– Gareth Rees
Jun 15 '15 at 19:25
@GarethRees I added an optional bit of code to handle this case.
– TheBlackCat
Jun 15 '15 at 19:33
1
@user2023861 I am not using arrays, I am using sets. Yes, for arrays it isO(n*n)
. But for sets the same operation isO(n)
. This is because it uses a hash table for value lookups. Looking up a value in an array is aO(n)
, but looking up a value in a well-structured hash table isO(1)
(in CPython, hash tables of integers are always well-structured). Since it is doing aO(1)
operation on each element of one of the sets, it is aO(1*n)
operation overall, orO(n)
. You can see this in the official python Time Complexity page.
– TheBlackCat
Jun 16 '15 at 13:39
|
show 6 more comments
This goes wrong in the casen = 2
andnumbers = [1]
.
– Gareth Rees
Jun 15 '15 at 19:17
It returns [(1,1)]. Is this not the expect value in such a case?
– TheBlackCat
Jun 15 '15 at 19:18
numbers
has only one element: it can't possibly contain two numbers that add up to anything. In other words: the items have to be chosen without replacement. See the original post, which is careful only to consider items at different indexes in the list.
– Gareth Rees
Jun 15 '15 at 19:25
@GarethRees I added an optional bit of code to handle this case.
– TheBlackCat
Jun 15 '15 at 19:33
1
@user2023861 I am not using arrays, I am using sets. Yes, for arrays it isO(n*n)
. But for sets the same operation isO(n)
. This is because it uses a hash table for value lookups. Looking up a value in an array is aO(n)
, but looking up a value in a well-structured hash table isO(1)
(in CPython, hash tables of integers are always well-structured). Since it is doing aO(1)
operation on each element of one of the sets, it is aO(1*n)
operation overall, orO(n)
. You can see this in the official python Time Complexity page.
– TheBlackCat
Jun 16 '15 at 13:39
This goes wrong in the case
n = 2
and numbers = [1]
.– Gareth Rees
Jun 15 '15 at 19:17
This goes wrong in the case
n = 2
and numbers = [1]
.– Gareth Rees
Jun 15 '15 at 19:17
It returns [(1,1)]. Is this not the expect value in such a case?
– TheBlackCat
Jun 15 '15 at 19:18
It returns [(1,1)]. Is this not the expect value in such a case?
– TheBlackCat
Jun 15 '15 at 19:18
numbers
has only one element: it can't possibly contain two numbers that add up to anything. In other words: the items have to be chosen without replacement. See the original post, which is careful only to consider items at different indexes in the list.– Gareth Rees
Jun 15 '15 at 19:25
numbers
has only one element: it can't possibly contain two numbers that add up to anything. In other words: the items have to be chosen without replacement. See the original post, which is careful only to consider items at different indexes in the list.– Gareth Rees
Jun 15 '15 at 19:25
@GarethRees I added an optional bit of code to handle this case.
– TheBlackCat
Jun 15 '15 at 19:33
@GarethRees I added an optional bit of code to handle this case.
– TheBlackCat
Jun 15 '15 at 19:33
1
1
@user2023861 I am not using arrays, I am using sets. Yes, for arrays it is
O(n*n)
. But for sets the same operation is O(n)
. This is because it uses a hash table for value lookups. Looking up a value in an array is a O(n)
, but looking up a value in a well-structured hash table is O(1)
(in CPython, hash tables of integers are always well-structured). Since it is doing a O(1)
operation on each element of one of the sets, it is a O(1*n)
operation overall, or O(n)
. You can see this in the official python Time Complexity page.– TheBlackCat
Jun 16 '15 at 13:39
@user2023861 I am not using arrays, I am using sets. Yes, for arrays it is
O(n*n)
. But for sets the same operation is O(n)
. This is because it uses a hash table for value lookups. Looking up a value in an array is a O(n)
, but looking up a value in a well-structured hash table is O(1)
(in CPython, hash tables of integers are always well-structured). Since it is doing a O(1)
operation on each element of one of the sets, it is a O(1*n)
operation overall, or O(n)
. You can see this in the official python Time Complexity page.– TheBlackCat
Jun 16 '15 at 13:39
|
show 6 more comments
up vote
0
down vote
We first sort the list and then keep comparing the ends of the sorted list to get those pairs of numbers which sum to a given number. Merge sort has been used here, however any other sorting algorithm can also be used. The main logic is in the find_pairs function.
def mergeSort(A):
if len(A) > 1:
mid = len(A)//2
lefthalf = A[:mid]
righthalf = A[mid:]
mergeSort(lefthalf)
mergeSort(righthalf)
# Merge the halves
i,j,k=0,0,0
while i<len(lefthalf) and j<len(righthalf):
if lefthalf[i] < righthalf[j]:
A[k] = lefthalf[i]
i = i + 1
else:
A[k] = righthalf[j]
j = j + 1
k = k + 1
while i < len(lefthalf):
A[k] = lefthalf[i]
k = k +1
i = i + 1
while j < len(righthalf):
A[k] = righthalf[j]
k = k + 1
j = j + 1
def find_pairs(alist, item):
# We take two flags left and right to point to the ends of the sorted list
left = 0
right = len(alist) - 1
pairs =
while(left<right):
# We find the sum of the numbers in at these two points.
# If the sum is equal to our number for which we are finding pairs, we consider this pair and add it to our results
# If the sum is greater than expected then we move the right pointer one step back to a smaller number and then compute sum again
# If the sum is smaller than expected then we move the left pointer a step ahead and check the sum with a greater number
sum = alist[left] + alist[right]
if sum == item:
pairs += [(alist[left],alist[right])]
# Move the pointers to next elements in the list and find more pairs
right -= 1
left += 1
elif sum > item:
right -= 1
else:
left += 1
return pairs
l1 = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
mergeSort(l1)
print l1
print find_pairs(l1,181)
l2 = [-5,-2, -23, 34,21,90,1,0,65,8,-10]
mergeSort(l2)
print l2
print find_pairs(l2,-2)
The output of the above program is:
[1, 37, 38, 54, 58, 80, 83, 89, 92, 98]
[(83, 98), (89, 92)]
[-23, -10, -5, -2, 0, 1, 8, 21, 34, 65, 90]
[(-23, 21), (-10, 8), (-2, 0)]
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
add a comment |
up vote
0
down vote
We first sort the list and then keep comparing the ends of the sorted list to get those pairs of numbers which sum to a given number. Merge sort has been used here, however any other sorting algorithm can also be used. The main logic is in the find_pairs function.
def mergeSort(A):
if len(A) > 1:
mid = len(A)//2
lefthalf = A[:mid]
righthalf = A[mid:]
mergeSort(lefthalf)
mergeSort(righthalf)
# Merge the halves
i,j,k=0,0,0
while i<len(lefthalf) and j<len(righthalf):
if lefthalf[i] < righthalf[j]:
A[k] = lefthalf[i]
i = i + 1
else:
A[k] = righthalf[j]
j = j + 1
k = k + 1
while i < len(lefthalf):
A[k] = lefthalf[i]
k = k +1
i = i + 1
while j < len(righthalf):
A[k] = righthalf[j]
k = k + 1
j = j + 1
def find_pairs(alist, item):
# We take two flags left and right to point to the ends of the sorted list
left = 0
right = len(alist) - 1
pairs =
while(left<right):
# We find the sum of the numbers in at these two points.
# If the sum is equal to our number for which we are finding pairs, we consider this pair and add it to our results
# If the sum is greater than expected then we move the right pointer one step back to a smaller number and then compute sum again
# If the sum is smaller than expected then we move the left pointer a step ahead and check the sum with a greater number
sum = alist[left] + alist[right]
if sum == item:
pairs += [(alist[left],alist[right])]
# Move the pointers to next elements in the list and find more pairs
right -= 1
left += 1
elif sum > item:
right -= 1
else:
left += 1
return pairs
l1 = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
mergeSort(l1)
print l1
print find_pairs(l1,181)
l2 = [-5,-2, -23, 34,21,90,1,0,65,8,-10]
mergeSort(l2)
print l2
print find_pairs(l2,-2)
The output of the above program is:
[1, 37, 38, 54, 58, 80, 83, 89, 92, 98]
[(83, 98), (89, 92)]
[-23, -10, -5, -2, 0, 1, 8, 21, 34, 65, 90]
[(-23, 21), (-10, 8), (-2, 0)]
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
add a comment |
up vote
0
down vote
up vote
0
down vote
We first sort the list and then keep comparing the ends of the sorted list to get those pairs of numbers which sum to a given number. Merge sort has been used here, however any other sorting algorithm can also be used. The main logic is in the find_pairs function.
def mergeSort(A):
if len(A) > 1:
mid = len(A)//2
lefthalf = A[:mid]
righthalf = A[mid:]
mergeSort(lefthalf)
mergeSort(righthalf)
# Merge the halves
i,j,k=0,0,0
while i<len(lefthalf) and j<len(righthalf):
if lefthalf[i] < righthalf[j]:
A[k] = lefthalf[i]
i = i + 1
else:
A[k] = righthalf[j]
j = j + 1
k = k + 1
while i < len(lefthalf):
A[k] = lefthalf[i]
k = k +1
i = i + 1
while j < len(righthalf):
A[k] = righthalf[j]
k = k + 1
j = j + 1
def find_pairs(alist, item):
# We take two flags left and right to point to the ends of the sorted list
left = 0
right = len(alist) - 1
pairs =
while(left<right):
# We find the sum of the numbers in at these two points.
# If the sum is equal to our number for which we are finding pairs, we consider this pair and add it to our results
# If the sum is greater than expected then we move the right pointer one step back to a smaller number and then compute sum again
# If the sum is smaller than expected then we move the left pointer a step ahead and check the sum with a greater number
sum = alist[left] + alist[right]
if sum == item:
pairs += [(alist[left],alist[right])]
# Move the pointers to next elements in the list and find more pairs
right -= 1
left += 1
elif sum > item:
right -= 1
else:
left += 1
return pairs
l1 = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
mergeSort(l1)
print l1
print find_pairs(l1,181)
l2 = [-5,-2, -23, 34,21,90,1,0,65,8,-10]
mergeSort(l2)
print l2
print find_pairs(l2,-2)
The output of the above program is:
[1, 37, 38, 54, 58, 80, 83, 89, 92, 98]
[(83, 98), (89, 92)]
[-23, -10, -5, -2, 0, 1, 8, 21, 34, 65, 90]
[(-23, 21), (-10, 8), (-2, 0)]
We first sort the list and then keep comparing the ends of the sorted list to get those pairs of numbers which sum to a given number. Merge sort has been used here, however any other sorting algorithm can also be used. The main logic is in the find_pairs function.
def mergeSort(A):
if len(A) > 1:
mid = len(A)//2
lefthalf = A[:mid]
righthalf = A[mid:]
mergeSort(lefthalf)
mergeSort(righthalf)
# Merge the halves
i,j,k=0,0,0
while i<len(lefthalf) and j<len(righthalf):
if lefthalf[i] < righthalf[j]:
A[k] = lefthalf[i]
i = i + 1
else:
A[k] = righthalf[j]
j = j + 1
k = k + 1
while i < len(lefthalf):
A[k] = lefthalf[i]
k = k +1
i = i + 1
while j < len(righthalf):
A[k] = righthalf[j]
k = k + 1
j = j + 1
def find_pairs(alist, item):
# We take two flags left and right to point to the ends of the sorted list
left = 0
right = len(alist) - 1
pairs =
while(left<right):
# We find the sum of the numbers in at these two points.
# If the sum is equal to our number for which we are finding pairs, we consider this pair and add it to our results
# If the sum is greater than expected then we move the right pointer one step back to a smaller number and then compute sum again
# If the sum is smaller than expected then we move the left pointer a step ahead and check the sum with a greater number
sum = alist[left] + alist[right]
if sum == item:
pairs += [(alist[left],alist[right])]
# Move the pointers to next elements in the list and find more pairs
right -= 1
left += 1
elif sum > item:
right -= 1
else:
left += 1
return pairs
l1 = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
mergeSort(l1)
print l1
print find_pairs(l1,181)
l2 = [-5,-2, -23, 34,21,90,1,0,65,8,-10]
mergeSort(l2)
print l2
print find_pairs(l2,-2)
The output of the above program is:
[1, 37, 38, 54, 58, 80, 83, 89, 92, 98]
[(83, 98), (89, 92)]
[-23, -10, -5, -2, 0, 1, 8, 21, 34, 65, 90]
[(-23, 21), (-10, 8), (-2, 0)]
answered Dec 1 '16 at 19:30
Sonal Dubey
91
91
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
add a comment |
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
add a comment |
up vote
0
down vote
I came up with the following, which ends up using a dictionary as a helper to avoid the set operations and extra testing:
def sum_of_pairs_matches(K, arr):
uniques = {i: True for i in arr}
pairs = set()
for val in arr:
k = -val + K if val<K else -K - val
if(uniques.get(k, False)):
pairs.add(tuple(sorted([k,val])))
return pairs
Running:
sum_of_pairs_matches(5, [-5, -4, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
will yield
{(-5, 10), (-4, 9), (-1, 6), (0, 5), (1, 4), (2, 3)}
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
add a comment |
up vote
0
down vote
I came up with the following, which ends up using a dictionary as a helper to avoid the set operations and extra testing:
def sum_of_pairs_matches(K, arr):
uniques = {i: True for i in arr}
pairs = set()
for val in arr:
k = -val + K if val<K else -K - val
if(uniques.get(k, False)):
pairs.add(tuple(sorted([k,val])))
return pairs
Running:
sum_of_pairs_matches(5, [-5, -4, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
will yield
{(-5, 10), (-4, 9), (-1, 6), (0, 5), (1, 4), (2, 3)}
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
add a comment |
up vote
0
down vote
up vote
0
down vote
I came up with the following, which ends up using a dictionary as a helper to avoid the set operations and extra testing:
def sum_of_pairs_matches(K, arr):
uniques = {i: True for i in arr}
pairs = set()
for val in arr:
k = -val + K if val<K else -K - val
if(uniques.get(k, False)):
pairs.add(tuple(sorted([k,val])))
return pairs
Running:
sum_of_pairs_matches(5, [-5, -4, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
will yield
{(-5, 10), (-4, 9), (-1, 6), (0, 5), (1, 4), (2, 3)}
I came up with the following, which ends up using a dictionary as a helper to avoid the set operations and extra testing:
def sum_of_pairs_matches(K, arr):
uniques = {i: True for i in arr}
pairs = set()
for val in arr:
k = -val + K if val<K else -K - val
if(uniques.get(k, False)):
pairs.add(tuple(sorted([k,val])))
return pairs
Running:
sum_of_pairs_matches(5, [-5, -4, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
will yield
{(-5, 10), (-4, 9), (-1, 6), (0, 5), (1, 4), (2, 3)}
edited Jan 18 at 23:22
Jamal♦
30.2k11115226
30.2k11115226
answered Jan 18 at 21:26
wgrampon
1
1
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
add a comment |
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
This appears to output the values that sum to the target total, rather than the indices of those values as in the original code.
– Toby Speight
Oct 25 at 13:38
add a comment |
up vote
0
down vote
It's really worth writing this as a function. That will make it easier to test, and it's an opportunity to give it a sensible name and a docstring describing what it does (it's not obvious from your description that you wanted to output the positions of the found numbers, rather than the numbers themselves):
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
total_of_two_items = numbers[i] + numbers[j]
if(total_of_two_items == target):
print('{first_item} {second_item}'.format(first_item=i+1, second_item=j+1))
print('n')
if __name__ == '__main__':
find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89])
We can improve the interface by returning the index pairs instead of printing them. That pares the function down to its essential responsibility, instead of it finding and printing the results:
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
if numbers[i] + numbers[j] == target:
yield [i, j]
if __name__ == '__main__':
for i,j in find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]):
print('{} {}'.format(i+1, j+1))
Now, let's look at the implementation. The first thing that strikes me is that we enumerate numbers
, but never use the item
we obtain. We might as well have written
for i in range(len(numbers)):
Perhaps we could enumerate()
the list once, and use it for both augends:
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
numbers = list(enumerate(numbers))
while numbers:
i, first = numbers.pop(0)
for j, second in numbers:
if first + second == target:
yield [i, j]
We still have an efficiency problem, in that we're adding every possible pair and testing it against the target sum. We can avoid the addition by using a single subtraction outside the loop, but this still requires looking at all pairs, so still scales as O(n²):
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
numbers = list(enumerate(numbers))
while numbers:
i, first = numbers.pop(0)
difference = target - first
for j, second in numbers:
if second == difference:
yield [i, j]
What we really need to do now is to improve our search for difference
. We'll need to use an additional data structure that can locate it in sub-linear time. The obvious choice would be a dict
that maps from value to index; for a general solution, we'll need it to map to a list of indexes, because any number may appear multiple times. We can build such a map quite easily:
index_map = collections.defaultdict(list)
for i, item in enumerate(numbers):
index_map[item].append(i)
The reading is a bit more involved: once we find two values that sum to the target, we need to form all combinations of the first value's indexes and the second value's indexes, like this:
for first, indices in index_map.items():
difference = target - first
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
If we do this, we'll see that we produce every pair twice, once in each order. We can fix this by ignoring the cases where the first is bigger than the second:
for first, indices in index_map.items():
difference = target - first
if first < difference:
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
There's another case we missed, and we can demonstrate with a simple test case:
for i,j in find_2sum((6, [2, 2, 3, 3, 3, 4, 4]):
print('{} {}'.format(i+1, j+1))
Because 3
is exactly half of 6
, we need to enumerate all the combinations of these:
if first == difference:
while indices:
i = indices.pop()
for j in indices:
yield [i, j]
We produce results in somewhat arbitrary order, as we're using an unsorted dict
. If we want a consistent order to the results, the best way is to sort them after they are generated:
for i,j in sorted(sorted(x) for x in find_2sum(6, [2, 2, 3, 3, 3, 4, 4])):
print('{} {}'.format(i+1, j+1))
Full program
import collections
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
index_map = collections.defaultdict(list)
for i, item in enumerate(numbers):
index_map[item].append(i)
# now read from index_map
for first, indices in index_map.items():
difference = target - first
if first == difference:
# return all distinct pairs from indices (we won't need it again)
while indices:
i = indices.pop()
for j in indices:
yield [i, j]
elif first < difference:
# normal case - return all combinations of first and second
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
if __name__ == '__main__':
for i,j in find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]):
print('{} {}'.format(i+1, j+1))
print()
for i,j in sorted(sorted(x) for x in find_2sum(6, [2, 2, 3, 3, 3, 4, 4])):
print('{} {}'.format(i+1, j+1))
add a comment |
up vote
0
down vote
It's really worth writing this as a function. That will make it easier to test, and it's an opportunity to give it a sensible name and a docstring describing what it does (it's not obvious from your description that you wanted to output the positions of the found numbers, rather than the numbers themselves):
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
total_of_two_items = numbers[i] + numbers[j]
if(total_of_two_items == target):
print('{first_item} {second_item}'.format(first_item=i+1, second_item=j+1))
print('n')
if __name__ == '__main__':
find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89])
We can improve the interface by returning the index pairs instead of printing them. That pares the function down to its essential responsibility, instead of it finding and printing the results:
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
if numbers[i] + numbers[j] == target:
yield [i, j]
if __name__ == '__main__':
for i,j in find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]):
print('{} {}'.format(i+1, j+1))
Now, let's look at the implementation. The first thing that strikes me is that we enumerate numbers
, but never use the item
we obtain. We might as well have written
for i in range(len(numbers)):
Perhaps we could enumerate()
the list once, and use it for both augends:
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
numbers = list(enumerate(numbers))
while numbers:
i, first = numbers.pop(0)
for j, second in numbers:
if first + second == target:
yield [i, j]
We still have an efficiency problem, in that we're adding every possible pair and testing it against the target sum. We can avoid the addition by using a single subtraction outside the loop, but this still requires looking at all pairs, so still scales as O(n²):
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
numbers = list(enumerate(numbers))
while numbers:
i, first = numbers.pop(0)
difference = target - first
for j, second in numbers:
if second == difference:
yield [i, j]
What we really need to do now is to improve our search for difference
. We'll need to use an additional data structure that can locate it in sub-linear time. The obvious choice would be a dict
that maps from value to index; for a general solution, we'll need it to map to a list of indexes, because any number may appear multiple times. We can build such a map quite easily:
index_map = collections.defaultdict(list)
for i, item in enumerate(numbers):
index_map[item].append(i)
The reading is a bit more involved: once we find two values that sum to the target, we need to form all combinations of the first value's indexes and the second value's indexes, like this:
for first, indices in index_map.items():
difference = target - first
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
If we do this, we'll see that we produce every pair twice, once in each order. We can fix this by ignoring the cases where the first is bigger than the second:
for first, indices in index_map.items():
difference = target - first
if first < difference:
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
There's another case we missed, and we can demonstrate with a simple test case:
for i,j in find_2sum((6, [2, 2, 3, 3, 3, 4, 4]):
print('{} {}'.format(i+1, j+1))
Because 3
is exactly half of 6
, we need to enumerate all the combinations of these:
if first == difference:
while indices:
i = indices.pop()
for j in indices:
yield [i, j]
We produce results in somewhat arbitrary order, as we're using an unsorted dict
. If we want a consistent order to the results, the best way is to sort them after they are generated:
for i,j in sorted(sorted(x) for x in find_2sum(6, [2, 2, 3, 3, 3, 4, 4])):
print('{} {}'.format(i+1, j+1))
Full program
import collections
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
index_map = collections.defaultdict(list)
for i, item in enumerate(numbers):
index_map[item].append(i)
# now read from index_map
for first, indices in index_map.items():
difference = target - first
if first == difference:
# return all distinct pairs from indices (we won't need it again)
while indices:
i = indices.pop()
for j in indices:
yield [i, j]
elif first < difference:
# normal case - return all combinations of first and second
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
if __name__ == '__main__':
for i,j in find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]):
print('{} {}'.format(i+1, j+1))
print()
for i,j in sorted(sorted(x) for x in find_2sum(6, [2, 2, 3, 3, 3, 4, 4])):
print('{} {}'.format(i+1, j+1))
add a comment |
up vote
0
down vote
up vote
0
down vote
It's really worth writing this as a function. That will make it easier to test, and it's an opportunity to give it a sensible name and a docstring describing what it does (it's not obvious from your description that you wanted to output the positions of the found numbers, rather than the numbers themselves):
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
total_of_two_items = numbers[i] + numbers[j]
if(total_of_two_items == target):
print('{first_item} {second_item}'.format(first_item=i+1, second_item=j+1))
print('n')
if __name__ == '__main__':
find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89])
We can improve the interface by returning the index pairs instead of printing them. That pares the function down to its essential responsibility, instead of it finding and printing the results:
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
if numbers[i] + numbers[j] == target:
yield [i, j]
if __name__ == '__main__':
for i,j in find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]):
print('{} {}'.format(i+1, j+1))
Now, let's look at the implementation. The first thing that strikes me is that we enumerate numbers
, but never use the item
we obtain. We might as well have written
for i in range(len(numbers)):
Perhaps we could enumerate()
the list once, and use it for both augends:
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
numbers = list(enumerate(numbers))
while numbers:
i, first = numbers.pop(0)
for j, second in numbers:
if first + second == target:
yield [i, j]
We still have an efficiency problem, in that we're adding every possible pair and testing it against the target sum. We can avoid the addition by using a single subtraction outside the loop, but this still requires looking at all pairs, so still scales as O(n²):
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
numbers = list(enumerate(numbers))
while numbers:
i, first = numbers.pop(0)
difference = target - first
for j, second in numbers:
if second == difference:
yield [i, j]
What we really need to do now is to improve our search for difference
. We'll need to use an additional data structure that can locate it in sub-linear time. The obvious choice would be a dict
that maps from value to index; for a general solution, we'll need it to map to a list of indexes, because any number may appear multiple times. We can build such a map quite easily:
index_map = collections.defaultdict(list)
for i, item in enumerate(numbers):
index_map[item].append(i)
The reading is a bit more involved: once we find two values that sum to the target, we need to form all combinations of the first value's indexes and the second value's indexes, like this:
for first, indices in index_map.items():
difference = target - first
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
If we do this, we'll see that we produce every pair twice, once in each order. We can fix this by ignoring the cases where the first is bigger than the second:
for first, indices in index_map.items():
difference = target - first
if first < difference:
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
There's another case we missed, and we can demonstrate with a simple test case:
for i,j in find_2sum((6, [2, 2, 3, 3, 3, 4, 4]):
print('{} {}'.format(i+1, j+1))
Because 3
is exactly half of 6
, we need to enumerate all the combinations of these:
if first == difference:
while indices:
i = indices.pop()
for j in indices:
yield [i, j]
We produce results in somewhat arbitrary order, as we're using an unsorted dict
. If we want a consistent order to the results, the best way is to sort them after they are generated:
for i,j in sorted(sorted(x) for x in find_2sum(6, [2, 2, 3, 3, 3, 4, 4])):
print('{} {}'.format(i+1, j+1))
Full program
import collections
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
index_map = collections.defaultdict(list)
for i, item in enumerate(numbers):
index_map[item].append(i)
# now read from index_map
for first, indices in index_map.items():
difference = target - first
if first == difference:
# return all distinct pairs from indices (we won't need it again)
while indices:
i = indices.pop()
for j in indices:
yield [i, j]
elif first < difference:
# normal case - return all combinations of first and second
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
if __name__ == '__main__':
for i,j in find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]):
print('{} {}'.format(i+1, j+1))
print()
for i,j in sorted(sorted(x) for x in find_2sum(6, [2, 2, 3, 3, 3, 4, 4])):
print('{} {}'.format(i+1, j+1))
It's really worth writing this as a function. That will make it easier to test, and it's an opportunity to give it a sensible name and a docstring describing what it does (it's not obvious from your description that you wanted to output the positions of the found numbers, rather than the numbers themselves):
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
total_of_two_items = numbers[i] + numbers[j]
if(total_of_two_items == target):
print('{first_item} {second_item}'.format(first_item=i+1, second_item=j+1))
print('n')
if __name__ == '__main__':
find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89])
We can improve the interface by returning the index pairs instead of printing them. That pares the function down to its essential responsibility, instead of it finding and printing the results:
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
for i, item in enumerate(numbers):
for j in range(i+1, len(numbers)):
if numbers[i] + numbers[j] == target:
yield [i, j]
if __name__ == '__main__':
for i,j in find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]):
print('{} {}'.format(i+1, j+1))
Now, let's look at the implementation. The first thing that strikes me is that we enumerate numbers
, but never use the item
we obtain. We might as well have written
for i in range(len(numbers)):
Perhaps we could enumerate()
the list once, and use it for both augends:
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
numbers = list(enumerate(numbers))
while numbers:
i, first = numbers.pop(0)
for j, second in numbers:
if first + second == target:
yield [i, j]
We still have an efficiency problem, in that we're adding every possible pair and testing it against the target sum. We can avoid the addition by using a single subtraction outside the loop, but this still requires looking at all pairs, so still scales as O(n²):
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
numbers = list(enumerate(numbers))
while numbers:
i, first = numbers.pop(0)
difference = target - first
for j, second in numbers:
if second == difference:
yield [i, j]
What we really need to do now is to improve our search for difference
. We'll need to use an additional data structure that can locate it in sub-linear time. The obvious choice would be a dict
that maps from value to index; for a general solution, we'll need it to map to a list of indexes, because any number may appear multiple times. We can build such a map quite easily:
index_map = collections.defaultdict(list)
for i, item in enumerate(numbers):
index_map[item].append(i)
The reading is a bit more involved: once we find two values that sum to the target, we need to form all combinations of the first value's indexes and the second value's indexes, like this:
for first, indices in index_map.items():
difference = target - first
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
If we do this, we'll see that we produce every pair twice, once in each order. We can fix this by ignoring the cases where the first is bigger than the second:
for first, indices in index_map.items():
difference = target - first
if first < difference:
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
There's another case we missed, and we can demonstrate with a simple test case:
for i,j in find_2sum((6, [2, 2, 3, 3, 3, 4, 4]):
print('{} {}'.format(i+1, j+1))
Because 3
is exactly half of 6
, we need to enumerate all the combinations of these:
if first == difference:
while indices:
i = indices.pop()
for j in indices:
yield [i, j]
We produce results in somewhat arbitrary order, as we're using an unsorted dict
. If we want a consistent order to the results, the best way is to sort them after they are generated:
for i,j in sorted(sorted(x) for x in find_2sum(6, [2, 2, 3, 3, 3, 4, 4])):
print('{} {}'.format(i+1, j+1))
Full program
import collections
def find_2sum(target, numbers):
"""Find indexes of pairs from NUMBERS that add to TARGET"""
index_map = collections.defaultdict(list)
for i, item in enumerate(numbers):
index_map[item].append(i)
# now read from index_map
for first, indices in index_map.items():
difference = target - first
if first == difference:
# return all distinct pairs from indices (we won't need it again)
while indices:
i = indices.pop()
for j in indices:
yield [i, j]
elif first < difference:
# normal case - return all combinations of first and second
other_indices = index_map.get(difference, )
for i in indices:
for j in other_indices:
yield [i, j]
if __name__ == '__main__':
for i,j in find_2sum(181, [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]):
print('{} {}'.format(i+1, j+1))
print()
for i,j in sorted(sorted(x) for x in find_2sum(6, [2, 2, 3, 3, 3, 4, 4])):
print('{} {}'.format(i+1, j+1))
edited Oct 25 at 15:17
answered Oct 25 at 13:36
Toby Speight
22.4k537109
22.4k537109
add a comment |
add a comment |
up vote
0
down vote
Its faster to check what your desired total (e.g. 181) minus every Listelement is and then see if the answer is also in the list.
def addup(List, K):
for index,item in enumerate(List):
if K - item in List[:index] + List[index+1:]:
return True
return False
X = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
Y = 181
print addup(X,Y)
2
That sounds identical to the approach presented by TheBlackCat..."So to put it more briefly, it finds all values x such that 181-x is also present in the list."
– Sᴀᴍ Onᴇᴌᴀ
Nov 16 at 22:28
Yes, you are right. I didn't check it properly.
– Raini Kandi
Nov 17 at 23:36
add a comment |
up vote
0
down vote
Its faster to check what your desired total (e.g. 181) minus every Listelement is and then see if the answer is also in the list.
def addup(List, K):
for index,item in enumerate(List):
if K - item in List[:index] + List[index+1:]:
return True
return False
X = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
Y = 181
print addup(X,Y)
2
That sounds identical to the approach presented by TheBlackCat..."So to put it more briefly, it finds all values x such that 181-x is also present in the list."
– Sᴀᴍ Onᴇᴌᴀ
Nov 16 at 22:28
Yes, you are right. I didn't check it properly.
– Raini Kandi
Nov 17 at 23:36
add a comment |
up vote
0
down vote
up vote
0
down vote
Its faster to check what your desired total (e.g. 181) minus every Listelement is and then see if the answer is also in the list.
def addup(List, K):
for index,item in enumerate(List):
if K - item in List[:index] + List[index+1:]:
return True
return False
X = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
Y = 181
print addup(X,Y)
Its faster to check what your desired total (e.g. 181) minus every Listelement is and then see if the answer is also in the list.
def addup(List, K):
for index,item in enumerate(List):
if K - item in List[:index] + List[index+1:]:
return True
return False
X = [80, 98, 83, 92, 1, 38, 37, 54, 58, 89]
Y = 181
print addup(X,Y)
edited Nov 16 at 22:26
Sᴀᴍ Onᴇᴌᴀ
7,73061748
7,73061748
answered Nov 16 at 22:06
Raini Kandi
1
1
2
That sounds identical to the approach presented by TheBlackCat..."So to put it more briefly, it finds all values x such that 181-x is also present in the list."
– Sᴀᴍ Onᴇᴌᴀ
Nov 16 at 22:28
Yes, you are right. I didn't check it properly.
– Raini Kandi
Nov 17 at 23:36
add a comment |
2
That sounds identical to the approach presented by TheBlackCat..."So to put it more briefly, it finds all values x such that 181-x is also present in the list."
– Sᴀᴍ Onᴇᴌᴀ
Nov 16 at 22:28
Yes, you are right. I didn't check it properly.
– Raini Kandi
Nov 17 at 23:36
2
2
That sounds identical to the approach presented by TheBlackCat..."So to put it more briefly, it finds all values x such that 181-x is also present in the list."
– Sᴀᴍ Onᴇᴌᴀ
Nov 16 at 22:28
That sounds identical to the approach presented by TheBlackCat..."So to put it more briefly, it finds all values x such that 181-x is also present in the list."
– Sᴀᴍ Onᴇᴌᴀ
Nov 16 at 22:28
Yes, you are right. I didn't check it properly.
– Raini Kandi
Nov 17 at 23:36
Yes, you are right. I didn't check it properly.
– Raini Kandi
Nov 17 at 23:36
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f93661%2ffinding-two-numbers-that-add-to-a-given-total%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
2
Sort the numbers in ascending order (for example) and then iteratively compare the head and tail of the resulting list. There are only three options: The head is too small, the tail is too big, or they are just right ;-)
– twohundredping
Jun 15 '15 at 18:34
Agree with @twohundredping Also for any given number, you know what its pair would have to be, so you might check if performance is better to search for a specific number int he ordered list. Also once you have chosen a number, you don't have to search the entire list for its pair. Also a pair is the same whether you choose the lower or higher number first, so even for the top for loop, you should probably only search half of an ordered list.
– sunny
Jun 16 '15 at 16:18