删除多个List<>中的重复对象使用LINQ的对象

时间:2014-08-21 15:28:33

标签: c# linq

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

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

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

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

例如:

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执行此操作的任何建议?我也在寻找自己的解决方案,我想在这里发帖看看它是否比我自己搞定更快。 :)

5 个答案:

答案 0 :(得分:0)

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

修改结果的Equals方法,以便在名称匹配时,结果的线程为“相等”。 (这是不正确的,但是在检查项目存在时,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);
            }

无论您提供add-calls的顺序,包含的元素都是:

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

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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

如果您想获取Result s:

的列表,这是一个解决方案
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 :(得分:0)

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

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)));
}

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

答案 4 :(得分:0)

您可以使用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);