【问题标题】:Get all non-repeating unordered combinations of size in range获取范围内所有不重复的无序大小组合
【发布时间】:2017-05-13 21:49:27
【问题描述】:

我见过几个处理类似问题的问题,但我尝试过的问题都不能满足我的特定需求,即使我尝试调整它们。

所以我有一组自定义对象,其中包含 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 查询以了解子集是否已经存在。

从技术上讲,这是可行的。但在实践中,问题实际上与性能有关。我有很多相同的对象,因此这计算了太多的子集。然后,我还计算了大量最终丢弃的子集。我的问题是如何在代码中添加这两个概念,以实际加速它,当子集不符合我的需求时,避免尽可能多的操作。我不知道该怎么做。

【问题讨论】:

  • “我尝试过的任何一个都不能满足我的特定需求,即使我尝试调整它们” - 如前所述,您的问题太宽泛了。您要求 Stack Overflow 从头开始​​编写您的实现。请提供一个好的minimal reproducible example,以显示您的尝试最接近成功,关于调整 Stack Overflow 上已经存在的许多现有答案之一。详细解释您的特定场景如何无法通过该实现解决,以及您在具体方面遇到的问题。
  • 我已经编辑了问题以添加我的有效尝试,因为问题在于让它更快。显然,由于我的需求的性质,如果我只想要大小为 10-12 的子集并且在原始集合中只有三种不同类型的对象,那么目前大量的操作都是徒劳的尺寸 30。
  • 如果您使用 new HashSet&lt;HashSet&lt;Tile&gt;&gt;(HashSet&lt;Tile&gt;.CreateSetComparer()); 而不是 new List&lt;List&lt;Tile&gt;&gt;(); 会起作用吗? (Tile 可能要实现IComparable

标签: c# algorithm set subset combinations


【解决方案1】:

您可以将问题简化为寻找多重集合的 k-多重组合。也就是说,从只生成大小为k 的子集的过程开始,然后将kminimum 变为maximum。必须为每个 k 重新启动过程并不是最优的,但考虑到您的约束(大小为 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;
}

此实现通过对原始输入进行排序来避免生成重复项,这要求您的自定义对象具有可比性。但实际算法本身不需要排序,只需将“相似”或“相等”对象组合在一起的能力。

如果您可以保证输入的对象列表始终正确分组,那么您可以完全取消排序。例如:

var groupedTiles = tilesList
    .GroupBy(tile => tile.VisibleFace)
    // or .GroupBy(tile => tile, tileEqualityComparer)
    .SelectMany(grp => grp)
    .ToList();

var combos = MultiCombinationsWithoutSorting(groupedTiles);
// ...

这里我假设您的Tile 对象的可见面具有正确的.Equals() 方法,但您也可以使用.GroupBy() 的重载之一并传入自定义IEqualityComparer。您还必须更新 IndexOfNextUniqueItem() 辅助方法以使用您需要的任何相等性测试。

【讨论】:

  • 我会尽快尝试。至于可比较的,我将实现它,因为我的对象在字面上并不相同(例如,与 int 不同)。它们是可以翻转的瓷砖,在不同的侧面有两个不同的“值”。 “相等”的意思是“现在,可见的脸显示相同的东西”,但隐藏的脸可能不同,所以我不能使用你的数字技巧。一旦我能够确认它有效,我会立即接受答案。
  • @KaitoKid 听起来实现IComparable 对您的用例没有意义。相反,您可以用分组操作替换排序操作。我在答案中添加了一个粗略的草图,说明您将如何做到这一点。
  • 我可能会尝试两者,因为在单轮中瓷砖不会翻转,所以双面部分只是为了增加一些随机性,以确定每个瓷砖有多少可用为回合。我明天试试这个,然后给你反馈。感谢您的宝贵时间
猜你喜欢
  • 2020-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-03
  • 2018-09-03
  • 1970-01-01
  • 1970-01-01
  • 2018-03-26
相关资源
最近更新 更多