使用C#多次比较2个巨大的列表(带有扭曲)

gus*_*gus 4 c# compare list matching

嘿大家,你来到这里的社区很棒.我是一名电气工程师,在一边做一些"编程"工作,以帮助支付账单.我这样说是因为我希望你考虑到我没有适当的计算机科学培训,但我在过去的7年里一直在编码.

我有几个带有信息的excel表(全数字),基本上是一列中的"拨打的电话号码"和另一列中的每个号码的分钟数.另外,我有一个"我的国家/地区不同运营商的"运营商前缀代码编号列表.我想要做的是分离每个运营商的所有"流量".这是场景:

第一个拨打的号码行:123456789ABCD,100 < - 这将是一个13位数的电话号码和100分钟.

我有一份载体1的12,000多个前缀代码列表,这些代码的长度各不相同,我需要检查每个代码:

前缀代码1:1234567 < - 此代码长度为7位.

我需要检查所拨号码的前7位数字,并将其与拨打的号码进行比较,如果找到匹配,我会将分钟数添加到小计中供以后使用.请注意,并非所有前缀代码都是相同的长度,有时它们更短或更长.

大部分应该是小菜一碟,我应该能够做到这一点,但我对大量的数据感到害怕; 有时拨打的号码列表包含多达30,000个号码,"运营商前缀码"列出大约13,000行,我通常会检查3个号码,这意味着我必须做很多"匹配".

有没有人知道如何使用C#有效地做到这一点?或任何其他语言诚实坦诚.我需要经常这样做,设计一个工具来做更有意义.我需要一个有"计算机科学家"背景的人的良好视角.

列表不需要在excel工作表中,我可以导出到csv文件并从那里工作,我不需要"MS Office"界面.

谢谢你的帮助.

更新:

谢谢大家回答我的问题.我想在我的无知中,我夸大了"有效"这个词.我每隔几秒钟就不会执行这项任务.这是我每天必须做的事情,我讨厌使用Excel和VLOOKUP等.

我已经了解了你们的新概念,我希望我能用你的想法建立一个解决方案.

Dan*_*ner 11

UPDATE

您可以执行一个简单的技巧 - 将前缀按其第一个数字分组到字典中,并仅将数字与正确的子集匹配.我使用以下两个LINQ语句测试它,假设每个前缀至少有三个digis.

const Int32 minimumPrefixLength = 3;

var groupedPefixes = prefixes
    .GroupBy(p => p.Substring(0, minimumPrefixLength))
    .ToDictionary(g => g.Key, g => g);

var numberPrefixes = numbers
    .Select(n => groupedPefixes[n.Substring(0, minimumPrefixLength)]
        .First(n.StartsWith))
    .ToList();
Run Code Online (Sandbox Code Playgroud)

那有多快?15.000个前缀和50.000个数字花费不到250毫秒.两行代码足够快?

请注意,性能在很大程度上取决于最小前缀长度(MPL),因此取决于您可以构造的前缀组的数量.

     MPL    Runtime
    -----------------
     1     10.198 ms
     2      1.179 ms
     3        205 ms
     4        130 ms
     5        107 ms

只是为了给出一个粗略的想法 - 我只做了一次运行并且还有很多其他的东西在继续.

原始答案

我不太关心性能 - 平均台式电脑可以安静地轻松处理1亿行的数据库表.也许需要五分钟,但我认为你不想每隔一秒执行一次任务.

我刚做了一个测试.我生成了一个包含15.000个唯一前缀的列表,其中包含5到10个数字.从这个前缀我生成50.000个数字,前缀和额外的5到10位数字.

List<String> prefixes = GeneratePrefixes();
List<String> numbers = GenerateNumbers(prefixes);
Run Code Online (Sandbox Code Playgroud)

然后我使用以下LINQ to Object查询来查找每个数字的前缀.

var numberPrefixes = numbers.Select(n => prefixes.First(n.StartsWith)).ToList();
Run Code Online (Sandbox Code Playgroud)

好吧,我的Core 2 Duo笔记本电脑花了大约一分钟就用了2.0 GHz.因此,如果一分钟的处理时间是可接受的,如果你包括聚合,可能是两三个,我不会尝试优化任何东西.当然,如果程序可以在一两秒内完成任务,那将是非常好的,但这会增加相当多的复杂性和许多出错的事情.设计,编写和测试需要时间.LINQ语句花了我几秒钟.

