【问题标题】:Python return list of items that fit set parametersPython返回适合设置参数的项目列表
【发布时间】:2021-01-17 18:04:48
【问题描述】:

我在将返回的解决方案正确装箱到 min_cals

menu = [
    {'name':'Cheese Pizza Slice', 'calories': 700, 'cost': 4},
    {'name':'House Salad', 'calories': 100, 'cost': 8.5},
    {'name':'Grilled Shrimp', 'calories': 400, 'cost': 15},
    {'name':'Beef Brisket', 'calories': 400, 'cost': 12},
    {'name':'Soda', 'calories': 100, 'cost': 1},
    {'name':'Cake', 'calories': 300, 'cost': 3},
]

def menu_recommendation(menu, min_cal, max_cal, budget):
    menu = [item for item in menu if item['calories'] <= max_cal and item['cost'] <= budget]
    if len(menu) == 0: return []
    return min((
        [item] + menu_recommendation(menu, min_cal - item['calories'], max_cal - item['calories'], budget - item['cost'])
        for item in menu
    ), key= 
        lambda recommendations: [budget - sum(item['cost'] for item in recommendations) and min_cal <= sum(item['calories'] for item in recommendations) <= max_cal, -sum(item['calories'] for item in recommendations)]
    )

recommendation = menu_recommendation(menu, 1000, 1200, 15)
total_cost = sum(item['cost'] for item in recommendation)
total_cals = sum(item['calories'] for item in recommendation)
print(f'recommendation: {recommendation}')
print(f'total cost: {total_cost}')
print(f'total calories: {total_cals}')

例如,以下返回总卡路里数为 700 的解决方案,低于 1000 的最小值。 推荐 = menu_recommendation(menu, 1000, 1200, 15)

【问题讨论】:

  • 使用遗传算法
  • 您可以使用遗传算法,但如果使用线性目标和线性约束,您最好使用混合整数规划:developers.google.com/optimization/mip/…
  • @DavidEisenstat 感谢您将我指向 google OR-Tools。我不了解此资源,也不知道搜索此类内容所需的语言。干杯:)
  • 我不确定,但不能使用knapsack 的变体来解决吗?它使用重量小于w 的第一个i 项计算矩阵C[i,w],即可能的最大值。假设卡路里为重量,预算为价值。计算出C 后,查找min_cal &lt;= w &lt;= max_cal 所在的单元格以找到低于预算的最高值。
  • 对我来说,这似乎是一个常见的背包问题,但有两个约束,最好的解决方法是动态规划。我猜这是一些考试/面试问题?请参阅此处了解更多信息en.wikipedia.org/wiki/List_of_knapsack_problems 或更具体地了解这里en.wikipedia.org/wiki/Packing_problems

标签: python algorithm iteration scientific-computing


【解决方案1】:

也许我们可以做一些递归的事情。

def smallest_combo(lst, m, n, z):
    # filter list to remove elements we can't use next without breaking the rules
    lst = [dct for dct in lst if m <= dct['x'] <= n and dct['y'] <= z]
    # recursive base case
    if len(lst) == 0:
        return []
    # go through our list of eligibles
    # simulate 'what would the best possibility be if we picked that one to go with next'
    # then of those results select the one with the sum('y') closest to z
    #   (breaking ties with the largest sum('x'))
    return min((
        [dct] + smallest_combo(lst, m - dct['x'], n - dct['x'], z - dct['y'])
        for dct in lst
    ), key= 
        lambda com: [z - sum(d['y'] for d in com), -sum(d['x'] for d in com)]
    )

inp = [{'name': 'item1', 'x': 600, 'y': 5},
 {'name': 'item2', 'x': 200, 'y': 8},
 {'name': 'item3', 'x': 500, 'y': 12.5},
 {'name': 'item4', 'x': 0, 'y': 1.5},
 {'name': 'item5', 'x': 100, 'y': 1}]
print(smallest_combo(inp, 500, 1500, 25))
# [{'name': 'item3', 'x': 500, 'y': 12.5}, {'name': 'item3', 'x': 500, 'y': 12.5}]

