我有几个带有信息的excel表(全数字),基本上是一列中的“拨打的电话号码”和另一列中的每个号码的分钟数。另外,我有一个“我的国家/地区不同运营商的”运营商前缀代码编号列表。我想要做的是分离每个运营商的所有“流量”。这是场景:
第一个已拨打的号码行: 123456789ABCD,100 < - 这将是一个13位数的电话号码和100分钟。
我有一份载体1的12,000多个前缀代码列表,这些代码的长度各不相同,我需要检查每个代码:
前缀代码1 : 1234567 < - 此代码长度为7位数。
我需要检查拨打号码的前7位数字,并将其与拨打的号码进行比较,如果找到匹配项,我会将分钟数添加到小计中供以后使用。 请注意,并非所有前缀代码的长度都相同,有时会更短或更长。
这大部分应该是小菜一碟,我应该可以做到,但我对大量的数据感到害怕;有时拨打的号码列表包含多达30,000个号码,“运营商前缀码”列出大约13,000行,我通常会检查3个号码,这意味着我必须做很多“匹配”。
有没有人知道如何使用C#有效地做到这一点?或任何其他语言诚实坦诚。我需要经常这样做,设计一个工具来做更有意义。我需要一个有“计算机科学家”背景的人的良好视角。
列表不需要在excel工作表中,我可以导出到csv文件并从那里工作,我不需要“MS Office”界面。
感谢您的帮助。
更新
感谢大家回答我的问题。我想在我的无知中,我夸大了“有效”这个词。我每隔几秒钟就不会执行这项任务。这是我每天必须做的事情,我讨厌使用Excel和VLOOKUP等。
我已经从你们那里了解了新的概念,我希望我能用你的想法建立一个解决方案。
答案 0 :(得分:11)
<强>更新强>
您可以执行一个简单的技巧 - 将前缀按其第一个数字分组到字典中,并仅将数字与正确的子集匹配。我使用以下两个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();
这有多快? 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);
然后我使用以下LINQ to Object查询来查找每个数字的前缀。
var numberPrefixes = numbers.Select(n => prefixes.First(n.StartsWith)).ToList();
嗯,我的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; }
}
}
}
答案 1 :(得分:8)
听起来我需要从运营商前缀构建trie。你最终会得到一个trie,终结节点告诉你那个前缀的载体。
然后创建一个从运营商到int
或long
(总计)的字典。
然后,对于每个已拨打的号码行,只需沿着特里行进行,直到找到载体为止。找到目前为止运营商的总分钟数,然后添加当前行 - 然后继续。
答案 2 :(得分:1)
能够相当有效地完成此操作的最简单的数据结构将是一组集合。为每个运营商设置一个包含所有前缀的集合。
现在,将呼叫与运营商联系起来:
foreach (Carrier carrier in carriers)
{
bool found = false;
for (int length = 1; length <= 7; length++)
{
int prefix = ExtractDigits(callNumber, length);
if (carrier.Prefixes.Contains(prefix))
{
carrier.Calls.Add(callNumber);
found = true;
break;
}
}
if (found)
break;
}
如果您有10个运营商,则每次呼叫的集合中将有70个查找。但是在集合中查找不是太慢(比线性搜索快得多)。因此,这应该会比蛮力线性搜索更快地加速。
您可以更进一步,根据长度为每个运营商分组前缀。这样,如果运营商只有长度为7和4的前缀,那么每次查看该长度的前缀集时,您都知道只需要提取并查找这些长度。
答案 3 :(得分:1)
如何将数据转储到几个数据库表中,然后使用SQL查询它们?简单!
CREATE TABLE dbo.dialled_numbers ( number VARCHAR(100), minutes INT )
CREATE TABLE dbo.prefixes ( prefix VARCHAR(100) )
-- now populate the tables, create indexes etc
-- and then just run your query...
SELECT p.prefix,
SUM(n.minutes) AS total_minutes
FROM dbo.dialled_numbers AS n
INNER JOIN dbo.prefixes AS p
ON n.number LIKE p.prefix + '%'
GROUP BY p.prefix
(这是为SQL Server编写的,但对于任何其他DBMS来说都应该非常简单。)
答案 4 :(得分:0)
在数据库而不是C#中执行此操作可能更简单(不一定更高效)。
您可以在数据库中插入行,并在插入上确定载体并将其包含在记录中(可能在插入触发器中)。
然后你的报告就是桌子上的总和查询。
答案 5 :(得分:0)
我可能只是将条目放在List中,对其进行排序,然后使用binary search来查找匹配项。定制二进制搜索匹配条件以返回匹配的第一个项目然后沿列表迭代,直到找到一个不匹配的项目。二元搜索只需大约15次比较即可搜索30,000个项目的列表。
答案 6 :(得分:0)
您可能希望在C#中使用HashTable。
这样你就拥有了键值对,你的键可以是电话号码,你的价值就是总分钟数。如果在密钥集中找到匹配项,则修改总分钟数,否则添加新密钥。
然后您只需要修改搜索算法,不要查看整个密钥,而只查看它的前7位数。