-1

I have a dict like so:

a = {"a" : 1.04,
     "b" : 1.07,
     "c" : 10.99,
     ...}

I am trying to get all of the combinations of keys that value sum produces x, there can be repeating keys.

So for example a target of 3.12 can be a result of ["a", "a", "a"]

And I wrote a code like so:

def combinations(data, target, result = [], partial_target = 0):
    if partial_target == target:
        yield result
    if partial_target >= target:
        return
    for key, value in data.items():
        yield from combinations(data, target, result + [key], partial_target + value)


list(combinations(a, 199.45))

But this keeps on spinning forever with no result, I edited my code after reading this answer. So I am not sure if it works or its just stuck in an infinite loop, and if not, if this is the optimal way to solve this problem.

Jonas Palačionis
  • 4,591
  • 4
  • 22
  • 55

2 Answers2

1

As has been mentioned, do not trust float for exact equality. You can use math.isclose, however:

import math

def combinations(data, target):
    def inner(target, pool):
        if math.isclose(0.0, target, abs_tol=0.00001):
            yield []
            return
        if target < 0:
            return
        for i, key in enumerate(pool):
            for c in inner(target - data[key], pool[i:]):
                yield c + [key]

    return inner(target, tuple(data.keys()))
user2390182
  • 72,016
  • 6
  • 67
  • 89
1

To speed up your calculations you could define a max_keys that defines the maximum length of your key combinations.

from itertools import combinations_with_replacement
from math import isclose

test_dict = {
    "a" : 1.04,
     "b" : 1.07,
     "c" : 10.99,
     "d" : 1.56}

def get_target_combinations(dictionary, target, max_keys=3):
    filtered = {key: value for key, value in dictionary.items() if value < target}
    for num_combs in range(2, max_keys+1):
        combs = combinations_with_replacement(filtered.keys(), r=num_combs)
        for keys in combs:
            if isclose(sum(map(lambda x: dictionary[x], keys)), target):
                yield keys
        
        
list(get_target_combinations(test_dict, 3.12, max_keys=4))
>> [('d', 'd'), ('a', 'a', 'a')]
chatax
  • 990
  • 3
  • 17