【问题标题】:Removing duplicate objects across several List<> objects using LINQ使用 LINQ 删除多个 List<> 对象中的重复对象
【发布时间】:2014-08-22 01:19:41
【问题描述】:

我有几个看起来像这样的类(简化为 SO):

public class Result
{
    pubic List<Review> Reviews { get; set; }
}

public class Review
{
    public string Name { get; set; }
    public int Amount { get; set; }
}

我开始的是一个List&lt;Result&gt; 对象。列表中的每个“结果”对象显然都有另一个 List 属性。在每个“结果”对象中,其评论列表永远不会有重复的“名称”属性。但是,在预期的情况下,列表中的所有 Result 对象都会有重复的“名称”属性。

例如:

var results = new List<Result>();

var result1 = new Result();
result1.Reviews = new List<Review>();
result1.Reviews.Add(new Review { Name = "ABC", Amount = 5 });
result1.Reviews.Add(new Review { Name = "DEF", Amount = 4 });
results.Add(result1);

var result2 = new Result();
result2.Reviews = new List<Review>();
result2.Reviews.Add(new Review { Name = "ABC", Amount = 1 });
result2.Reviews.Add(new Review { Name = "WRA", Amount = 4 });
results.Add(result2);

var result3 = new Result();
result3.Reviews = new List<Review>();
result3.Reviews.Add(new Review { Name = "ABC", Amount = 2 });
result3.Reviews.Add(new Review { Name = "ARA", Amount = 4 });
results.Add(result3);

因此,您可以看到,在整个结果列表中,名称“ABC”在评论列表中重复出现。我需要做的是编写一个 LINQ 查询,对 results 对象进行操作,该对象将从子列表中删除所有重复的 Review 对象,最高的除外。因此,在这种情况下,result2 和 result3 的“ABC”评论都会从列表中完全删除。

关于如何使用 LINQ 执行此操作的任何建议?我也在自己寻找解决方案,只是想我会在这里发布,看看它是否比我自己解决它更快。 :)

【问题讨论】:

  • 不太确定,但是var query = result1.Reviews.Concat(result2.Reviews).Concat(result3.Reviews).GroupBy(r =&gt; r.Name).Select(grp =&gt; new Review { Name = grp.Key, Amount = grp.Max(r =&gt; r.Amount)});
  • 结果对象的数量未定义。我不想那样硬编码,但谢谢。
  • 在这种情况下,您可以使用SelectMany 获取Review 的扁平列表,例如List&lt;Review&gt; reviewList = list.SelectMany(r =&gt; r.Reviews).ToList();,其中您的listList&lt;Result&gt;
  • “最高”是指Review 最大的Amount

标签: c# linq


【解决方案1】:

不要为Reviews 使用公共属性,而是使用私有属性。然后提供您自己的add() 方法并插入或替换元素。

修改 Result 的 Equals 方法,因此当名称匹配时,Results 线程“相等”。 (这是不正确的,但会使contains()indexOf() 在检查项目存在时按照我们希望的方式工作):

public class Review
    {
        public string Name { get; set; }
        public int Amount { get; set; }

        public override bool Equals(System.Object obj)
        {
            // If parameter is null return false.
            if (obj == null)
            {
                return false;
            }


            Review r = obj as Review;
            if ((System.Object)r == null)
            {
                return false;
            }

            //only compare on the name.
            return Name.Equals(r.Name);
        }
    }

现在,结果和添加方法:

class Result
    {
        private List<Review> _Reviews = new List<Review>();
        public List<Review> Reviews { get { return _Reviews; } }

        public void Add(Review r)
        {
            if (_Reviews.Contains(r))
            {
                //check if difference on Amount
                int index = _Reviews.IndexOf(r);
                Review currentReview = _Reviews[index];

                if (currentReview.Amount < r.Amount)
                {
                    //replace
                    _Reviews.RemoveAt(index);
                    _Reviews.Add(r);
                }
                //else keep existing
            }
            else
            {
                //Add
                _Reviews.Add(r);
            }
        }
    }

最后,您可以将每条评论添加到相同的结果中,而无需处理任何事情:

var result = new Result();

            result.Add(new Review { Name = "ABC", Amount = 5 });
            result.Add(new Review { Name = "DEF", Amount = 4 });
            result.Add(new Review { Name = "ABC", Amount = 1 });
            result.Add(new Review { Name = "WRA", Amount = 4 });
            result.Add(new Review { Name = "ABC", Amount = 2 });
            result.Add(new Review { Name = "ARA", Amount = 4 });

            foreach (Review r in result.Reviews)
            {
                MessageBox.Show(r.Name + ": " + r.Amount);
            }

无论您提供添加调用的顺序是什么,包含的元素是:

ABC:5 
DEF:4
WRA:4
ARA:4

(如果需要,按名称排序)

【讨论】:

    【解决方案2】:

    这里有一个解决方案,如果你想获取Results的列表:

    List<Result> newList = results.SelectMany(res => res.Reviews.Select(r => new { Result = res, Review = r }))
            .GroupBy(pair => pair.Review.Name)
            .Select(grp => grp.First())
            .GroupBy(pair => pair.Result, pair => pair.Review)
            .Select(reviews =>
                    {
                        // Here you can create new Result object of use the original one
                        Result result = reviews.Key; // Here is original Result object
                        result.Reviews = reviews.ToList(); // Replacing Reviews list with the filtered one
                        return result;
                    })
            .ToList();
    

    这个查询的缺点是它会删除没有评论的结果。

    如果您不需要原始 Result 对象,则可以使用简化版查询:

    List<Result> newList = results.SelectMany(res => res.Reviews)
                                  .GroupBy(r => r.Name)
                                  .Select(grp => grp.First())
                                  .ToList();
    

    【讨论】:

      【解决方案3】:

      这将为您提供所有评论的 Enumerable 并过滤以排除除最大重复项之外的所有内容。然后,您可以随意处理它们。

      results
          .SelectMany(l => l.Reviews) // flattens the lists
          .GroupBy(r => r.Name)       // groups all reviews of the same thing together
          .Select(g => new Review {Name = g.Key, Amount = g.Max(x => x.Amount)})  // select only the highest of each group
      

      【讨论】:

        【解决方案4】:

        这是一个详细的解决方案,但它会产生您的要求:

        var rev = results.SelectMany(r => r.Reviews).GroupBy(n => n.Name).Where(c => c.Count() > 1);
        
        foreach (var item in rev)
        {
           Review review = results.SelectMany(r => r.Reviews).First(x => x.Name == item.Key);
           results.ForEach(r => r.Reviews.RemoveAll(re => re.Name == item.Key && !re.Equals(review)));
        }
        

        这将删除除第一次出现的所有重复项。

        【讨论】:

          【解决方案5】:

          您可以使用SelectMany 从所有结果中获取所有评论的平面序列,您可以按名称对它们进行分组,过滤掉包含多个项目的组,选择后面的所有项目第一个,然后从结果中删除所有这些评论。

          查询语法比方法语法更方便。

          var query = from result in results
                      from review in result.Reviews
                      group new { result, review } by review.Name into matches
                      where matches.Count() > 1
                      from match in matches
                          .OrderByDescending(match => match.review.Amount)
                          .Skip(1)
                      select match;
          
          foreach (var match in query)
              match.result.Reviews.Remove(match.review);
          

          【讨论】:

            猜你喜欢
            • 2019-06-01
            • 1970-01-01
            • 1970-01-01
            • 2014-06-14
            • 1970-01-01
            • 2012-04-17
            • 2011-02-16
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多