使用Group By 来去除重复项

3

我希望找到一种简单的方法来去除重复项,而不必实现IComparable类,覆盖GetHashCode等。

我认为可以通过linq实现。 我有以下类:

class Person
{
    public string Name;
    public ing Age;
}

我是一位有用的助手,可以翻译文本。

我有一个大约500人的名单List<Person> someList = new List<Person()

现在我想删除同名的人,如果有重复的话,我想保留年龄较大的人。换句话说,如果我的列表如下:

Name----Age---
Tom,     24  |
Alicia,  22  |
Alicia,  12  |

我希望最终得到的是:

我想结束:

Name----Age---
Tom,     24  |
Alicia,  22  |

我该如何用查询方式实现这个?我的列表不太长,所以我不想创建哈希集或实现IComparable接口。如果我能用linq查询来实现就好了。
我认为可以使用groupBy扩展方法来完成,例如:
var people = // the list of Person
person.GroupBy(x=>x.Name).Where(x=>x.Count()>1)
      ...    // select the person that has the greatest age...

不需要 Where 子句 - 如果只有一个人有特定的姓名,那个人将拥有最大的年龄。 - Michael Bray
4个回答

8
people
  .GroupBy(p => p.Name)
  .Select(g => g.OrderByDescending(p => p.Age).First())

这将适用于不同的Linq提供程序。如果只是针对Linq2Objects,而且速度很重要(通常不是),请考虑使用网络上找到的众多MaxBy扩展之一(这里是Skeet的),并进行替换。
g.OrderByDescending(p => p.Age).First()

使用

g.MaxBy(p => p.Age)

3

只要您首先创建一个能够选择序列中选择器最大值的辅助函数MaxBy,这将变得非常简单。不幸的是,LINQ中的Max函数无法按我们想要的方式选择序列中的项目,而不是选择的值。

var distinctPeople = people.GroupBy(person => person.Name)
   .Select(group => group.MaxBy(person => person.Age));

接下来是MaxBy的实现:

public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector, IComparer<TKey> comparer = null)
{
    comparer = comparer ?? Comparer<TKey>.Default;

    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
            throw new ArgumentException("Source must have at least one item");

        var maxItem = iterator.Current;
        var maxKey = keySelector(maxItem);

        while (iterator.MoveNext())
        {
            var nextKey = keySelector(iterator.Current);
            if (comparer.Compare(nextKey, maxKey) > 0)
            {
                maxItem = iterator.Current;
                maxKey = nextKey;
            }
        }

        return maxItem;
    }
}

请注意,虽然您可以通过对序列进行排序然后获取第一个项目来达到相同的结果,但通常情况下这样做比使用 max 函数进行一次遍历效率要低。

即使没有辅助函数,这也非常容易。 ;) - Michael Bray
如果你指的是spender的解决方案,那么问题在于你不应该必须对整个序列进行排序才能获得最大值;这种方法避免了这种情况。如果在你的特定情况下,你不关心这一点,那就没问题了,但在一般情况下,总是将数据排序以获取属性值最大的项并不总是合适的。 - Servy
Servy:一个很好的观点。你应该澄清你的帖子,以便清楚地表明这是你通过提供的代码获得的优势。 - Michael Bray

0

我更喜欢简单:

var retPeople = new List<Person>;
        foreach (var p in person)
        {
            if(!retPeople.Contains(p))
            {
                retPeople.Add(p);
            }
        }

实现IComparable接口的个人


-1
我删除了我的上一个答案,因为我意识到它太慢了,而且太复杂了。这里是一个更合理的解决方案。
        var peoplewithLargestAgeByName =
            from p in people
            orderby p.Name
            group p by p.Name into peopleByName
            select peopleByName.First ( );

这个解决方案与@spender贡献的解决方案相同,只是使用了linq语法。

1
您的查询正在选择一堆年龄,而不是人。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接