更新
您可以做一个简单的技巧 - 将前缀按其第一个数字分组到字典中,并仅将数字与正确的子集匹配。我假设每个前缀至少有三个数字,我使用以下两个 LINQ 语句对其进行了测试。
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 运行时
-----------------
1 10.198 毫秒
2 1.179 毫秒
3 205 毫秒
4 130 毫秒
5 107 毫秒
只是给出一个粗略的想法——我只跑了一次,还有很多其他的事情要做。
原始答案
我不太关心性能——一台普通的台式电脑可以轻松处理具有 1 亿行的数据库表。也许这需要五分钟,但我假设您不想每隔一秒执行一次任务。
我刚做了一个测试。我生成了一个包含 5 到 10 位数字的 15.000 个唯一前缀的列表。从这个前缀中,我生成了 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();
好吧,在我的 2.0 GHz Core 2 Duo 笔记本电脑上花了大约一分钟。因此,如果一分钟的处理时间是可以接受的,如果包括聚合,可能是两到三分钟,我不会尝试优化任何东西。当然,如果程序可以在一两秒内完成任务,那将是非常好的,但这会增加相当多的复杂性和许多出错的地方。设计、编写和测试都需要时间。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; }
}
}
}