基于预算上限的产品选择问题求助(VBA/M Query)
基于预算筛选所有有效产品组合的解决方案
VBA 实现方案
利用二进制位枚举所有非空产品组合(6个产品对应6位二进制,每一位代表是否选择该产品),计算每个组合的总价后筛选出符合预算的结果并输出到工作表:
Sub GetValidProductCombinations() Dim products As Variant Dim prices As Variant Dim budget As Double Dim totalCombinations As Long Dim i As Long Dim j As Long Dim currentTotal As Double Dim combination As String Dim outputRow As Long ' 初始化产品和价格数据 products = Array("Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6") prices = Array(1, 3, 0.5, 40, 20, 5) budget = 50 totalCombinations = 2 ^ UBound(products) - 1 ' 排除空组合 outputRow = 2 ' 从第2行开始输出(第1行放表头) ' 写入表头 Cells(1, 1).Value = "产品组合" Cells(1, 2).Value = "总价(美元)" ' 遍历所有组合 For i = 1 To totalCombinations currentTotal = 0 combination = "" For j = 0 To UBound(products) ' 检查第j位是否为1(是否选择该产品) If (i And 2 ^ j) <> 0 Then currentTotal = currentTotal + prices(j) combination = combination & products(j) & ", " End If Next j ' 筛选符合预算的组合 If currentTotal <= budget Then ' 移除末尾的逗号和空格 combination = Left(combination, Len(combination) - 2) Cells(outputRow, 1).Value = combination Cells(outputRow, 2).Value = currentTotal outputRow = outputRow + 1 End If Next i ' 格式化输出列 Columns("A:B").AutoFit End Sub
代码说明
- 定义产品名称和价格数组,设置预算值;
- 通过
2^6-1计算所有非空组合数(共63种); - 外层循环遍历每个组合的二进制编码,内层循环检查每个产品是否被选中,累加总价并拼接组合文本;
- 筛选出总价≤50美元的组合,去除末尾多余符号后写入工作表,最后自动调整列宽。
M Query(Power Query)实现方案
在Power Query中生成所有非空产品子集,计算总价后筛选符合预算的组合:
- 新建空白查询,打开高级编辑器,替换为以下M代码:
let // 初始化产品数据 Source = Table.FromRecords({ [产品 = "Item 1", 价格 = 1.0], [产品 = "Item 2", 价格 = 3.0], [产品 = "Item 3", 价格 = 0.5], [产品 = "Item 4", 价格 = 40.0], [产品 = "Item 5", 价格 = 20.0], [产品 = "Item 6", 价格 = 5.0] }), // 添加索引列(用于生成子集) AddIndex = Table.AddIndexColumn(Source, "索引", 0, 1, Int64.Type), // 获取产品列表和价格列表 ProductsList = AddIndex[产品], PricesList = AddIndex[价格], // 生成所有非空子集的索引集合 AllSubsets = List.RemoveItems(List.Generate( () => 1, each _ < 2 ^ List.Count(ProductsList), each _ + 1 ), {0}), // 转换每个子集索引为产品组合和总价 ConvertToCombinations = List.Transform(AllSubsets, (subset) => let SelectedIndices = List.PositionOf(List.Transform({0..List.Count(ProductsList)-1}, (i) => Number.Mod(Number.IntegerDivide(subset, 2^i), 2)), 1), SelectedProducts = List.Select(ProductsList, (p, idx) => List.Contains(SelectedIndices, idx)), TotalPrice = List.Sum(List.Select(PricesList, (pr, idx) => List.Contains(SelectedIndices, idx))) in [产品组合 = Text.Combine(SelectedProducts, ", "), 总价 = TotalPrice] ), // 转换为表 ConvertToTable = Table.FromRecords(ConvertToCombinations), // 筛选总价≤50的组合 FilterByBudget = Table.SelectRows(ConvertToTable, each [总价] <= 50), // 排序(可选) Sorted = Table.Sort(FilterByBudget,{{"总价", Order.Ascending}, {"产品组合", Order.Ascending}}) in Sorted
代码说明
- 通过
Table.FromRecords创建产品数据表; - 添加索引列方便后续生成子集时定位产品;
- 用
List.Generate生成所有非空子集的二进制编码(从1到63); - 对每个编码,解析出选中的产品索引,提取对应的产品名称和价格,计算总价并拼接组合文本;
- 将转换后的列表转为表,筛选出总价≤50美元的组合,最后可选择按总价和组合名称排序。
内容的提问来源于stack exchange,提问作者Yassine Erraihani
相关产品推荐
相关产品推荐

