【问题标题】:Use Group By in order to remove duplicates使用 Group By 来删除重复项
【发布时间】:2013-07-09 18:57:13
【问题描述】:

我正在寻找一种简单的方法来删除重复项,而无需实现 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 子句 - 如果只有一个人具有特定姓名,则该人的年龄最大。

标签: c# linq collections duplicates


【解决方案1】:
people
  .GroupBy(p => p.Name)
  .Select(g => g.OrderByDescending(p => p.Age).First())

这将适用于不同的 Linq 提供程序。如果这只是 Linq2Objects,并且速度很重要(通常情况下并不重要),请考虑使用网络上的许多 MaxBy 扩展之一(这里是 Skeet's)并替换

g.OrderByDescending(p => p.Age).First()

g.MaxBy(p => p.Age)

【讨论】:

    【解决方案2】:

    只要您首先创建一个辅助函数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 函数仅执行一次。

    【讨论】:

    • 即使没有辅助函数也非常简单。 ;)
    • @MichaelBray 如果您指的是支出者的解决方案,那么问题是您不必为了获得最大序列而对整个序列进行排序;这避免了这一点。如果在您的特定情况下,您不在乎,那很好,但在一般情况下,始终对数据进行排序以获取属性具有最大值的项目并不总是合适的。
    • Servy:有效点。您应该澄清您的帖子,以便清楚这是您通过提供的代码获得的优势。
    【解决方案3】:

    我更喜欢简单:

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

    使 Person 实现 IComparable

    【讨论】:

      【解决方案4】:

      我放弃了我的最后一个答案,因为我意识到它太慢而且太复杂了。这是更有意义的解决方案

              var peoplewithLargestAgeByName =
                  from p in people
                  orderby p.Name
                  group p by p.Name into peopleByName
                  select peopleByName.First ( );
      

      这与@spender 提供的解决方案相同,但使用的是 linq 语法。

      【讨论】:

      • 您的查询选择的是一组年龄,而不是人。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多