Python 在对象的组合上有条件地迭代

Python 在对象的组合上有条件地迭代,python,Python,我正在研究一个小的营养计划,希望找到某些食物的最佳组合,以满足卡路里和大量营养素方面的一些要求。就这个问题而言,我将只关注目标卡路里 我有来自API的数据,可以告诉我关于给定食物的事情。例如: egg = Food(FOOD_IDS["Boiled Egg"]) print(egg.macros.__dict__) 结果: {'protein_grams': 6.0, 'fat_grams': 4.5, 'carb_grams': 1.0, 'calories': 68.5} 我想做的是找到

我正在研究一个小的营养计划,希望找到某些食物的最佳组合,以满足卡路里和大量营养素方面的一些要求。就这个问题而言,我将只关注目标卡路里

我有来自API的数据,可以告诉我关于给定食物的事情。例如:

egg = Food(FOOD_IDS["Boiled Egg"])
print(egg.macros.__dict__)
结果:

{'protein_grams': 6.0, 'fat_grams': 4.5, 'carb_grams': 1.0, 'calories': 68.5}
我想做的是找到我所能吃的食物的可能组合,这些组合能增加目标数量的卡路里

因此,如果我有
鸡蛋
大米
鸡肉
,在
食物
的集合中,我如何找到这些食物的所有可能组合,比如3000卡路里

我曾考虑使用
随机
循环中不断收集食物,直到达到卡路里目标,但随着食物清单的增加,我担心性能。我以前使用过
itertools
库中的
产品
组合
、和
置换
,但我不知道这些方法如何在这里应用,因为我打算在边界条件内迭代

以下是我迄今为止的尝试:

def get_daily_food_options(self):
    target = 3000
    food_combinations = []
    groceries = []
    groceries.append(Food(FOOD_IDS["Boiled Egg"]))
    groceries.append(Food(FOOD_IDS["Brown Rice"]))
    groceries.append(Food(FOOD_IDS["Chicken Breast"]))
    num_iterations = 100 # I have to manually change this to get more/less results
    for i in range(num_iterations):
        temp_combination = []
        current_calories = 0
        while current_calories < target:
            food = choice(groceries) # random (not smart)
            temp_combination.append(food)
            current_calories += food.macros.calories
        food_combinations.append(temp_combination)
    return food_combinations

下面的程序获取总热量计数和代表单个食品的热量含量的整数列表。它试图找到这些食物的倍数,这些食物加起来正好达到所需的总热量:

from itertools import product

def get_combinations(total_calories, calorie_counts):
    n = len(calorie_counts)
    max_factors = [total_calories // calorie_counts[i] for i in range(n)]
    for t in product(*(range(factors + 1) for factors in max_factors)):
        calorie_count = sum([t[i] * calorie_counts[i] for i in range(n)])
        if calorie_count == total_calories:
            print(t)


get_combinations(3000, [300, 150, 200])
如果您坚持每种食物至少有一份,则将for循环更改为:

for t in product(*(range(1, factors + 1) for factors in max_factors)):
其想法是,如果总热量为X,而某一食物项目的热量为Y,则该食物项目的唯一可能份数在[0..X//Y]范围内(或[1..X//Y],如果您坚持至少有一份)。因此,如果有N个食物项目,程序会通过生成长度为N的元组,对所有可能的食物项目组合进行详尽的试验,其中每个元组是一个可能的组合


这里通常采用的类似里程表的方法是

def combos(menu,target,extra):  # menu: list of values
  # Put the biggest first for efficiency
  # and to avoid large shortfalls:
  order=sorted(enumerate(menu),key=lambda e: -e[1])
  # Construct inverse permutation:
  inv=[None]*len(menu)
  for i,(j,_) in enumerate(order): inv[j]=i
  for c in combos0([v for _,v in order],target,extra,[]):
    yield [c[i] for i in inv]
def combos0(menu,target,extra,pfx):
  v=menu[len(pfx)]
  # Leave no budget unspent:
  if len(pfx)==len(menu)-1:
    n=-(-target//v)  # ceiling division
    if target+extra>=n*v: yield pfx+[n]
  else:
    for i in range((target+extra)//v+1):
      for c in combos0(menu,target-i*v,extra,pfx+[i]): yield c

你在这里的电话是
套餐([f.macros.carries for f in foods],3000100)

这个怎么样<代码>当前热量+=总和([f.macros.carries for f in temp\u composition])
可能不是必需的,因为
当前热量
具有当前while迭代之前的卡路里数。由于每次while迭代都会将食物添加到
temp\u组合中
,因此只需将
当前热量
与所选食物的热量相加,即
当前热量+=food.macros.carries
。这样,您就可以避免在
sum([f.macros.carries for f in temp_composition])
表达式中进行额外的O(n)计算。@Viethran很好,谢谢您研究过线性规划吗?scipy是一个很棒的软件包。它不会直接回答您的问题,但您应该能够找到一个最佳解决方案列表。同样有用:@JacobIRR:目标是“大约3000”,还是需要精确?我真的很喜欢这有一个排序输出。我将把它插入到我的大环境中,看看它是如何工作的我想我知道了
max\u factors
在做什么,但是当我用13种食物运行这个时,它运行了很长一段时间(现在已经五分钟了)。这是否意味着我需要通过某种数据科学包/库来运行它,它的数学运算在C级进行了优化?我看不到任何进一步优化它的方法。@JacobIRR好吧,有13种食物,你就有了13个嵌套循环,测试每种可能的食物组合,其中每种食物的分量可以从0变化到其最大允许值(我现在希望我将变量
max\u factors
命名为
max\u servings
)。它可能会运行很长时间。
对于i,(j,u),按顺序
抛出
TypeError:“int”对象在Python 3.7中是不可编辑的
。它应该是
枚举(顺序)
?@JacobIRR:当然可以;从移动设备发布代码有其局限性。编辑。
def combos(menu,target,extra):  # menu: list of values
  # Put the biggest first for efficiency
  # and to avoid large shortfalls:
  order=sorted(enumerate(menu),key=lambda e: -e[1])
  # Construct inverse permutation:
  inv=[None]*len(menu)
  for i,(j,_) in enumerate(order): inv[j]=i
  for c in combos0([v for _,v in order],target,extra,[]):
    yield [c[i] for i in inv]
def combos0(menu,target,extra,pfx):
  v=menu[len(pfx)]
  # Leave no budget unspent:
  if len(pfx)==len(menu)-1:
    n=-(-target//v)  # ceiling division
    if target+extra>=n*v: yield pfx+[n]
  else:
    for i in range((target+extra)//v+1):
      for c in combos0(menu,target-i*v,extra,pfx+[i]): yield c