有很多方法可以加快速度。首先是制作递归缓存,其次是采用动态编程方法(即从底部开始而不是从顶部开始)。

【讨论】:

  • 这真的很有帮助,但我不明白 lambda 函数是如何工作的。表达式的后半部分如何计算,为什么表达式用括号括起来?我觉得我在这里遗漏了一些基本的东西,而且我很难在 python 文档中找到合适的类比。
  • @是的。 lambda 采用每个组合(字典列表),并返回本质上是一个 2 元组:z 减去其中每个字典的 'y' 的总和,以及其中每个总和的负数 'x'sorted()min() 等函数可以接受 iterables 作为键,告诉它们如何排序,并按优先级顺序比较元素 - 如果元素超出第一个元素,则用作决胜局在他们合法之前。我使用方括号使其成为列表而不是元组,除了使表达式更加清晰之外,没有任何理由。
  • 我已经更新了问题以增加对问题的明确性,非常感谢您的意见。
  • 我在smallest_combo() 中得到KeyError: 'x' 第3 行作为问题的示例。
  • @greybeard 请记住,此代码是为适应此特定示例输入而构建的,其中每个 dict 都有键 'name''x''y'。您需要确保您的情况也是如此,或者更改变量所指的内容,或添加错误处理。
【解决方案2】:

这是一个动态编程解决方案,它构建了一个数据结构,显示了我们可以使用的所有 (calorie, cost) 选项以及每个选项。我们会寻找符合标准的最佳方案,然后找出是什么建议。

def menu_recommendation(menu, min_cal, max_cal, budget):
    # This finds the best possible solution in pseudo-polynomial time.
    recommendation_tree = {(0, 0.0): None}
    for item in menu:
        # This tree will wind up being the old plus new entries from adding this item.
        new_recommendation_tree = {}
        for key in recommendation_tree.keys():
            calories, cost = key
            new_recommendation_tree[key] = recommendation_tree[key]
            new_key = (calories + item['calories'], cost + item['cost'])
            if new_key not in recommendation_tree and new_key[0] <= max_cal:
                # This is a new calorie/cost combination to look at.
                new_recommendation_tree[new_key] = item
        # And now save the work.
        recommendation_tree = new_recommendation_tree

    # Now we look for the best combination.
    best = None
    for key in recommendation_tree:
        calories, cost = key
        # By construction, we know that calories <= max_cal
        if min_cal <= calories:
            if best is None or abs(budget - cost) < abs(budget - best[1]):
                # We improved!
                best = key

    if best is None:
        return None
    else:
        # We need to follow the tree back to the root to find the recommendation
        calories, cost = best
        item = recommendation_tree[best]
        answer = []
        while item is not None:
            # This item is part of the menu.
            answer.append(item)
            # And now find where we were before adding this item.
            calories = calories - item['calories']
            cost = cost - item['cost']
            best = (calories, cost)
            item = recommendation_tree[best]
        return answer

【讨论】:

  • 这是一个有趣的方法,但我在以下情况下运行它时遇到了一个错误:recommendation = menu_recommendation(menu, 800, 1000, 4) 我希望收到一个空列表,表示没有可能的解决方案,但我得到了以下建议: 总成本:5 总卡路里:800 总成本超出了可用预算。我按如下方式调整了代码,似乎又回到了接收 None: if new_key not in Recommendation_tree and new_key[0] 的正轨
  • 您在描述中说您希望尽可能接近预算。如果您只想不超出预算,我可以更高效地找到合适卡路里范围内最便宜的解决方案。
  • 它并不总是最便宜的,但会在预算之内。而且通常很便宜。
  • 听起来不错,我很想看到您更有效的解决方案。为此,最便宜的不是可取的。事实上,最理想的解决方案在不超过预算的情况下最接近预算,而 min_cal
  • @是的。高效只是简单地进行 A* 搜索以找到正确卡路里范围的最便宜路径。但它不适用于您的标准。
