如何找到最少量的常见集

时间:2013-05-31 13:21:52

标签: c# linq categories

鉴于集合

{1,2,3,4} {2,3,4} {1,4} {1}

查找组的简单(并且最好是高性能)算法是什么: {1} {2,3} {4}

因为这是最短的集合列表,其中:

  • 代表所有成员(1-4)。
  • 2和3组合在一起,因为它们始终一起出现在原始集合中。

真实数据是一堆引用,而不是值类型。

编辑:我不认为总结我尝试过的东西可以帮助解决这个问题,并且只是因为可能存在分类理论中的算法,但是(出于娱乐原因)这里有:

  • 我在试图使用union运算符的哈希集上进行了聚合。
  • 我在gethashcode上进行了聚合分组。
  • 我使用第一个条目作为候选集迭代了列表,试图在与其他成员进行比较时逐渐减少它。这个表现并不好,我不确定它最终会有最少量的套装。

3 个答案:

答案 0 :(得分:7)

首先,让我们仔细描述您的问题。

relation 是一个函数,它接受两个参数并返回一个bool,指示关系是否成立。例如,“小于”是一种关系。

等价关系 reflexive 的关系 - 每个项目都与自身相关 - 对称 - 如果A是相关的到B然后B与A - 和传递有关 - 如果A与B有关,而B与C有关,那么A与C有关。

等价关系形成集合的等价分区;也就是说,每个子集中的每个元素彼此相关的多个子集。每个子集称为等价类。例如,整数“A和B的等价关系是相关的,如果它们的差异可以被3整除”则形成三个等价类:

{0, 3, -3, 6, -6, ... }
{1, 4, -2, 7, -5, ... }
{2, 5, -1, 8, -4, ... }

您希望形成所有集合的联合:

{1, 2, 3, 4} U {2, 3, 4} U {1, 4} U {1} --> {1, 2, 3, 4}

然后将该集合划分为等价类,其中等价关系是“当且仅当A和B总是在每个原始集合中出现时,A和B是相关的”。

首先形成一个字典,将每个元素映射到其关联的等价类。正如您正确指出的那样,最糟糕的情况是我们有等价分区,其中每个等价类只包含一个元素,所以让我们从那开始。 (顺便说一下,这是“A等于B”等价关系的等价划分。)

1 --> { 1 }
2 --> { 2 }
3 --> { 3 }
4 --> { 4 }

现在从union中生成所有无序对的集合:

{ {1, 2}, {1, 3}, {1, 4}, {2, 3}, {2, 4}, {3, 4} }

现在对于每个无序对,请问“这对关系是否成立”这个问题?

对于{1, 2}{1, 3}{1, 4},关系不成立。

对于{2, 3},关系确实存在,因此将23存储桶合并在一起:

1 -->     { 1 }
2 --\ 
     -->  { 2, 3 }
3 --/
4 -->     { 4 }

对于{2, 4}{3, 4},这种关系不成立。

现在你已经完成了,你有一个从每个元素到相应等价类的映射。

有意义吗?

有一些方法可以在您正确理解后优化此算法。先把它弄清楚。

注意我在这里做了什么:我通过解决等价分区的一般问题解决了你的具体问题。如果您对如何编写本文很聪明,那么您将能够重用逻辑来解决任何等价分区问题,而不仅仅是您的具体问题。

答案 1 :(得分:1)

以下是一种与您所做的相同答案的方法:

var sets = new [] { new [] {1,2,3,4}, new [] {2,3,4}, new [] {1,4}, new [] {1}};
var results = sets.SelectMany((x,i) => x.Select(y => new { y, i }))
                .GroupBy(x => x.y).Select(x => new { x.Key, g = string.Join("", x.Select(y => y.i).ToArray())})
                .GroupBy(x => x.g).Select(x => x.Select(y => y.Key).ToArray()).ToArray();

我可能会将此查询的结果定义为可用于组成原始集的最小集的最短列表。它使用值的索引作为对它们进行分组的方法。 (1出现在0,2,3; 4出现在0,1,2等)2和3具有相同的索引数组,因此它们在最终结果中组合在一起。

我的第一种方法不适用于集合{1,2,3,4},{2,3,4},{1,4}(答案应为{1},{4},{2 ,3})。这个会。

答案 2 :(得分:0)

虽然Eric Lippert正确地描述了解决方案,但我没有看到如何为它创建良好的并行代码。因此我不得不使用这种方法。 我的解决方案如下

{1,2,3,4} {2,3,4} {1,4} {1}

让我们分别引用这些列表A,B,C和D.

A :{1,2,3,4}
B: {2,3,4}
C: {1,4}
D: {1}

我执行了SelectMany,将每个成员与其所在列表的引用相关联。

A, 1
A, 2
A, 3
A, 4
B, 2
B, 3
B, 4
C, 1
C, 4
D, 1

然后我按成员对它们进行分组。

1 : {A,C,D}
2 : {A,B}
3 : {A,B}
4 : {A,B,C}

(这里我们看到2和3有类似的列表,这是预期的,因为它们出现在相同的原始列表中)。这也是关键点。

为了找到具有相同成员的列表,我通过对列表项中的GetHashcode()的结果进行异或来做了一个Aggregate()。 所以对于“1”,我实际上做了

var SomeInt = A.GetHashcode()^C.GetHashcode()^D.GetHashcode().

因此为每个成员生成一个int。

1: SomeIntA
2: SomeIntB
3: SomeIntB
4: SomeIntC.

通过对此进行分组。我终于得到了理想。 {1},{2,3},{4}