In addition to the improvements Winston made in his answer his answer, I would add:
In addition to the improvements Winston made in his answer, I would add:
In addition to the improvements Winston made in his answer, I would add:
Your function needs a docstring. What does it do and how do you call it?
It's better to raise an exception rather than return an exceptional value as a result. It's too easy for the caller to omit the check for the exceptional value.
The number zero is arbitrary, so why not generalize the code so that can find triples that sum to any target number?
It's not clear to me what the purpose of
a.sort(reverse=True)
is. This has the side-effect of sorting the inputa
into reverse order, which is probably not what the caller expected. If you really need to processa
in reverse order, you should write something like:a = sorted(a, reverse=True)
from itertools import combinations
class NotFoundError(Exception):
pass
def sum_to_zerotriple_with_sum(a, target=0):
"""Return a tuple of three numbers in the sequence a that sum to
zerotarget (default: 0). Raise NotFoundError if allno triple sums ofto
triples are non-zero target.
"""
for triplettriple in combinations(a, 3):
if sum(triplettriple) == 0target:
return triplettriple
raise NotFoundError('No triplettriple sums to zero{}.'.format(target))
from collections import defaultdict
def sum_to_zero2triple_with_sum2(a, target=0):
"""Return a tuple of three numbers in the sequence a that sum to
zerotarget (default: 0). Raise NotFoundError if allno triple sums ofto
triples are non-zero target.
"""
positions = defaultdict(set)
for i, n in enumerate(a):
positions[n].add(i)
for (i, ai), (j, aj) in combinations(range(lenenumerate(a)), 2):
n = target -a[i] ai - a[j]aj
if positions[n].difference((i, j)):
return n, a[i]ai, a[j]aj
raise NotFoundError('No triplettriple sums to zero{}.'.format(target))
from random import randint
from timeit import timeit
def test(n, m, number=100cases=100):
"""Create numbercases (default: 100) random testcases and run both algorithms on them. Each test case consists of n numbers in the range [-m, m].
"""
testcases = [[randint(-m, m) for _ in xrangerange(n)] for _ in xrangerange(numbercases)]
for algorithm in sum_to_zerotriple_with_sum, sum_to_zero2triple_with_sum2:
def run():
for testcase in testcases:
try:
assert(sum(algorithm(testcase)) == 0)
except NotFoundError:
pass
print(algorithm.__name__, timeit(run, number=1))
The sum_to_zerotriple_with_sum
algorithm has the advantage that it might terminate early (after examining only a few triples), whereas sum_to_zero2triple_with_sum2
takes Ω(n) even in the best case, because it builds the dictionary of positions before starting its search. This means that sum_to_zerotriple_with_sum
runs faster than sum_to_zero2triple_with_sum2
for test cases where there are many triples that sum to zero:
>>> test(1000, 100)
sum_to_zerotriple_with_sum 0.017406940460203754958091303706
sum_to_zero2triple_with_sum2 0.047677993774405412021093070507
But when triples that sum to zero are rare, the better asymptotic performance of sum_to_zero2triple_with_sum2
wins big:
>>> test(1000, 1000)
sum_to_zerotriple_with_sum 0.086078882217410221871780231595
sum_to_zero2triple_with_sum2 0.067800045013407950551761314273
>>> test(1000, 10000)
sum_to_zerotriple_with_sum 0.9181730747229003877746872604
sum_to_zero2triple_with_sum2 0.075778961181608799532195553184
>>> test(1000, 100000)
sum_to_zerotriple_with_sum 9.44526004791874092630110681
sum_to_zero2triple_with_sum2 0.13381314277614182585570961237
>>> test(1000, 1000000)
sum_to_zerotriple_with_sum 8497.022378921511457079928368
sum_to_zero2triple_with_sum2 01.9465799331670804437939077616
Your function needs a docstring. What does it do and how do you call it?
It's better to raise an exception rather than return an exceptional value as a result. It's too easy for the caller to omit the check for the exceptional value.
It's not clear to me what the purpose of
a.sort(reverse=True)
is. This has the side-effect of sorting the inputa
into reverse order, which is probably not what the caller expected. If you really need to processa
in reverse order, you should write something like:a = sorted(a, reverse=True)
from itertools import combinations
class NotFoundError(Exception):
pass
def sum_to_zero(a):
"""Return a tuple of three numbers in the sequence a that sum to
zero. Raise NotFoundError if all sums of triples are non-zero.
"""
for triplet in combinations(a, 3):
if sum(triplet) == 0:
return triplet
raise NotFoundError('No triplet sums to zero.')
from collections import defaultdict
def sum_to_zero2(a):
"""Return a tuple of three numbers in the sequence a that sum to
zero. Raise NotFoundError if all sums of triples are non-zero.
"""
positions = defaultdict(set)
for i, n in enumerate(a):
positions[n].add(i)
for i, j in combinations(range(len(a)), 2):
n = -a[i] - a[j]
if positions[n].difference((i, j)):
return n, a[i], a[j]
raise NotFoundError('No triplet sums to zero.')
from random import randint
from timeit import timeit
def test(n, m, number=100):
"""Create number random testcases and run both algorithms on them. Each test case consists of n numbers in the range [-m, m].
"""
testcases = [[randint(-m, m) for _ in xrange(n)] for _ in xrange(number)]
for algorithm in sum_to_zero, sum_to_zero2:
def run():
for testcase in testcases:
try:
assert(sum(algorithm(testcase)) == 0)
except NotFoundError:
pass
print(algorithm.__name__, timeit(run, number=1))
The sum_to_zero
algorithm has the advantage that it might terminate early (after examining only a few triples), whereas sum_to_zero2
takes Ω(n) even in the best case, because it builds the dictionary of positions before starting its search. This means that sum_to_zero
runs faster than sum_to_zero2
for test cases where there are many triples that sum to zero:
>>> test(1000, 100)
sum_to_zero 0.0174069404602
sum_to_zero2 0.0476779937744
But when triples that sum to zero are rare, the better asymptotic performance of sum_to_zero2
wins big:
>>> test(1000, 1000)
sum_to_zero 0.0860788822174
sum_to_zero2 0.0678000450134
>>> test(1000, 10000)
sum_to_zero 0.918173074722
sum_to_zero2 0.0757789611816
>>> test(1000, 100000)
sum_to_zero 9.44526004791
sum_to_zero2 0.133813142776
>>> test(1000, 1000000)
sum_to_zero 84.0223789215
sum_to_zero2 0.946579933167
Your function needs a docstring. What does it do and how do you call it?
It's better to raise an exception rather than return an exceptional value as a result. It's too easy for the caller to omit the check for the exceptional value.
The number zero is arbitrary, so why not generalize the code so that can find triples that sum to any target number?
It's not clear to me what the purpose of
a.sort(reverse=True)
is. This has the side-effect of sorting the inputa
into reverse order, which is probably not what the caller expected. If you really need to processa
in reverse order, you should write something like:a = sorted(a, reverse=True)
from itertools import combinations
class NotFoundError(Exception):
pass
def triple_with_sum(a, target=0):
"""Return a tuple of three numbers in the sequence a that sum to
target (default: 0). Raise NotFoundError if no triple sums to
target.
"""
for triple in combinations(a, 3):
if sum(triple) == target:
return triple
raise NotFoundError('No triple sums to {}.'.format(target))
from collections import defaultdict
def triple_with_sum2(a, target=0):
"""Return a tuple of three numbers in the sequence a that sum to
target (default: 0). Raise NotFoundError if no triple sums to
target.
"""
positions = defaultdict(set)
for i, n in enumerate(a):
positions[n].add(i)
for (i, ai), (j, aj) in combinations(enumerate(a), 2):
n = target - ai - aj
if positions[n].difference((i, j)):
return n, ai, aj
raise NotFoundError('No triple sums to {}.'.format(target))
from random import randint
from timeit import timeit
def test(n, m, cases=100):
"""Create cases (default: 100) random testcases and run both algorithms on them. Each test case consists of n numbers in the range [-m, m].
"""
testcases = [[randint(-m, m) for _ in range(n)] for _ in range(cases)]
for algorithm in triple_with_sum, triple_with_sum2:
def run():
for testcase in testcases:
try:
assert(sum(algorithm(testcase)) == 0)
except NotFoundError:
pass
print(algorithm.__name__, timeit(run, number=1))
The triple_with_sum
algorithm has the advantage that it might terminate early (after examining only a few triples), whereas triple_with_sum2
takes Ω(n) even in the best case, because it builds the dictionary of positions before starting its search. This means that triple_with_sum
runs faster than triple_with_sum2
for test cases where there are many triples that sum to zero:
>>> test(1000, 100)
triple_with_sum 0.03754958091303706
triple_with_sum2 0.05412021093070507
But when triples that sum to zero are rare, the better asymptotic performance of triple_with_sum2
wins big:
>>> test(1000, 1000)
triple_with_sum 0.10221871780231595
triple_with_sum2 0.07950551761314273
>>> test(1000, 10000)
triple_with_sum 0.9003877746872604
triple_with_sum2 0.08799532195553184
>>> test(1000, 100000)
triple_with_sum 9.874092630110681
triple_with_sum2 0.14182585570961237
>>> test(1000, 1000000)
triple_with_sum 97.11457079928368
triple_with_sum2 1.0804437939077616
- 50.1k
- 3
- 130
- 210
from itertools import combinations
class NotFoundError(Exception):
pass
def sum_to_zero(a):
"""
Return"""Return a tuple of three numbers in the sequence `a`a that sum to zero.
zero. Raise NotFoundError if all sums of triples are non-zero.
"""
for triplet in combinations(a, 3):
if sum(triplet) == 0:
return triplet
raise NotFoundError('No triplet sums to zero.')
from collections import defaultdict
def sum_to_zero2(a):
"""
Return"""Return a tuple of three numbers in the sequence `a`a that sum to zero.
zero. Raise NotFoundError if all sums of triples are non-zero.
"""
positions = defaultdict(set)
for i, n in enumerate(a):
positions[n].add(i)
for i, j in combinations(range(len(a)), 2):
n = -a[i] - a[j]
if positions[n].difference((i, j)):
return n, a[i], a[j]
raise NotFoundError('No triplet sums to zero.')
from random import randint
from timeit import timeit
def test(n, m, number=100):
"""
Create"""Create `number`number random testcases and run both algorithms on them.
them. Each test case consists of `n` randomn numbers in the range [-`m`m, `m`]m].
"""
testcases = [[randint(-m, m) for _ in xrange(n)] for _ in xrange(number)]
for algorithm in sum_to_zero, sum_to_zero2:
def run():
for testcase in testcases:
try: assert(sum(algorithm(testcase)) == 0)
except NotFoundError: pass
print(algorithm.__name__, timeit(run, number=1))
>>> test(1000, 100)
sum_to_zero 0.0174069404602
sum_to_zero2 0.0476779937744
>>> test(1000, 100)
sum_to_zero 0.0174069404602
sum_to_zero2 0.0476779937744
>>> test(1000, 1000)
sum_to_zero 0.0860788822174
sum_to_zero2 0.0678000450134
>>> test(1000, 10000)
sum_to_zero 0.918173074722
sum_to_zero2 0.0757789611816
>>> test(1000, 100000)
sum_to_zero 9.44526004791
sum_to_zero2 0.133813142776
>>> test(1000, 1000000)
sum_to_zero 84.0223789215
sum_to_zero2 0.946579933167
>>> test(1000, 1000)
sum_to_zero 0.0860788822174
sum_to_zero2 0.0678000450134
>>> test(1000, 10000)
sum_to_zero 0.918173074722
sum_to_zero2 0.0757789611816
>>> test(1000, 100000)
sum_to_zero 9.44526004791
sum_to_zero2 0.133813142776
>>> test(1000, 1000000)
sum_to_zero 84.0223789215
sum_to_zero2 0.946579933167
from itertools import combinations
class NotFoundError(Exception):
pass
def sum_to_zero(a):
"""
Return a tuple of three numbers in the sequence `a` that sum to zero.
Raise NotFoundError if all sums of triples are non-zero.
"""
for triplet in combinations(a, 3):
if sum(triplet) == 0:
return triplet
raise NotFoundError
from collections import defaultdict
def sum_to_zero2(a):
"""
Return a tuple of three numbers in the sequence `a` that sum to zero.
Raise NotFoundError if all sums of triples are non-zero.
"""
positions = defaultdict(set)
for i, n in enumerate(a):
positions[n].add(i)
for i, j in combinations(range(len(a)), 2):
n = -a[i] - a[j]
if positions[n].difference((i, j)):
return n, a[i], a[j]
raise NotFoundError
from random import randint
from timeit import timeit
def test(n, m, number=100):
"""
Create `number` random testcases and run both algorithms on them. Each test case consists of `n` random numbers in the range [-`m`, `m`].
"""
testcases = [[randint(-m, m) for _ in xrange(n)] for _ in xrange(number)]
for algorithm in sum_to_zero, sum_to_zero2:
def run():
for testcase in testcases:
try: algorithm(testcase)
except NotFoundError: pass
printalgorithm.__name__, timeit(run, number=1)
>>> test(1000, 100)
sum_to_zero 0.0174069404602
sum_to_zero2 0.0476779937744
>>> test(1000, 1000)
sum_to_zero 0.0860788822174
sum_to_zero2 0.0678000450134
>>> test(1000, 10000)
sum_to_zero 0.918173074722
sum_to_zero2 0.0757789611816
>>> test(1000, 100000)
sum_to_zero 9.44526004791
sum_to_zero2 0.133813142776
>>> test(1000, 1000000)
sum_to_zero 84.0223789215
sum_to_zero2 0.946579933167
from itertools import combinations
class NotFoundError(Exception):
pass
def sum_to_zero(a):
"""Return a tuple of three numbers in the sequence a that sum to
zero. Raise NotFoundError if all sums of triples are non-zero.
"""
for triplet in combinations(a, 3):
if sum(triplet) == 0:
return triplet
raise NotFoundError('No triplet sums to zero.')
from collections import defaultdict
def sum_to_zero2(a):
"""Return a tuple of three numbers in the sequence a that sum to
zero. Raise NotFoundError if all sums of triples are non-zero.
"""
positions = defaultdict(set)
for i, n in enumerate(a):
positions[n].add(i)
for i, j in combinations(range(len(a)), 2):
n = -a[i] - a[j]
if positions[n].difference((i, j)):
return n, a[i], a[j]
raise NotFoundError('No triplet sums to zero.')
from random import randint
from timeit import timeit
def test(n, m, number=100):
"""Create number random testcases and run both algorithms on them.
Each test case consists of n numbers in the range [-m, m].
"""
testcases = [[randint(-m, m) for _ in xrange(n)] for _ in xrange(number)]
for algorithm in sum_to_zero, sum_to_zero2:
def run():
for testcase in testcases:
try: assert(sum(algorithm(testcase)) == 0)
except NotFoundError: pass
print(algorithm.__name__, timeit(run, number=1))
>>> test(1000, 100)
sum_to_zero 0.0174069404602
sum_to_zero2 0.0476779937744
>>> test(1000, 1000)
sum_to_zero 0.0860788822174
sum_to_zero2 0.0678000450134
>>> test(1000, 10000)
sum_to_zero 0.918173074722
sum_to_zero2 0.0757789611816
>>> test(1000, 100000)
sum_to_zero 9.44526004791
sum_to_zero2 0.133813142776
>>> test(1000, 1000000)
sum_to_zero 84.0223789215
sum_to_zero2 0.946579933167