【解决方案3】:

我想出了这个,它基本上是一个背包,但是如果它们不适合推荐,它会递归地从菜单中删除它们:

menu = [
    {'name':'Cheese Pizza Slice', 'calories': 700, 'cost': 4},
    {'name':'House Salad', 'calories': 100, 'cost': 8.5},
    {'name':'Grilled Shrimp', 'calories': 400, 'cost': 15},
    {'name':'Beef Brisket', 'calories': 400, 'cost': 12},
    {'name':'Soda', 'calories': 100, 'cost': 1},
    {'name':'Cake', 'calories': 300, 'cost': 3},
]



def get_price(recommendation):
    return sum(dish["cost"] for dish in recommendation)

def get_calories(recommendation):
    return sum(dish["calories"] for dish in recommendation)

def menu_recommendation(menu, min_cal, max_cal, budget):
    sorted_menu = sorted(menu, key=lambda dish: dish["cost"], reverse=True)
    recommendation = []
    for dish in sorted_menu:
      if dish["cost"] + get_price(recommendation) <= budget:
        recommendation.append(dish)
    if recommendation:
      if get_calories(recommendation) < min_cal:
        sorted_menu.remove(min(recommendation, key=lambda dish: dish["calories"]/dish["cost"]))
        return menu_recommendation(sorted_menu, min_cal, max_cal, budget)
      if get_calories(recommendation) > max_cal:
        sorted_menu.remove(max(recommendation, key=lambda dish: dish["calories"]/dish["cost"]))
        return menu_recommendation(sorted_menu, min_cal, max_cal, budget)
    return recommendation




recommendation = menu_recommendation(menu, 500, 800, 15)
total_cost = sum(item['cost'] for item in recommendation)
total_cals = sum(item['calories'] for item in recommendation)
print(f'recommendation: {recommendation}')
print(f'total cost: {total_cost}')
 

它根据卡路里/成本比率去除元素,因为它是应用背包的成本。

如果您有任何问题,请告诉我。

【讨论】:

    【解决方案4】:

    我相信我已经解决了提出超出范围 min_cal / max_cal 界限的建议的问题,但我仍然觉得可能有一个更接近预算的解决方案。

    这是我更新的代码:

    menu = [
        {'name':'Cheese Pizza Slice', 'calories': 700, 'cost': 4},
        {'name':'House Salad', 'calories': 100, 'cost': 8.5},
        {'name':'Grilled Shrimp', 'calories': 400, 'cost': 15},
        {'name':'Beef Brisket', 'calories': 400, 'cost': 12},
        {'name':'Soda', 'calories': 100, 'cost': 1},
        {'name':'Cake', 'calories': 300, 'cost': 3},
    ]
    
    def menu_recommendation(menu, min_cal, max_cal, budget):
        menu = [item for item in menu if item['calories'] <= max_cal and item['cost'] <= budget]
        if len(menu) == 0: return []
        return min(([item] + menu_recommendation(menu, min_cal - item['calories'], max_cal - item['calories'], budget - item['cost']) 
            for item in menu
    ), key= 
        lambda recommendations: [budget - sum(item['cost'] for item in recommendations) 
                                 and min_cal - sum(item['calories'] for item in recommendations) >= 0
                                 and max_cal - sum(item['calories'] for item in recommendations) >= 0, 
                                 -sum(item['calories'] for item in recommendations)]
    )
    
    recommendation = menu_recommendation(menu, 1000, 1200, 15)
    total_cost = sum(item['cost'] for item in recommendation)
    total_cals = sum(item['calories'] for item in recommendation)
    print(f'recommendation: {recommendation}')
    print(f'total cost: {total_cost}')
    print(f'total calories: {total_cals}')
    

    如果有人有任何改进,我很乐意听到!

    【讨论】:

    • 0/1 如果物品数量或预算不是很大,可以使用背包找到最佳解决方案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-15
    • 1970-01-01
    • 1970-01-01
    • 2020-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多