【问题标题】:Fastest way of updating items of one list from another using StartsWith使用 StartsWith 从另一个列表更新项目的最快方法
【发布时间】:2017-07-21 13:04:09
【问题描述】:

我有一个场景,我需要根据另一个列表中的数据更新一些项目。我已经在这里解决了各种问题,但没有任何帮助。

场景

listA: 总数约为 88000

public class CDRs
 {
    public string cld { get; set; }
    public string prefix2 { get; set; }
    public string country { get; set; }
    public string city { get; set; }
 }

listB:总数:3000。

public class RatesVM
    {
        public string prefix { get; set; }
        public string Country { get; set; }
        public string City { get; set; }
    }

现在在 listB 中可以有多个匹配的 listA 字段,即 cld

例如。 listA.cld = "8801123232"; 我得到的 ListB 中匹配的前缀是

880     BGD Proper
8801    BGD Mobile
88011   BGD Dhaka Mobile
88017   BGD Dhaka Mobile
88018   BGD Dhaka Mobile
88019   BGD Dhaka Mobile

现在我想要在这种情况下最接近的匹配是

88011   BGD Dhaka Mobile

我现在正在遵循的方法。

foreach (var x in listA)
            {
                var tempObj = listB.FirstOrDefault(y => x.cld.StartsWith(y.prefix));
                if (tempObj != null)
                {
                    x.prefix2 = tempObj.prefix;
                    x.country = tempObj.Country;
                    x.city = tempObj.City;
                }
                else
                {
                    x.prefix2 = "InBound";
                    x.country = "Unknown";
                    x.city = "Unknown";
                }
            }

它工作正常,但需要很多时间。 本案例大约需要 2-3 分钟

在少数情况下 ListA 会有大约 100 万条记录。我担心这会花很长时间。

提前致谢

【问题讨论】:

  • 为什么需要找到最接近的匹配项?为什么不能直接链接?另外,您在标题中提到了Contains,但您没有显示代码。
  • @TimSchmelter 我假设他们的意思是 StartsWith 并编辑 em 的标题:P
  • 如果将列表拆分为计算机上的核心数并使用 Parallel.Foreach() 方法,速度会更快一些。 --> tutorial
  • 这些选项中的任何一个对你有用吗@JitenderSharma ?
  • @mjwills - 事实上,你的是这里最快的,在此之后 Tim-Schmelter 的回答也适用于某些场景,但是当我有 200 万条记录时,它会变慢,这很常见根据我的要求。所以我决定避免这种情况,并且只将数据存储在我需要的 sql 中。

标签: c# asp.net list ienumerable


【解决方案1】:

我建议使用以下代码。关键区别在于使用orderedListB 来确保您获得最具体的匹配(即首先从最长的前缀开始),以及使用Dictionary 来缓存结果。 *

Dictionary<string, RatesVM> cache = new Dictionary<string, RatesVM>();
var orderedListB = listB.OrderByDescending(z => z.prefix.Length).ToList();

foreach (var x in listA)
{
    RatesVM cached;
    cache.TryGetValue(x.cld, out cached);
    var tempObj = cached ?? orderedListB.FirstOrDefault(z => x.cld.StartsWith(z.prefix));

    if (tempObj != null)
    {
        if (cached == null)
        {
            cache.Add(x.cld, tempObj);
        }

        x.prefix2 = tempObj.prefix;
        x.country = tempObj.Country;
        x.city = tempObj.City;
    }
    else
    {
        x.prefix2 = "InBound";
        x.country = "Unknown";
        x.city = "Unknown";
    }
}

您可能还想考虑使用 Parallel.ForEach 而不仅仅是 foreach。

【讨论】:

    【解决方案2】:

    您的问题很难,因为您需要“最接近”的解决方案,而不是任何解决方案。这会强制您针对listA 中的每个元素迭代listB 中的每条记录。

    由于您需要为listA 中的每个元素提供答案,因此您必须检查其中的每个元素。

    不过,您可以通过创建树形结构来预处理listB。您为 B 中所有字符串的每个不同的第一个数字创建一个节点。然后该节点将成为 listB 中以该数字开头的所有记录的父节点。该节点下方的节点将保存字符串中的第二个数字,依此类推。

    继续为您画出这样一棵树可能是什么样子的视觉概念:

    现在,如果您在 listB 中搜索,则不必遍历整个 listB,而只需遍历列表,这将使您每次迭代的时间从 O(n) 增加到 O(log n)

    您将获取listA 中记录中的第一个字母并将其与树进行比较,然后在该分支中遍历(立即消除您需要比较的大量记录,从而提高您的性能)。然后比较第二个字母,依此类推,直到在树中找不到更多字母。当您停下来时,您在listB 中找到了最长的匹配记录,保证了“最接近”的匹配,而FirstOrDefault(\x -&gt; x.StartsWith()) 根本不会这样做! (它只找到第一个匹配项,几乎总是只找到第一个字母!)。

    您只需为listA 中的所有搜索创建一次此树,如果listB 中有更改,您也可以轻松更新树。

    如果您在一台具有多个内核的体面机器上运行它,您还可以并行化此搜索。它增加了您正在编写的程序的复杂性,因为您需要管理哪些线程在 listA 中搜索哪些记录,尽管这将极大地提高性能并大大减少所需的时间。

    【讨论】:

      猜你喜欢
      • 2017-08-25
      • 1970-01-01
      • 2013-08-28
      • 1970-01-01
      • 1970-01-01
      • 2020-06-20
      • 2014-04-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多