如果是项目列表,则最近/相等的总和组合 - LINQ

时间:2015-11-08 20:18:24

标签: vb.net linq visual-studio combinations

我有一个包含上述属性的项目列表。 ID 量 PackageNbr 我需要完成的是从这个列表中获得具有 NEAREST ORAL 金额总和的项目到特定值;但条件应始终有效,即返回的项应来自不同的PackageNbr。 例如。

LISTOFITEMS:

╔══════╦══════════╦═════════════╗
║ ID   ║  amount  ║  packageNbr ║
╠══════╬══════════╬═════════════╣
║   1  ║      10  ║          1  ║
║   2  ║       7  ║          1  ║
║   3  ║       4  ║          1  ║
║   4  ║       6  ║          2  ║
║   5  ║       5  ║          2  ║
║   6  ║       3  ║          2  ║
║   7  ║      10  ║          3  ║
║   8  ║       9  ║          3  ║
║   9  ║       3  ║          4  ║
║  10  ║       2  ║          5  ║
╚══════╩══════════╩═════════════╝

对于值21,returnedItems的id为:1 - 6 - 8

对于值14,returnedItems的id为:3 - 7

对于值11,returnedItems的id是:4 - 9 - 10

我认为通过获得所有总和组合(具有不同的PackageNbr)可以实现上述目标,之后我们可以得到最接近或相等的。

知道如何完成这项任务吗? 我在网上冲浪,并没有找到使用linq的方法。 任何帮助将不胜感激。

此致

1 个答案:

答案 0 :(得分:2)

我认为你的问题映射到计算机科学中众所周知的问题Subset Sum Problem

如果您没有其他约束并且必须找到所有可能的组合,那么为了缩短它,您最终会得到一个指数算法。

现在找到一个实用的解决方案:

  1. 如果您有一个很少更改的数据集,并且您需要使用不同的总和多次查询它,只需构建一个包含相应总和的所有组合的表。按总和对其进行排序,并使用二进制搜索来获得具体总和的结果。

  2. 如果您的数据集几乎相同,但相对频繁地更改,您仍然可以创建一个包含所有组合和相应总和的排序数据结构。但是,您需要在原始数据集中每次添加或删除后保持最新。这比再次重新制作还要便宜。

  3. 如果你总是有一个新的数据集和新的查询值,你最终会得到wiki文章中描述的解决方案之一。

  4. 这是选项1的示例实现。 它使用combinatorics library的NUGet来生成组合。

            //Dataset
            var items = new[] {
                new Item {Id=1, Amount=10, PackageNr=1},
                new Item {Id=2, Amount=7, PackageNr=1},
                new Item {Id=3, Amount=4, PackageNr=2},
                new Item {Id=4, Amount=3, PackageNr=2},
                new Item {Id=5, Amount=8, PackageNr=3},
                new Item {Id=6, Amount=9, PackageNr=3},
                new Item {Id=7, Amount=10, PackageNr=4},
            };
    
    
            //Building a table
            var stack = new List<Tuple<int, int[]>>();
            for(int count=1; count <= items.Count(); count++) {
                stack.AddRange(
                 new Combinations<Item>(items, count)
                    .Where(combination => !combination
                                            .GroupBy(item => item.PackageNr)
                                            .Where(group => group.Count() > 1)
                                            .Any())
                    .Select(combination => new Tuple<int, int[]>(
                                            combination.Sum(item=>item.Amount), 
                                            combination.Select(item=>item.Id).ToArray())));
            }
    
            var table = 
                stack
                .OrderBy(tuple => tuple.Item1)
                .ToArray();
            var index = table.Select(i => i.Item1).ToArray(); 
    
            //Print the table
            foreach (var row in table)
            {
                Console.WriteLine(" {0} ->  {1}", row.Item1, string.Join(",", row.Item2));
            };
    
    
            //Binary search in the table.
            Console.WriteLine("Number to search for:");
            int number;
            int.TryParse(Console.ReadLine(), out number);
    
            var position = Array.BinarySearch(index, number);
            if (position >= 0) Console.WriteLine("Exact match {0}", string.Join(",", table[position].Item2));
            else Console.WriteLine("Best match {0}", string.Join(",", table[~position].Item2));
            Console.ReadKey();
        }