首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >我需要比较两个很大的集合和可能缺少的元素。

我需要比较两个很大的集合和可能缺少的元素。
EN

Stack Overflow用户
提问于 2013-10-11 07:52:44
回答 4查看 2.2K关注 0票数 8

我在为我的工作写的程序中遇到了一堵砖墙。你不需要知道具体的上下文,但长话短说,我有两套大约650 k的记录。

让我们假设集合A是正确的,集合B是我知道的不正确的集合。

集合B包含一个复杂对象,它具有与集合A中的元素相同类型的属性(换句话说,它看起来有点像这样):

代码语言:javascript
复制
// Where T : IComparable
IEnumerable<DateTime> A = ...; // Collection of T elements
IEnumerable<Complex> B = ...; // Collection of complex elements.
class Complex<DateTime>
{
   public DateTime Time { get; set; }
   .....
}

我的问题是,我基本上需要对A进行顺序枚举,并查看A的当前元素是否存在于B中的复杂对象中;如果它不存在,则需要创建一个将封装该元素的复杂对象(以及其他内容)。

当我意识到这两个列表都有650,000个元素长时,就会出现这个问题。我不能减少设定的数据,我必须使用这650,000。现在,我已经使用了ICollection.Contains(),并且尝试了二进制搜索的(朴素)实现,但是它所花费的时间太长了。

你对我有什么建议吗?

编辑:如果有帮助的话,T会实现IComparable。EDIT2:一些更多的上下文:使用Linq从DataTable中检索IEnumerable。

代码语言:javascript
复制
        IEnumerable<Complex> set = set.Tbl
            .Where(dbObject => dbObject.TS.CompareTo(default(DateTime)) != 0)
            .Select(Func<DataRow,Complex>) // Function that wraps the DataRow in a Complex object
            // Just done to make debugging a little easier so we still have a large sample but small enough that it doesn't make me grow a beard
            .Take(100000) 
            .AsEnumerable<Complex>();

为了完整性起见,如果这个问题被归档,而且其他人需要解决这个问题,我当前的实现看起来有点像这样

代码语言:javascript
复制
        BDataSet bSet = new BDataSet();
        B_LUTableAdapter adap = new B_LUTableAdapter();
        adap.Fill(bSet.B_LU);
        IEnumerable<Complex> w3 = bSet.B
            .Where(dbObject => dbObject.TS.CompareTo(default(DateTime)) != 0)
            // Function that just wraps datarow into a complex object
            .Select(Func<DataRow, Complex>)
            // Just for sake of debugging speed
            .Take(100000)
            .AsEnumerable<Complex>();

        List<Complex> b = bSet.OrderBy(x => x.Time).ToList<Complex>();
        // Get last & first timestamps
        // Some of the timestamps in b are 01/01/1011 for some reason,
        // So we do this check.
        Complex start = b.Where(x => x.Time != default(DateTime)).First();
        Complex end = b.Last();

        List<DateTime> a = new List<DateTime>();
        // RoundSeconds reduces seconds in a DateTime to 0.
        DateTime current = RoundSeconds(new DateTime(start.Time.Ticks));

        while (current.CompareTo(RoundSeconds(end.Time)) <= 0)
        {
            a.Add(current);
            current = current.Add(TimeSpan.FromMinutes(1));
        }

        IEnumerable<DateTime> times = b.Select(x => x.Time);
        var missing = a.Where(dt => times.Contains(dt));
        foreach (var dt in missing)
        {
            adap.Insert(dt, 0, "", "", "", null, 0, 0);
            // This has since been changed to List.Add()
        }

多亏了Cosmin,这个问题现在已经解决了,完成的实现如下: List expected = new List();DateTime current = RoundSeconds(new DateTime(start.Time.Ticks));

代码语言:javascript
复制
        while (current.CompareTo(RoundSeconds(end.Time)) <= 0)
        {
            expected.Add(current);
            current = current.Add(TimeSpan.FromMinutes(1));
        }

        Console.WriteLine("Expecting {0} intervals.", expected.Count);
        var missing = b.FindAllMissing(expected, x => x.Time);
        if(!missing.Any()) return;
        Console.WriteLine("{0} missing intervals.", missing.Count());
        foreach (var dt in missing)
        {
            b.Add(new Complex() { /* some values */ });
            //Console.WriteLine("\t> Inserted new record at {0}", dt);
        }

        //.....
        public static IEnumerable<Basic> FindAllMissing<Basic, Complex>(this IEnumerable<Complex> complexList,
        IEnumerable<Basic> basicList,
        Func<Complex, Basic> selector)
        {
            HashSet<Basic> inComplexList = new HashSet<Basic>();
            foreach (Complex c in complexList)
                inComplexList.Add(selector(c));
            List<Basic> missing = new List<Basic>();
            foreach (Basic basic in basicList)
                if (!(inComplexList.Contains(basic)))
                    missing.Add(basic);
            return missing;
        }
EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-10-11 07:56:57

一步步地:

  • 使用O(1)的一个泛型集合来创建已经在第二个集合中的T的快速搜索列表。
  • 枚举第二个集合,并从第一步开始将所有T放在集合中。
  • 枚举第一个集合,并检查每个项是否在第一步创建的集合中。因为该操作是O(1),所以现在有了一个O(n)解决方案。
  • 好好享受吧。

这里有一个类将该算法实现为泛型扩展方法,以使其更易于使用LINQ。Made将它的参数作为IEnumerable<T>并返回IEnumerable<T>,没有对类型(TComplex)作任何假设。在我的测试中,我使用Tuple<int,int>列表作为复杂类型,使用简单int作为简单类型。控制台应用程序用600000个值填充List<Tuple<int,int>>,然后在使用枚举数的简单List<int>中放置100000个值,以计数List<Tuple<int,int>>中没有找到的所有简单值;它太快了,以至于您没有机会看到它在工作,当您点击F5时,它只显示结果。

代码语言:javascript
复制
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication2
{

    static class FixProblem
    {
        public static IEnumerable<T> FindAllThatNeedCreating<T, Complex>(this IEnumerable<Complex> list_of_complex, IEnumerable<T> list_of_T, Func<Complex, T> extract)
        {
            HashSet<T> T_in_list_of_complex = new HashSet<T>();
            foreach (Complex c in list_of_complex)
                T_in_list_of_complex.Add(extract(c));
            List<T> answer = new List<T>();
            foreach (T t in list_of_T)
                if (!T_in_list_of_complex.Contains(t))
                    answer.Add(t);
            return answer;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Test the code
            List<Tuple<int, int>> complex = new List<Tuple<int, int>>();
            List<int> simple = new List<int>();

            // Fill in some random data
            Random rnd = new Random();
            for (int i = 1; i < 600000; i++)
                complex.Add(new Tuple<int, int>(rnd.Next(), rnd.Next()));

            for (int i = 1; i < 100000; i++)
                simple.Add(rnd.Next());

            // This is the magic line of code:
            Console.WriteLine(complex.FindAllThatNeedCreating(simple, x => x.Item1).Count());

            Console.ReadKey();

        }
    }
}
票数 4
EN

Stack Overflow用户

发布于 2013-10-11 08:10:05

更新:首先,停止使用数据集。我建议您将Linq用于SQL或实体框架。

试试这个:

代码语言:javascript
复制
        var lookup = B.ToLookup(c => c.MyComplex);

        var noMatch = from a in A
                      where !lookup.Contains(a)
                      select a;

这应该是更快的,但衡量。

那就试试

代码语言:javascript
复制
        var lookup = B.AsParallel().ToLookup(c => c.MyComplex);

        var noMatch = from a in A.AsParallel()
                      where !lookup.Contains(a)
                      select a;

再量一次。

显然,确保A中的对象类型覆盖了GetHashCode()Equals(object)的相关性和有效性。特别是GetHashCode()应该有很高的概率,不同的对象有不同的哈希码,而且仍然是快速的。

更新:因为我们现在知道A中的对象类型是DateTime,所以对GetHashCode()Equals(object)的需求是可以的。

代码变成

代码语言:javascript
复制
        var lookup = B.ToLookup(c => c.Time);

        var noMatch = from a in A
                      where !lookup.Contains(a)
                      select a;
票数 0
EN

Stack Overflow用户

发布于 2013-10-11 08:33:24

如果我已经理解了您的需求,那么我认为这个代码工作得很好。我已经将这两个集合的记录提高到650万条,并在11秒内完成。将其减少到650 K记录只需不到一秒钟。

代码语言:javascript
复制
var rnd = new Random();

var xs = Enumerable
    .Range(0, 650000)
    .Select(x => new Complex<int>()
    {
        MyComplex = rnd.Next(0, 100001)
    })
    .ToList();

var ys = Enumerable
    .Range(0, 650000)
    .Select(x => rnd.Next(0, 100001))
    .ToArray();

var xsLookup = xs.ToLookup(x => x.MyComplex);

var newYs = ys.Where(y => !xsLookup[y].Any()).ToArray();

newYs
    .ToList()
    .ForEach(y =>
    {
        xs.Add(new Complex<int>()
        {
            MyComplex = y
        });
    });
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19313010

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档