在c#</t>中交叉N个SortedLists <t>

时间:2012-09-14 22:46:06

标签: c# linq

我有N个SortedLists,每个都有一个对象集合,这些对象包含一个按ID排序的int ID。我需要找到所有列表中存在的对象集。

我的第一个想法是按大小排序列表以从最小的子集开始,然后我可以将每个列表和.Intersect()用于其他列表,但对于大型列表和效率,我想利用它们的事实。重新排序。我猜测有一些最优的算法 - 也许是数据库引擎会像散列连接那样使用的东西。我只是不知道什么算法是最好的。任何帮助表示赞赏。

4 个答案:

答案 0 :(得分:3)

或多或少相交哈希联接。如果数据已经排序,你可以做一个嵌套循环合并,但我不认为有任何库方法可以为你做这个,并且编写方法有点麻烦。

另一种基于散列的方法是Distinct。为什么不连接列表并使用Distinct?这将使其保持一个哈希表。

使用Distinct / hash逻辑,只有在实际导致性能问题时才寻求优化。嵌套循环方法可能会更慢,并且无论如何,如果Distinct(或其他基于散列的)方法足够快,您不希望花费大量时间来编写它。

示例:

var result = list1.Concat(list2).Concat(list3).Distinct();

如果您在编译时不知道列表的数量,请尝试:

IEnumerable<IEnumerable<T>> lists = // a sequence of lists
var result = lists.Aggregate(Enumerable.Empty<T>(), (a, b) => a.Concat(b)).Distinct();

答案 1 :(得分:2)

您可以并行循环浏览列表,每个列表使用一个索引。从其索引处的一个列表中选择一个值,然后在其索引处的值较小时推进其他列表。如果您找到缺少该值的列表,请从该列表中获取下一个更高的值,然后开始寻找该值。

如果您已经推进了所有列表并在所有列表中找到了值,那么您可以将值添加到结果中。推进所有列表并重新开始寻找值。重复,直到到达所有列表的末尾。

这似乎可以完成这项工作:

public static SortedList<int, T> MultiIntersect<T>(params SortedList<int, T>[] lists) {
  SortedList<int, T> result = new SortedList<int, T>();
  int[] index = new int[lists.Length];
  bool cont;
  do {
    int list = 0;
    int value = lists[list].Keys[index[list]];
    while (list < lists.Length) {
      while (index[list] < lists[list].Count && lists[list].Keys[index[list]] < value) index[list]++;
      if (index[list] == lists[list].Count) {
        return result;
      } else if (lists[list].Keys[index[list]] > value) {
        value = lists[list].Keys[index[list]];
        list = 0;
      } else {
        list++;
      }
    }
    result.Add(value, lists[0].Values[index[0]]);
    cont = true;
    for (var i = 0; i < index.Length; i++) {
      index[i]++;
      cont &= index[i] < lists[i].Count;
    }
  } while(cont);
  return result;
}

答案 2 :(得分:0)

这种做法怎么样?

HashSet<YourType> hashSet = new HashSet<YourType>(list1);
hashSet.IntersectWith(list2);
hashSet.IntersectWith(list3);
...
hashSet.IntersectWith(listn);
List<YourType> intersection = hashSet.ToList();

恕我直言应该足够有效率。

答案 3 :(得分:0)

我认为Guffas在代码中的建议。对不起阵列,他们输入的速度更快。

void Main()
{
var lists = new [] {new[] {1, 1, 2, 3, 4, 5, 6, 9, 11, 13},
                    new[] {1, 1, 5, 6, 7, 13},
                    new[] {1, 1, 6, 8, 9, 13},
                    };

var mergedSet = lists[0];
for(var i = 1; i < lists.Length; i++)
{
    mergedSet = Merge(lists[i], mergedSet);
}
}

int[] Merge (int[] sla, int[] slb)
{
int ixa = 0, ixb = 0;
List<int> result = new List<int>();
while(ixa < sla.Length && ixb < slb.Length)
{
    if (sla[ixa] < slb[ixb]) { ixa++; } 
    else if (sla[ixa] > slb[ixb]) { ixb++; } 
    else { result.Add(sla[ixa]); ixa++; ixb++; }
}

return result.ToArray();
}    

对大小的输入进行排序并从最小的列表开始可能会提供一些额外的性能,但如果最小的列表包含总计集中的最小值和最大值,则仍将遍历所有列表中的所有项。

我认为可读性可能有利于使用linq查询的可能效率较低的方法,如其他地方所建议的那样。