Return the indexs of two numbers that total a given number I attempted an online Python test the other day. The function I wrote works fine however takes too long to complete.
Question:

From a list of numbers find the indexes of two numbers that total n and return as a tuple.

I tried using itertools but I don't know if there is a function that would out perform my original. I thought itertools.combinations may be able to.
Any suggestions as the best way to tackle this problem?
import numpy as np
from itertools import combinations

numbers = np.random.randint(1, 100, 1000000)


# used to time function run time
def timer_func(orig):
    import time
    def wrapper_func(*args):
        t1 = time.time()
        result = orig(*args)
        t2 = time.time() - t1
        print(f&quot;{orig.__name__} ran in {t2}&quot;)
        return result

    return wrapper_func


# new combination test
@timer_func
def find_two_sum(numbers, target_sum):
    &quot;&quot;&quot;
    :param numbers: (list of ints) The list of numbers.
    :param target_sum: (int) The required target sum.
    :returns: (a tuple of 2 ints) The indices of the two elements whose sum is equal to target_sum
    &quot;&quot;&quot;
    for perm in combinations(numbers, 2):
        if sum(perm) == target_sum:
            first = np.where(numbers == perm[0])
            second = np.where(numbers == perm[1])
            return (first[0][0], second[0][0])


# original function
@timer_func
def find_two_sum_original(numbers, target_sum):
    &quot;&quot;&quot;
    :param numbers: (list of ints) The list of numbers.
    :param target_sum: (int) The required target sum.
    :returns: (a tuple of 2 ints) The indices of the two elements whose sum is equal to target_sum
    &quot;&quot;&quot;
    for i, x in enumerate(numbers):
        for ii, y in enumerate(numbers):
            if i != ii and x + y == target_sum:
                return (i, ii)


if __name__ == &quot;__main__&quot;:
    print(find_two_sum(numbers, 25))
    print(find_two_sum_original(numbers, 25))


find_two_sum ran in 1.341470718383789
(2, 307)
find_two_sum_original ran in 1.0022737979888916
(2, 307)
Your solutions might have to try all pairs, so you have up to quadratic runtime.
Let's use an allegedly not adequate set to keep track of the numbers we've already seen, so that for each number, we can check in constant time whether we've seen the needed partner:
def find_two_sum(numbers, target_sum):
    &quot;&quot;&quot;
    :param numbers: (list of ints) The list of numbers.
    :param target_sum: (int) The required target sum.
    :returns: (a tuple of 2 ints) The indices of the two elements whose sum is equal to target_sum
    &quot;&quot;&quot;
    seen = set()
    for number in numbers:
        needed = target_sum - number
        if needed in seen:
            i = numbers.index(needed)
            j = numbers.index(number, i + 1)
            return i, j
        seen.add(number)

This only takes linear time.
Other points:

Your function signature seems a bit inappropriate. The question called the target sum &quot;n&quot;, not &quot;target_sum&quot;. Even your whole program doesn't use &quot;n&quot; anywhere. I always follow the specification (maybe unless the specification is really bad). A middle ground would be to define/read the input into variable n and then name your function parameter like you did. This way, a reader of the question and your code can see the connection.
The question says list of numbers (your docstring even repeats that), not a NumPy array. My code assumes it's indeed a list (I find using list.index a lot better here.)
Your benchmark can be improved. The test case contains a million ints from 1 to 100, and your target sum is 25. You're almost guaranteed to find something quickly, despite the large size and despite your quadratic runtime. Better test a worst case, like list(range(1000)) with target 1997 (the sum of the last two numbers).

Motivated by the comments, here's a benchmark comparing this set+index solution and a dict+enumerate solution (numbers are times, so lower=faster):
Round 1:
2.10 twosum_set
1.77 twosum_set_optimized
2.11 twosum_dict

Round 2:
2.05 twosum_set
1.75 twosum_set_optimized
2.08 twosum_dict

Round 3:
2.14 twosum_set
1.83 twosum_set_optimized
2.11 twosum_dict

They look about equally fast, although the optimized set solution is clearly faster.
That was with my above-mentioned list(range(1000)). Let's use a million instead (and fewer repetitions):
Round 1:
2.64 twosum_set
2.31 twosum_set_optimized
2.84 twosum_dict

Round 2:
2.70 twosum_set
2.36 twosum_set_optimized
2.88 twosum_dict

Round 3:
2.68 twosum_set
2.38 twosum_set_optimized
2.87 twosum_dict

Here the set solution does seem faster than the dict solution, and the optimized set solution is again clearly faster.
Benchmark code:
from timeit import repeat

def twosum_set(numbers, target_sum):
    seen = set()
    for number in numbers:
        needed = target_sum - number
        if needed in seen:
            i = numbers.index(needed)
            j = numbers.index(number, i + 1)
            return i, j
        seen.add(number)

def twosum_set_optimized(numbers, target_sum):
    seen = set()
    add = seen.add           # This is the optimization
    for number in numbers:
        needed = target_sum - number
        if needed in seen:
            i = numbers.index(needed)
            j = numbers.index(number, i + 1)
            return i, j
        add(number)          # This is the optimization

def twosum_dict(numbers, target_sum):
    index = {}
    for i, number in enumerate(numbers):
        needed = target_sum - number
        if needed in index:
            return index[needed], i
        index[number] = i

numbers = list(range(10**3))
repeat_number = 10**4

numbers = list(range(10**6))
repeat_number = 10**1

target_sum = sum(numbers[-2:])

for r in range(3):
    print(f'Round {r+1}:')
    for twosum in twosum_set, twosum_set_optimized, twosum_dict:
        t = min(repeat(lambda: twosum(numbers, target_sum), number=repeat_number))
        print('%.2f' % t, twosum.__name__)
    print()