Skip to main content
Code Review

Return to Answer

replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link

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:

algorithm could be generalized; better names
Source Link
Gareth Rees
  • 50.1k
  • 3
  • 130
  • 210
  1. Your function needs a docstring. What does it do and how do you call it?

  2. 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.

  3. The number zero is arbitrary, so why not generalize the code so that can find triples that sum to any target number?

  4. It's not clear to me what the purpose of a.sort(reverse=True) is. This has the side-effect of sorting the input a into reverse order, which is probably not what the caller expected. If you really need to process a 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
  1. Your function needs a docstring. What does it do and how do you call it?

  2. 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.

  3. It's not clear to me what the purpose of a.sort(reverse=True) is. This has the side-effect of sorting the input a into reverse order, which is probably not what the caller expected. If you really need to process a 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
  1. Your function needs a docstring. What does it do and how do you call it?

  2. 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.

  3. The number zero is arbitrary, so why not generalize the code so that can find triples that sum to any target number?

  4. It's not clear to me what the purpose of a.sort(reverse=True) is. This has the side-effect of sorting the input a into reverse order, which is probably not what the caller expected. If you really need to process a 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
Follow PEP8 and PEP257; make portable to Python 3
Source Link
Gareth Rees
  • 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
wording improvements; another point in §1
Source Link
Gareth Rees
  • 50.1k
  • 3
  • 130
  • 210
Loading
added 98 characters in body
Source Link
Gareth Rees
  • 50.1k
  • 3
  • 130
  • 210
Loading
Source Link
Gareth Rees
  • 50.1k
  • 3
  • 130
  • 210
Loading
lang-py

AltStyle によって変換されたページ (->オリジナル) /