Как получить дубликаты из списка с помощью LINQ? - PullRequest
165 голосов
/ 28 сентября 2010

У меня List<string> вроде:

List<String> list = new List<String>{"6","1","2","4","6","5","1"};

Мне нужно получить дубликаты в списке в новый список.Теперь я использую для этого вложенный цикл for.

Полученный list будет содержать {"6","1"}.

Есть ли идея сделать это с помощью LINQ или лямбда-выражения ?

Ответы [ 9 ]

231 голосов
/ 28 сентября 2010
var duplicates = lst.GroupBy(s => s)
    .SelectMany(grp => grp.Skip(1));

Обратите внимание, что при этом будут возвращены все дубликаты, поэтому, если вы хотите узнать только, какие элементы дублируются в исходном списке, вы можете применить Distinct к результирующей последовательности или использовать решение, данное Марком Байерсом.

171 голосов
/ 28 сентября 2010

Вот один из способов сделать это:

List<String> duplicates = lst.GroupBy(x => x)
                             .Where(g => g.Count() > 1)
                             .Select(g => g.Key)
                             .ToList();

GroupBy группирует одинаковые элементы, а Where отфильтровывает те, которые появляются только один раз, оставляя толькодубликаты.

37 голосов
/ 28 сентября 2010

Вот еще один вариант:

var list = new List<string> { "6", "1", "2", "4", "6", "5", "1" };

var set = new HashSet<string>();
var duplicates = list.Where(x => !set.Add(x));
23 голосов
/ 06 ноября 2013

Я знаю, что это не ответ на первоначальный вопрос, но вы можете оказаться здесь с этой проблемой.

Если вы хотите, чтобы в ваших результатах были все дубликаты, сработает следующее.

var duplicates = list
    .GroupBy( x => x )               // group matching items
    .Where( g => g.Skip(1).Any() )   // where the group contains more than one item
    .SelectMany( g => g );           // re-expand the groups with more than one item

В моей ситуации мне нужны все дубликаты, чтобы я мог пометить их в пользовательском интерфейсе как ошибки.

18 голосов
/ 28 сентября 2010

Я написал этот метод расширения на основе ответа @ Lee на OP. Примечание , использовался параметр по умолчанию (требуется C # 4.0).Однако перегруженного вызова метода в C # 3.0 будет достаточно.

/// <summary>
/// Method that returns all the duplicates (distinct) in the collection.
/// </summary>
/// <typeparam name="T">The type of the collection.</typeparam>
/// <param name="source">The source collection to detect for duplicates</param>
/// <param name="distinct">Specify <b>true</b> to only return distinct elements.</param>
/// <returns>A distinct list of duplicates found in the source collection.</returns>
/// <remarks>This is an extension method to IEnumerable&lt;T&gt;</remarks>
public static IEnumerable<T> Duplicates<T>
         (this IEnumerable<T> source, bool distinct = true)
{
     if (source == null)
     {
        throw new ArgumentNullException("source");
     }

     // select the elements that are repeated
     IEnumerable<T> result = source.GroupBy(a => a).SelectMany(a => a.Skip(1));

     // distinct?
     if (distinct == true)
     {
        // deferred execution helps us here
        result = result.Distinct();
     }

     return result;
}
10 голосов
/ 28 сентября 2010
  List<String> list = new List<String> { "6", "1", "2", "4", "6", "5", "1" };

    var q = from s in list
            group s by s into g
            where g.Count() > 1
            select g.First();

    foreach (var item in q)
    {
        Console.WriteLine(item);

    }
10 голосов
/ 28 сентября 2010

Надеюсь, что это поможет

int[] listOfItems = new[] { 4, 2, 3, 1, 6, 4, 3 };

var duplicates = listOfItems 
    .GroupBy(i => i)
    .Where(g => g.Count() > 1)
    .Select(g => g.Key);

foreach (var d in duplicates)
    Console.WriteLine(d);
2 голосов
/ 04 января 2013

Я пытался решить то же самое со списком объектов, и у меня были проблемы, потому что я пытался упаковать список групп в исходный список.Таким образом, я придумал перебирать группы, чтобы перепаковать исходный Список с элементами, которые имеют дубликаты.

public List<MediaFileInfo> GetDuplicatePictures()
{
    List<MediaFileInfo> dupes = new List<MediaFileInfo>();
    var grpDupes = from f in _fileRepo
                   group f by f.Length into grps
                   where grps.Count() >1
                   select grps;
    foreach (var item in grpDupes)
    {
        foreach (var thing in item)
        {
            dupes.Add(thing);
        }
    }
    return dupes;
}
0 голосов
/ 21 ноября 2017

Все упомянутые решения до сих пор выполняют GroupBy. Даже если мне нужен только первый дубликат, все элементы коллекций перечисляются хотя бы один раз.

Следующая функция расширения прекращает перечисление, как только дубликат найден. Это продолжается, если запрашивается следующий дубликат.

Как всегда в LINQ, есть две версии, одна с IEqualityComparer, а другая без него.

public static IEnumerable<TSource> ExtractDuplicates(this IEnumerable<TSource> source)
{
    return source.ExtractDuplicates(null);
}
public static IEnumerable<TSource> ExtractDuplicates(this IEnumerable<TSource source,
    IEqualityComparer<TSource> comparer);
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (comparer == null)
        comparer = EqualityCompare<TSource>.Default;

    HashSet<TSource> foundElements = new HashSet<TSource>(comparer);
    foreach (TSource sourceItem in source)
    {
        if (!foundElements.Contains(sourceItem))
        {   // we've not seen this sourceItem before. Add to the foundElements
            foundElements.Add(sourceItem);
        }
        else
        {   // we've seen this item before. It is a duplicate!
            yield return sourceItem;
        }
    }
}

Использование:

IEnumerable<MyClass> myObjects = ...

// check if has duplicates:
bool hasDuplicates = myObjects.ExtractDuplicates().Any();

// or find the first three duplicates:
IEnumerable<MyClass> first3Duplicates = myObjects.ExtractDuplicates().Take(3)

// or find the first 5 duplicates that have a Name = "MyName"
IEnumerable<MyClass> myNameDuplicates = myObjects.ExtractDuplicates()
    .Where(duplicate => duplicate.Name == "MyName")
    .Take(5);

Для всех этих операторов linq коллекция анализируется только до тех пор, пока запрошенные элементы не будут найдены. Остальная часть последовательности не интерпретируется.

ИМХО, это повышение эффективности, чтобы рассмотреть.

...