【问题标题】:Best method to remove items from a list从列表中删除项目的最佳方法
【发布时间】:2015-04-16 10:29:53
【问题描述】:

我有一个包含 500.000 到 1.000.000 个 MyClass 实例的列表,它具有以下属性:

class MyClass
{
    string ParentId;
    string Name;
    DateTime StartDate;
    DateTime EndDate;
}

数据可能如下所示:

ParentId | Name    | StartDate    | EndDate
----------------------------------------------
parent1  | alpha   | 01-01-2011   | 02-02-2015
parent1  | beta    | 01-01-2011   | 02-02-2014
parent2  | gamma   | 01-01-2012   | 02-02-2011

我需要过滤列表,使其包含“alpha”和“gamma”对象。应该排除“beta”对象,因为它与 alpha 具有相同的父对象,但 EndDate 更早。

即结果列表应该只包含每个 ParentId 的一个实例(具有最新 EndDate 的那个)。

过滤需要表现良好。

【问题讨论】:

  • myClasses.Where(x => x.Name == "alfa" || x.Name == "gamma").Distinct(),它应该是高效的 - 如果您想了解速度有多快,请使用 System.Timer()
  • 过滤和删除不一样。你在问什么?你是什​​么意思“表现良好”?存储了多少对象?树有多深?对于大量和/或深度树,您需要树结构
  • 你不应该在数据库中这样做吗?
  • 为什么这么多?它们是从数据库加载的吗?为什么不在数据库中过滤它们?无论如何,有这么多的项目,你需要一个专门的树集合,而不是一个简单的列表,特别是如果有很多或很深的父子关系。
  • @callum 类似于第一个答案,但都未通过 EndDate 比较进行检查。如果我理解预期结果,过滤不是在名称 == 'alpha' 或 'gamma' 上,而是在 ParentID && where max EndDate 上的 group --> 基于示例导致 alpha 和 gamma 记录。

标签: c# linq lambda ienumerable


【解决方案1】:

您可以使用GroupBy 和Select:

var filtered = list
              .GroupBy(mc=>mc.ParentId)
              .Select(g=>g.OrderByDescending(mc=>mc.EndDate).First())
              .ToList();

【讨论】:

  • 是否可以通过.SelectMany().Distinct() 获得相同的结果?
  • @CallumLinington - 我真的没有理由在这里使用SelectMany,First 更优雅,因为我们知道每个组只需要一个元素.如果我们知道只选择一个元素,我们也不需要Distinct。
  • 如果这些组没有任何内容怎么办?
  • @Callum Linington - 我不确定你的意思。这些组是从现有的 MyClass 实例创建的(名字不好)。此处不能存在空组 - 它一开始就不会生成。我们取MyClass,并根据其ParentId进行分组。
【解决方案2】:

我假设您想过滤掉 beta 是出于解释的原因,而不是因为它的名称。您可以使用以下方法来实现这样的结果:

myClasses.GroupBy(i => i.ParentId)
    .Select(i => i.OrderByDescending(i2 => i2.EndDate).First());

【讨论】:

  • 请注意,您在嵌套的 lambda 表达式中使用了两次 i:i => i.OrderByDescending(i => i.EndDate) - 这不会编译。除此之外,这与我的答案相同。
  • 我编辑并修复了它:谢谢。至于你的回答,我投了:)
【解决方案3】:

虽然当前接受的答案(@Kobi)是正确的并且可能是最简单的解决方案,但它可能不是“最佳”解决方案。

特别是,由于您提到列表中可能有很多项目并且解决方案应该表现良好,我想我会检查没有 LINQ 的解决方案的执行情况。

这是我的解决方案:

var tempDict = new Dictionary<string, MyClass>();
foreach (var data in list) // list is the List<MyClass>
{
    MyClass existing;
    if (!tempDict.TryGetValue(data.ParentId, out existing))
    {
        // Put item into temp dictionary (use ParentId as key)
        tempDict[data.ParentId] = data;
    }
    else
    {
        // Check if the instance in the temp dictionary has an
        // earlier EndDate. If yes, replace it.
        if (existing.EndDate < data.EndDate) // replace
            tempDict[data.ParentId] = data;
    }
}

var result = tempDict.Values.ToList();

快速比较(使用 500.000 个项目)表明,此解决方案比 LINQ 版本快大约 3 到 4 倍(取决于唯一 ParentId 值的数量)。

【讨论】:

    【解决方案4】:

    您可以使用Linq.Where 轻松过滤List&lt;T&gt;

    var result = myList
        .Where(item => item.Name == "gamma" || item.Name == "alfa")
        .ToList();
    

    如果你想区分某个字段的输出,你可以使用MoreLinq'sDistinctBy

    或GroupBy:

    var result = myList
        .Where(item => item.Name == "gamma" || item.Name == "alfa")
        .GroupBy(item => item.ParentId)
        .Select(g => g.First()) //Selection logic
        .ToList();
    

    【讨论】:

    • 也需要Distinct()?以确保其独一无二
    • 抱歉我的问题不清楚。我无法过滤名称,我需要查看 ParentId 和 EndDate
    • 考虑到这一点,我试图回答。让我知道它是否正确。
    • OP 对要求不是很清楚。其他答案中的 Group、Select、Orderby 是这里的解决方案,因为我认为他们想要每个父母的最新项目。 “gamma”等是红鲱鱼。
    【解决方案5】:

    你可以使用它,这种方法在大数组上运行良好且快速:

    var groupesList = yourList.GroupBy(x => x.ParentId,
         (y, set) => new {Key = y, Value = set.First(s => s.EndDate == set.Max(r => r.EndDate))}).Select(x => x.Value).ToList();
    

    【讨论】:

      猜你喜欢
      • 2016-08-09
      • 2010-09-17
      • 1970-01-01
      • 1970-01-01
      • 2018-08-12
      • 1970-01-01
      • 2010-11-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多