pythonmathoptimizationmathematical-optimizationinteger-arithmetic

How to find the biggest possible combination of two numbers under a certain threshold from a list?


So let's say if the list is [2,5,14,18,44], what I want to find out is what the biggest sum of two numbers under 60 would be. So the correct answer should be (14,44)

Right now I am calculating every possible combination before getting the result. It is horribly inefficient

Edit: Here is my code. It serves to solve the problem but it is slow because the list is really long

    import itertools

    #build_list is a long list of all constructions with the construction cost

    #build_power1 is the available amount of funds

    #in the first line I exclude all the constructions beyond total funds

    build_list = [ i for i in self.list_of_constructions if i['cost']<build_power1]

    iter_all_build_combos = itertools.combinations(build_list,2)
    
    list_all_build_combos_with_cost = []        
    for combo in iter_all_build_combos:
        sum1 = combo[0]['cost'] + combo[1]['cost']
        list_all_build_combos_with_cost.append((sum1, combo))
    #here I have a list with all combonations alongside with the sum of the costs
    list_all_build_combos_with_cost.sort()

Solution

  • Here's one possible approach based on iterating from both ends of the (sorted) list:

    def best_pair(nums: list[int], threshold: int) -> tuple[int, int]:
        """Return the pair of nums whose sum is closest to threshold
        without meeting or exceeding it."""
        nums = sorted(nums)
        best = nums[0], nums[1]
        if sum(best) >= threshold:
            raise ValueError(f'No pair exists under {threshold}.')
        j = len(nums) - 1
        for i in range(len(nums) - 1):
            while nums[i] + nums[j] >= threshold:
                j -= 1
            if j <= i:
                break
            if nums[i] + nums[j] > sum(best):
                best = nums[i], nums[j]
        return best
    
    assert best_pair([2, 4, 14, 18, 44], 60) == (14, 44)
    

    For any given number in the list, there is a particular other number that forms the best possible pair. If you iterate through the list in order from smallest to largest (i), the best pairing for those numbers (j) will always decrease. As i increases, all you need to do is incrementally decrease j enough to keep it from "busting" the pair it forms with that i. Once j <= i you can't form any more valid pairs, and you're done.

    This drastically cuts down on the number of possibilities that you need to consider; since each iteration only goes in one direction through the list, the whole thing ends up being O(n).

    Note that calling sorted() is O(n log n) only if the list isn't already sorted; for an already-sorted list it's O(n). If you know for certain that the list will always be sorted, you can save some time by skipping it, but it doesn't change the big-O runtime.