测试应用

请注意,生成许多前缀非常慢,可能需要一两分钟.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace Test
{
    static class Program
    {
        static void Main()
        {
            // Set number of prefixes and calls to not more than 50 to get results
            // printed to the console.

            Console.Write("Generating prefixes");
            List<String> prefixes = Program.GeneratePrefixes(5, 10, 15);
            Console.WriteLine();

            Console.Write("Generating calls");
            List<Call> calls = Program.GenerateCalls(prefixes, 5, 10, 50);
            Console.WriteLine();

            Console.WriteLine("Processing started.");

            Stopwatch stopwatch = new Stopwatch();

            const Int32 minimumPrefixLength = 5;

            stopwatch.Start();

            var groupedPefixes = prefixes
                .GroupBy(p => p.Substring(0, minimumPrefixLength))
                .ToDictionary(g => g.Key, g => g);

            var result = calls
                .GroupBy(c => groupedPefixes[c.Number.Substring(0, minimumPrefixLength)]
                    .First(c.Number.StartsWith))
                .Select(g => new Call(g.Key, g.Sum(i => i.Duration)))
                .ToList();

            stopwatch.Stop();

            Console.WriteLine("Processing finished.");
            Console.WriteLine(stopwatch.Elapsed);

            if ((prefixes.Count <= 50) && (calls.Count <= 50))
            {
                Console.WriteLine("Prefixes");
                foreach (String prefix in prefixes.OrderBy(p => p))
                {
                    Console.WriteLine(String.Format("  prefix={0}", prefix));
                }

                Console.WriteLine("Calls");
                foreach (Call call in calls.OrderBy(c => c.Number).ThenBy(c => c.Duration))
                {
                    Console.WriteLine(String.Format("  number={0} duration={1}", call.Number, call.Duration));
                }

                Console.WriteLine("Result");
                foreach (Call call in result.OrderBy(c => c.Number))
                {
                    Console.WriteLine(String.Format("  prefix={0} accumulated duration={1}", call.Number, call.Duration));
                }
            }

            Console.ReadLine();
        }

        private static List<String> GeneratePrefixes(Int32 minimumLength, Int32 maximumLength, Int32 count)
        {
            Random random = new Random();
            List<String> prefixes = new List<String>(count);
            StringBuilder stringBuilder = new StringBuilder(maximumLength);

            while (prefixes.Count < count)
            {
                stringBuilder.Length = 0;

                for (int i = 0; i < random.Next(minimumLength, maximumLength + 1); i++)
                {
                    stringBuilder.Append(random.Next(10));
                }

                String prefix = stringBuilder.ToString();

                if (prefixes.Count % 1000 == 0)
                {
                    Console.Write(".");
                }

                if (prefixes.All(p => !p.StartsWith(prefix) && !prefix.StartsWith(p)))
                {
                    prefixes.Add(stringBuilder.ToString());
                }
            }

            return prefixes;
        }

        private static List<Call> GenerateCalls(List<String> prefixes, Int32 minimumLength, Int32 maximumLength, Int32 count)
        {
            Random random = new Random();
            List<Call> calls = new List<Call>(count);
            StringBuilder stringBuilder = new StringBuilder();

            while (calls.Count < count)
            {
                stringBuilder.Length = 0;

                stringBuilder.Append(prefixes[random.Next(prefixes.Count)]);

                for (int i = 0; i < random.Next(minimumLength, maximumLength + 1); i++)
                {
                    stringBuilder.Append(random.Next(10));
                }

                if (calls.Count % 1000 == 0)
                {
                    Console.Write(".");
                }

                calls.Add(new Call(stringBuilder.ToString(), random.Next(1000)));
            }

            return calls;
        }

        private class Call
        {
            public Call (String number, Decimal duration)
            {
                this.Number = number;
                this.Duration = duration;
            }

            public String Number { get; private set; }
            public Decimal Duration { get; private set; }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Jon*_*eet 8

听起来像你需要从运营商前缀构建一个trie.您最终会得到一个trie,终结节点会告诉您该前缀的运营商.

然后创建一个从载体到a intlong(总数)的字典.

然后,对于每个拨打的号码行,只需沿着特里列车行驶,直到找到载体为止.找到目前为止运营商的总分钟数,然后添加当前行 - 然后继续.