获取范围

时间:2017-05-13 21:49:28

标签: c# algorithm set subset combinations

我已经看到了几个处理类似问题的问题,但是我尝试过的问题都不能满足我的特定需求,即使我尝试过这些问题也是如此。

所以我有一组自定义对象,有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查询来知道子集是否已经存在,使用其他相同的对象。

从技术上讲,这很有效。但在实践中,问题实际上与表演有关。我有许多相同的对象,因此这会计算太多的子集。然后,我还计算了一大堆我最终丢弃的子集。我的问题是如何在代码中以实际加速它的方式添加这两个概念,避免在子集与我的需求不匹配时尽可能多的操作。我无法弄清楚如何做到这一点。

1 个答案:

答案 0 :(得分:1)

您可以将问题减少到查找多集的k-multicombinations。也就是说,从仅生成大小为k的子集的过程开始,然后将kminimum变为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帮助程序方法以使用您需要的任何相等性测试。