将大型整数列表与较小的整数列表进行比较的最有效方法是什么?

时间:2012-05-23 12:40:54

标签: c# performance algorithm

目前我的list为1百万integers,我会根据2000 integer的黑名单检查每个integer。这大约需要2分钟。

for(int i = 0; i< MillionIntegerList.Length ; i++)
{
    for(int blacklisted = 0; blacklisted < TwoThousandIntegerList.Length ; blacklisted++)
        if(i==blacklisted)
            i = 0; //Zero is a sentinel value 
}

这样就可以完成2,000,000,000次迭代(循环)。 有没有更好的方式我没有看到?感谢

8 个答案:

答案 0 :(得分:51)

现在有三个选项 - 前两个更通用,因为它们不依赖MillionIntegerList被排序(最初没有指定)。在大型列表 已经排序的情况下,第三种方法更可取。

选项1

是的,使用LINQ确实有更好的方法:

var common = MillionIntegerList.Intersect(TwoThousandIntegerList).ToList();

这将在内部使用通过HashSet<int>构建的TwoThousandIntegerList,然后查找其中MillionIntegerList的每个元素 - 这将比通过整个{{TwoThousandIntegerList更有效率每次1}}。

如果您只想要非黑名单的,则需要:

var valid = MillionIntegerList.Except(TwoThousandIntegerList).ToList();

请注意,如果您只需要迭代结果一次,则应删除ToList调用 - 我已将其包含在内以实现结果,以便可以便宜地多次检查它们。如果您只是进行迭代,IntersectExcept的返回值只会流式结果,从而使其在内存使用方面更便宜。

选项2

如果您不想依赖LINQ to Objects的实现细节,但仍需要基于散列的方法:

var hashSet = new HashSet<int>(TwoThousandIntegerList);
hashSet.IntersectWith(MillionIntegerList);
// Now use hashSet

选项3

使用大型列表排序这一事实的方法肯定是有用的。

假设您不介意首先排序列入黑名单的列表,您可以编写像这样的流式(和通用)实现(未经测试):

// Note: to use this, you'd need to make sure that *both* sequences are sorted.
// You could either sort TwoThousandIntegerList in place, or use LINQ's OrderBy
// method.

public IEnumerable<T> SortedIntersect<T>(this IEnumerable<T> first,
    IEnumerable<T> second) where T : IComparable<T>
{
    using (var firstIterator = first.GetEnumerator())
    {
        if (!firstIterator.MoveNext())
        {
            yield break;
        }

        using (var secondIterator = second.GetEnumerator())
        {
            if (!secondIterator.MoveNext())
            {
                yield break;
            }
            T firstValue = firstIterator.Current;
            T secondValue = secondIterator.Current;

            while (true)
            {
                int comparison = firstValue.CompareTo(secondValue);
                if (comparison == 0) // firstValue == secondValue
                {
                    yield return firstValue;
                }
                else if (comparison < 0) // firstValue < secondValue
                {
                    if (!firstIterator.MoveNext())
                    {
                        yield break;
                    }
                    firstValue = firstIterator.Current;
                }
                else // firstValue > secondValue
                {
                    if (!secondIterator.MoveNext())
                    {
                        yield break;
                    }
                    secondValue = secondIterator.Current;
                }  
            }                
        }
    }
}

(如果您愿意,可以选择IComparer<T>,而不是依赖T进行比较。)

答案 1 :(得分:17)

由于大型列表已排序。您可以通过排序小列表(非常快)然后进行线性合并来获得最佳结果。您只需要查看大(和小)列表中的每个项目一次,并且不需要在后台创建Hashtable。

请参阅MergeSort的merge function部分,了解如何执行此操作。

答案 2 :(得分:5)

我认为你需要的是Enumerable.Except方法(IEnumerable,IEnumerable)

点击http://msdn.microsoft.com/en-us/library/bb300779.aspx

答案 3 :(得分:3)

您的方法需要O(n * n)时间。考虑这些优化:

  • 1)

    如果你的整数不是太大,你可以使用bool数组(例如,如果最大可能的整数是1000000,则使用bool [] b = new bool [1000000])。现在要将数字K添加到黑名单,请使用b [K] = true。检查是微不足道的。这适用于O(n)。您也可以使用BitArray

  • 2)

    整数可以很大。使用二叉搜索树存储黑名单(例如SortedSet)。它有O(logN)插入和检索时间。所以它总是O(N * logN)。语法与List(Add(int K),Contains(int K))的语法相同,忽略重复

答案 4 :(得分:1)

我认为最好的解决方案是使用Bloom filter,而Bloom过滤器表示元素可能在黑名单中,只检查是否为误报(可以在O中完成(Log(可以) n))如果黑名单已分类)。 这个解决方案是节省时间的,并且几乎不使用额外的空间,这使得它比使用hashset要好得多。

这是Google用于Chrome中黑名单的解决方案。

答案 5 :(得分:1)

如何在较长的列表上进行二进制搜索,因为它已经排序。

foreach(integer blacklisted in TwoThousandIntegerList)
{
    integer i  = MillionIntegerList.binarySearch(blacklisted)
    if(i==blacklisted){
          //Do your stuff
    } 
}

此解决方案仅花费 O(m log n)时间,其中m是小列表的大小,n是较长列表的大小。 警告:此解决方案假定MillionIntegerList没有重复值。

如果不是这种情况,那么你可以迭代重复,因为它们必须位于一个连续的块中。为此,我将假设MillionInterList是一个记录列表,每个记录都包含valueindex

foreach(integer blacklisted in TwoThousandIntegerList)
{
    integer index = MillionIntegerList.binarySearch(blacklisted)
    //Find the index of the first occurrence of blacklisted value
    while(index > 0 && MillionIntegerList[index - 1].value == blacklisted){
          --index;
    }
    while(MillionIntegerList[index].value == blacklisted){
          //Do your stuff
          ++index;
    } 
}

此解决方案的费用为 O(m log n + mk),其中 k MillionInterList中每个黑名单整数的平均重复数。

答案 6 :(得分:0)

将HashSet用于阻止列表。

foreach(integer i in MillionIntegerList)
{
        //check if blockedlist contains i
        //do what ever you like. 
}

答案 7 :(得分:-2)

对List使用Except方法。这将有效