我已经看到了几个处理类似问题的问题,但是我尝试过的问题都不能满足我的特定需求,即使我尝试过这些问题也是如此。
所以我有一组自定义对象,有1到30个对象。其中许多对象是相同的(我只有8种类型的对象),并且不能假设每种对象有多少,有些类型可能不存在。
我需要获得所有可能使用我的集合制作的对象类型的组合(无序),这些对象具有特定范围的大小。这意味着我有一个最小值和最大值,并且我需要获得asize大于或等于最小值的所有子集,并且小于或等于最大值。
通过使用为我提供所有子集的众多算法之一,然后删除那些不适合该范围的算法,这很容易做到。但我的问题是我不想根据性能问题计算它们(大多数情况下,最大值和最小值之间的范围大约为2)。
此外,出于性能原因,我还想避免使用相同的所有子集,因为它们使用两个被认为相同的对象版本。
示例(使用整数而不是对象。您可以使用bool AreSame(object1,object2)来检查它们是否相同:
Original set: {0, 1, 1, 5, 5, 6}
Minimum: 2
Maximum: 3
Expected result: {{0, 1}, {0, 5}, {0, 6}, {1, 1}, {1, 5}, {1, 6}, {5, 5}, {5, 6}, {0, 1, 1}, {0, 1, 5}, {0, 1, 6}, {0, 5, 5}, {0, 5, 6}, {1, 1, 5}, {1, 1, 6}, {1, 5, 5}, {1, 5, 6}, {5, 5, 6}}
Original set: {0, 1, 2}
Minimum: 1
Maximum: 4
Expected result: {{0}, {1}, {2}, {0, 1}, {0, 2}, {1, 2}, {0, 1, 2}}
编辑:目前,我在this answer
中提供了此代码的修改版本public static List<List<Tile>> GetSubsets(List<Tile> set, int min, int max)
{
List<List<Tile>> result = new List<List<Tile>>();
int length = set.Count;
int max = (int)Math.Pow(2, set.Count);
for (int count = 0; count < max; count++)
{
List<Tile> subset = new List<Tile>();
uint rs = 0;
while (rs < length && subset.Count() <= max)
{
if ((count & (1u << (int)rs)) > 0)
{
subset.Add(list[(int)rs]);
}
rs++;
}
if (subset.Count() <= max && subset.Count() >= min && !isInSet(result, subset))
result.Add(subset);
}
return result;
}
isInSet基本上是一个函数,使用linq查询来知道子集是否已经存在,使用其他相同的对象。
从技术上讲,这很有效。但在实践中,问题实际上与表演有关。我有许多相同的对象,因此这会计算太多的子集。然后,我还计算了一大堆我最终丢弃的子集。我的问题是如何在代码中以实际加速它的方式添加这两个概念,避免在子集与我的需求不匹配时尽可能多的操作。我无法弄清楚如何做到这一点。
答案 0 :(得分:1)
您可以将问题减少到查找多集的k-multicombinations。也就是说,从仅生成大小为k
的子集的过程开始,然后将k
从minimum
变为maximum
。必须重新启动每个k
的过程并不是最佳的,但是考虑到你的constaints(大小为1到30,包含1到8个不同的对象),除非你的基本算法是疯狂的,否则这样做会很好低效的。
您可以尝试将this recursive algorithm或此iterative algorithm从Python翻译为C#。
如果您不关心生成子集的顺序,那么您可以调整链接的递归算法以直接生成带有有界基数的组合:
static IEnumerable<List<T>> MultiCombinations<T>(List<T> multiset, int min, int max)
{
Debug.Assert(min >= 0 && max >= 0);
var a = multiset.OrderBy(x => x).ToList();
max = Math.Min(max, a.Count);
if (min <= max)
foreach (var combo in Combine(a, min, max, 0, new List<T>()))
yield return combo;
}
private static IEnumerable<List<T>> Combine<T>(List<T> a, int min, int max, int i, List<T> combo)
{
if (i < a.Count && combo.Count < max)
{
combo.Add(a[i]);
foreach (var c in Combine(a, min, max, i + 1, combo))
yield return c;
combo.RemoveAt(combo.Count - 1);
var j = IndexOfNextUniqueItem(a, i);
foreach (var c in Combine(a, min, max, j, combo))
yield return c;
}
else if (combo.Count >= min)
{
Debug.Assert(combo.Count <= max);
yield return new List<T>(combo);
}
}
private static int IndexOfNextUniqueItem<T>(List<T> a, int i)
{
int j = i + 1;
while (j < a.Count && a[j].Equals(a[i]))
j++;
return j;
}
此实现通过对原始输入进行排序来避免生成重复项,这需要您的自定义对象具有可比性。但实际的算法本身并不需要排序,只需要将组合在一起的能力和#34;喜欢&#34;或&#34;等于&#34;对象。
如果您可以保证对象的输入列表始终正确分组,那么您可以完全删除排序。例如:
var groupedTiles = tilesList
.GroupBy(tile => tile.VisibleFace)
// or .GroupBy(tile => tile, tileEqualityComparer)
.SelectMany(grp => grp)
.ToList();
var combos = MultiCombinationsWithoutSorting(groupedTiles);
// ...
在这里,我假设Tile
个对象的可见面具有正确的.Equals()
方法,但您也可以使用.GroupBy()
的一个重载并传入自定义{{1} }}。您还必须更新IEqualityComparer
帮助程序方法以使用您需要的任何相等性测试。