Удалить дубликаты из списка <T> в C #

487

У кого-нибудь есть быстрый способ дедупликации универсального списка в C #?

JC Grubbs
источник
4
Вы заботитесь о порядке элементов в результате? Это исключит некоторые решения.
Полковник Паника
Решение в одну строку:ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Харальд Коппулс

Ответы:

227

Возможно, вам следует рассмотреть возможность использования HashSet .

Из ссылки MSDN:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        HashSet<int> evenNumbers = new HashSet<int>();
        HashSet<int> oddNumbers = new HashSet<int>();

        for (int i = 0; i < 5; i++)
        {
            // Populate numbers with just even numbers.
            evenNumbers.Add(i * 2);

            // Populate oddNumbers with just odd numbers.
            oddNumbers.Add((i * 2) + 1);
        }

        Console.Write("evenNumbers contains {0} elements: ", evenNumbers.Count);
        DisplaySet(evenNumbers);

        Console.Write("oddNumbers contains {0} elements: ", oddNumbers.Count);
        DisplaySet(oddNumbers);

        // Create a new HashSet populated with even numbers.
        HashSet<int> numbers = new HashSet<int>(evenNumbers);
        Console.WriteLine("numbers UnionWith oddNumbers...");
        numbers.UnionWith(oddNumbers);

        Console.Write("numbers contains {0} elements: ", numbers.Count);
        DisplaySet(numbers);
    }

    private static void DisplaySet(HashSet<int> set)
    {
        Console.Write("{");
        foreach (int i in set)
        {
            Console.Write(" {0}", i);
        }
        Console.WriteLine(" }");
    }
}

/* This example produces output similar to the following:
 * evenNumbers contains 5 elements: { 0 2 4 6 8 }
 * oddNumbers contains 5 elements: { 1 3 5 7 9 }
 * numbers UnionWith oddNumbers...
 * numbers contains 10 elements: { 0 2 4 6 8 1 3 5 7 9 }
 */
Джейсон Бейкер
источник
11
это невероятно быстро ... 100 000 строк со списком занимают 400 с и 8 МБ оперативной памяти, мое собственное решение занимает 2,5 с и 28 МБ, хешсет занимает 0,1 с !!! и 11 Мбайт оперативной памяти
sasjaq
3
HashSet не имеет индекса , поэтому его не всегда можно использовать. Я должен создать один раз огромный список без дубликатов, а затем использовать его ListViewв виртуальном режиме. Это было очень быстро сделать HashSet<>сначала, а затем преобразовать его в List<>(так что ListViewможете получить доступ к элементам по индексу). List<>.Contains()слишком медленно
Синатр
58
Помогло бы, если бы был пример того, как использовать хэш-сет в этом конкретном контексте.
Натан МакКэскл
23
Как это можно считать ответом? Это ссылка
mcont
2
HashSet хорош в большинстве случаев. Но если у вас есть объект, такой как DateTime, он сравнивается по ссылке, а не по значению, поэтому вы все равно получите дубликаты.
Джейсон Маккиндли
813

Если вы используете .Net 3+, вы можете использовать Linq.

List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
Фактор Мистик
источник
14
Этот код потерпит неудачу, так как .Distinct () возвращает IEnumerable <T>. Вы должны добавить .ToList () к нему.
LJS
Этот подход может использоваться только для списка с простыми значениями.
Polaris
20
Нет, он работает со списками, содержащими объекты любого типа. Но вам придется переопределить компаратор по умолчанию для вашего типа. Примерно так: public override bool Equals (object obj) {...}
BaBu
1
Всегда полезно переопределить ToString () и GetHashCode () с вашими классами, чтобы это работало.
B Семь
2
Вы также можете использовать пакет MoreLinQ Nuget, который имеет метод расширения .DistinctBy (). Довольно полезно.
yu_ominae
178

Как насчет:

var noDupes = list.Distinct().ToList();

В .net 3.5?

СМЛ
источник
Это дублирует список?
Darkgaze
1
@darkgaze это просто создает другой список только с уникальными записями. Таким образом, любые дубликаты будут удалены, и у вас останется список, в котором каждая позиция имеет свой объект.
шестнадцатое
Работает ли это для списка списка элементов списка, где коды элементов повторяются и нужно получить уникальный список
venkat
90

Просто инициализируйте HashSet списком того же типа:

var noDupes = new HashSet<T>(withDupes);

Или, если вы хотите вернуть список:

var noDupsList = new HashSet<T>(withDupes).ToList();
Даже Миен
источник
3
... и если вам нужно использовать List<T>результатnew HashSet<T>(withDupes).ToList()
Тим
47

Сортируйте его, затем отметьте два и два рядом друг с другом, так как дубликаты будут объединяться.

Что-то вроде этого:

list.Sort();
Int32 index = list.Count - 1;
while (index > 0)
{
    if (list[index] == list[index - 1])
    {
        if (index < list.Count - 1)
            (list[index], list[list.Count - 1]) = (list[list.Count - 1], list[index]);
        list.RemoveAt(list.Count - 1);
        index--;
    }
    else
        index--;
}

Ноты:

  • Сравнение выполняется задом наперед, чтобы избежать необходимости прибегать к списку после каждого удаления.
  • Этот пример теперь использует кортежи значений C # для выполнения замены, замените соответствующим кодом, если вы не можете его использовать
  • Конечный результат больше не сортируется
Лассе В. Карлсен
источник
1
Если я не ошибаюсь, большинство из упомянутых выше подходов являются просто абстракциями этой самой подпрограммы, верно? Я бы взял твой подход здесь, Лассе, потому что именно так я мысленно представляю движение по данным. Но теперь меня интересуют различия в производительности между некоторыми предложениями.
Ян Патрик Хьюз
7
Внедряйте их и проверяйте время, единственный способ быть уверенным. Даже нотация Big-O не поможет вам с фактическими показателями производительности, только отношения эффекта роста.
Лассе В. Карлсен
1
Мне нравится такой подход, он более переносим на другие языки.
Джерри Лян
10
Не делай этого. Это супер медленно. RemoveAtявляется очень дорогостоящей операциейList
Clément
1
Клемент прав. Чтобы спасти это, можно заключить это в метод, который приводит к перечислению и возвращает только различные значения. В качестве альтернативы вы можете скопировать значения в новый массив или список.
JHubbard80
33

Мне нравится использовать эту команду:

List<Store> myStoreList = Service.GetStoreListbyProvince(provinceId)
                                                 .GroupBy(s => s.City)
                                                 .Select(grp => grp.FirstOrDefault())
                                                 .OrderBy(s => s.City)
                                                 .ToList();

У меня есть эти поля в моем списке: Id, StoreName, City, PostalCode Я хотел показать список городов в выпадающем списке, который имеет повторяющиеся значения. Решение: сгруппируйте по городам, затем выберите первый в списке.

Я надеюсь, что это помогает :)

Эрик
источник
31

Это сработало для меня. просто используйте

List<Type> liIDs = liIDs.Distinct().ToList<Type>();

Замените «Тип» на желаемый тип, например, int.

Хоссейн Саршар
источник
1
Distinct находится в Linq, а не в System.Collections.Generic, как сообщается на странице MSDN.
Almo
5
Этот ответ (2012), по-видимому, совпадает с двумя другими ответами на этой странице, полученными в 2008 году?
Джон Шнайдер
23

Как сказал кроноз в .Net 3.5, вы можете использовать Distinct() .

В .Net 2 вы можете имитировать это:

public IEnumerable<T> DedupCollection<T> (IEnumerable<T> input) 
{
    var passedValues = new HashSet<T>();

    // Relatively simple dupe check alg used as example
    foreach(T item in input)
        if(passedValues.Add(item)) // True if item is new
            yield return item;
}

Это может быть использовано для дедупликации любой коллекции и будет возвращать значения в исходном порядке.

Обычно фильтровать коллекцию намного быстрее (как Distinct()и в этом примере), чем удалять из нее элементы.

Кит
источник
Проблема с этим подходом заключается в том, что он O (N ^ 2) -ish, а не хэш-сет. Но, по крайней мере, очевидно, что он делает.
Тамас Чинеге
1
@DrJokepu - на самом деле я не понял, что HashSetконструктор дедуплицировал, что делает его лучше для большинства обстоятельств. Тем не менее, это сохранит порядок сортировки, чего HashSetнет.
Кит
1
HashSet <T> был представлен в 3.5
thorn̈
1
@ Торн действительно? Так сложно следить. В этом случае вы можете просто использовать Dictionary<T, object>вместо, заменить .Containsна .ContainsKeyи .Add(item)с.Add(item, null)
Кит
@ Кейт, согласно моему тестированию, HashSetсохраняет порядок, пока Distinct()нет.
Деннис Т - Восстановить Монику -
13

Метод расширения может быть приличным способом ... что-то вроде этого:

public static List<T> Deduplicate<T>(this List<T> listToDeduplicate)
{
    return listToDeduplicate.Distinct().ToList();
}

А потом позвоните вот так, например:

List<int> myFilteredList = unfilteredList.Deduplicate();
Джефф Тейлор
источник
11

В Java (я предполагаю, что C # более или менее идентичен):

list = new ArrayList<T>(new HashSet<T>(list))

Если вы действительно хотите изменить исходный список:

List<T> noDupes = new ArrayList<T>(new HashSet<T>(list));
list.clear();
list.addAll(noDupes);

Чтобы сохранить порядок, просто замените HashSet на LinkedHashSet.

Том Хотин - Tackline
источник
5
в C # это будет: List <T> noDupes = new List <T> (new HashSet <T> (list)); list.Clear (); list.AddRange (noDupes);
Smohamed
В C # так проще: var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);:)
nawfal
10

Это берет разные (элементы без дублирующих элементов) и снова конвертирует их в список:

List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();
Альфред Уда
источник
9

Используйте метод Linq's Union .

Примечание. Это решение не требует знания Linq, кроме того, что оно существует.

Код

Начните с добавления следующего в начало вашего файла класса:

using System.Linq;

Теперь вы можете использовать следующее для удаления дубликатов из объекта с именем obj1:

obj1 = obj1.Union(obj1).ToList();

Примечание: переименуйте obj1в название вашего объекта.

Как это работает

  1. Команда Union перечисляет одну из каждой записи двух исходных объектов. Поскольку obj1 - оба исходных объекта, это сводит obj1 к одной из каждой записи.

  2. ToList()Возвращает новый список. Это необходимо, поскольку команды Linq like Unionвозвращают результат в виде результата IEnumerable вместо изменения исходного списка или возврата нового списка.

Чудотворец
источник
7

В качестве вспомогательного метода (без Linq):

public static List<T> Distinct<T>(this List<T> list)
{
    return (new HashSet<T>(list)).ToList();
}
Грант
источник
Я думаю, что Distinct уже занят. Помимо этого (если вы переименуете метод), он должен работать.
Андреас Рифф
6

Если вы не заботитесь о порядке вы можете просто засунуть элементы в HashSet, если вы действительно хотите сохранить заказ вы можете сделать что - то вроде этого:

var unique = new List<T>();
var hs = new HashSet<T>();
foreach (T t in list)
    if (hs.Add(t))
        unique.Add(t);

Или Линк путь:

var hs = new HashSet<T>();
list.All( x =>  hs.Add(x) );

Edit:HashSet метод O(N)времени и O(N)пространства во время сортировки , а затем сделать уникальный (как это было предложено @ lassevk и другие) это O(N*lgN)время и O(1)пространство , так что это не так ясно для меня (как это было на первый взгляд) , что сортировка путь уступает (мой извиняюсь за временное отрицательное голосование ...)

Моти
источник
6

Вот метод расширения для удаления соседних дубликатов на месте. Сначала вызовите Sort () и передайте в тот же IComparer. Это должно быть более эффективно, чем версия Лассе В. Карлсена, которая неоднократно вызывает RemoveAt (что приводит к перемещению памяти из нескольких блоков).

public static void RemoveAdjacentDuplicates<T>(this List<T> List, IComparer<T> Comparer)
{
    int NumUnique = 0;
    for (int i = 0; i < List.Count; i++)
        if ((i == 0) || (Comparer.Compare(List[NumUnique - 1], List[i]) != 0))
            List[NumUnique++] = List[i];
    List.RemoveRange(NumUnique, List.Count - NumUnique);
}
Гэри
источник
5

Установив пакет MoreLINQ через Nuget, вы можете легко различать список объектов по свойству

IEnumerable<Catalogue> distinctCatalogues = catalogues.DistinctBy(c => c.CatalogueCode); 
dush88c
источник
3

Может быть проще просто убедиться, что дубликаты не добавляются в список.

if(items.IndexOf(new_item) < 0) 
    items.add(new_item)
Крис
источник
1
В настоящее время я делаю это так, но чем больше у вас записей, тем дольше длится проверка на дубликаты.
Роберт Штраух
У меня такая же проблема здесь. Я использую List<T>.Containsметод каждый раз, но с более чем 1 000 000 записей. Этот процесс замедляет мое приложение. Я использую List<T>.Distinct().ToList<T>()первый вместо этого.
RPDeshaies
Этот метод очень медленный
darkgaze
3

Вы можете использовать Союз

obj2 = obj1.Union(obj1).ToList();
flagamba
источник
7
Объяснение, почему это сработало бы, определенно улучшило бы этот ответ
Игорь Б.
2

Еще один способ в .Net 2.0

    static void Main(string[] args)
    {
        List<string> alpha = new List<string>();

        for(char a = 'a'; a <= 'd'; a++)
        {
            alpha.Add(a.ToString());
            alpha.Add(a.ToString());
        }

        Console.WriteLine("Data :");
        alpha.ForEach(delegate(string t) { Console.WriteLine(t); });

        alpha.ForEach(delegate (string v)
                          {
                              if (alpha.FindAll(delegate(string t) { return t == v; }).Count > 1)
                                  alpha.Remove(v);
                          });

        Console.WriteLine("Unique Result :");
        alpha.ForEach(delegate(string t) { Console.WriteLine(t);});
        Console.ReadKey();
    }
Bhasin
источник
2

Есть много способов решить проблему с дубликатами в списке, ниже один из них:

List<Container> containerList = LoadContainer();//Assume it has duplicates
List<Container> filteredList = new  List<Container>();
foreach (var container in containerList)
{ 
  Container duplicateContainer = containerList.Find(delegate(Container checkContainer)
  { return (checkContainer.UniqueId == container.UniqueId); });
   //Assume 'UniqueId' is the property of the Container class on which u r making a search

    if(!containerList.Contains(duplicateContainer) //Add object when not found in the new class object
      {
        filteredList.Add(container);
       }
  }

Приветствия Рави Ганесан

Рави Ганесан
источник
2

Вот простое решение, которое не требует сложного для чтения LINQ или какой-либо предварительной сортировки списка.

   private static void CheckForDuplicateItems(List<string> items)
    {
        if (items == null ||
            items.Count == 0)
            return;

        for (int outerIndex = 0; outerIndex < items.Count; outerIndex++)
        {
            for (int innerIndex = 0; innerIndex < items.Count; innerIndex++)
            {
                if (innerIndex == outerIndex) continue;
                if (items[outerIndex].Equals(items[innerIndex]))
                {
                    // Duplicate Found
                }
            }
        }
    }
Дэвид Дж.
источник
Этот метод позволяет вам лучше контролировать дублирующиеся элементы. Даже больше, если у вас есть база данных для обновления. Для innerIndex, почему бы не начинать с externalIndex + 1, а начинать каждый раз с начала?
Nolmë Informatique
2

Ответ Дэвида Дж. - хороший метод, не требующий дополнительных объектов, сортировки и т. Д. Однако его можно улучшить:

for (int innerIndex = items.Count - 1; innerIndex > outerIndex ; innerIndex--)

Таким образом, внешний цикл идет сверху вниз для всего списка, но внутренний цикл идет снизу «пока не будет достигнута позиция внешнего цикла».

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

Или, если вы не хотите делать восходящий цикл для внутреннего цикла, вы можете запустить внутренний цикл в externalIndex + 1.

гость
источник
2

Все ответы копируют списки, или создают новый список, или используют медленные функции, или просто мучительно медленные.

Насколько я понимаю, это самый быстрый и самый дешевый метод, который я знаю (при поддержке очень опытного программиста, специализирующегося на оптимизации физики в реальном времени).

// Duplicates will be noticed after a sort O(nLogn)
list.Sort();

// Store the current and last items. Current item declaration is not really needed, and probably optimized by the compiler, but in case it's not...
int lastItem = -1;
int currItem = -1;

int size = list.Count;

// Store the index pointing to the last item we want to keep in the list
int last = size - 1;

// Travel the items from last to first O(n)
for (int i = last; i >= 0; --i)
{
    currItem = list[i];

    // If this item was the same as the previous one, we don't want it
    if (currItem == lastItem)
    {
        // Overwrite last in current place. It is a swap but we don't need the last
       list[i] = list[last];

        // Reduce the last index, we don't want that one anymore
        last--;
    }

    // A new item, we store it and continue
    else
        lastItem = currItem;
}

// We now have an unsorted list with the duplicates at the end.

// Remove the last items just once
list.RemoveRange(last + 1, size - last - 1);

// Sort again O(n logn)
list.Sort();

Окончательная стоимость:

nlogn + n + nlogn = n + 2nlogn = O (nlogn), что довольно приятно.

Примечание об RemoveRange: поскольку мы не можем установить счетчик списка и избежать использования функций удаления, я не знаю точно скорость этой операции, но я думаю, что это самый быстрый способ.

darkgaze
источник
2

Если у вас есть классы буксирных Productи Customerмы хотим , чтобы удалить повторяющиеся элементы из своего списка

public class Product
{
    public int Id { get; set; }
    public string ProductName { get; set; }
}

public class Customer
{
    public int Id { get; set; }
    public string CustomerName { get; set; }

}

Вы должны определить общий класс в форме ниже

public class ItemEqualityComparer<T> : IEqualityComparer<T> where T : class
{
    private readonly PropertyInfo _propertyInfo;

    public ItemEqualityComparer(string keyItem)
    {
        _propertyInfo = typeof(T).GetProperty(keyItem, BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public);
    }

    public bool Equals(T x, T y)
    {
        var xValue = _propertyInfo?.GetValue(x, null);
        var yValue = _propertyInfo?.GetValue(y, null);
        return xValue != null && yValue != null && xValue.Equals(yValue);
    }

    public int GetHashCode(T obj)
    {
        var propertyValue = _propertyInfo.GetValue(obj, null);
        return propertyValue == null ? 0 : propertyValue.GetHashCode();
    }
}

Затем вы можете удалить дубликаты в вашем списке.

var products = new List<Product>
            {
                new Product{ProductName = "product 1" ,Id = 1,},
                new Product{ProductName = "product 2" ,Id = 2,},
                new Product{ProductName = "product 2" ,Id = 4,},
                new Product{ProductName = "product 2" ,Id = 4,},
            };
var productList = products.Distinct(new ItemEqualityComparer<Product>(nameof(Product.Id))).ToList();

var customers = new List<Customer>
            {
                new Customer{CustomerName = "Customer 1" ,Id = 5,},
                new Customer{CustomerName = "Customer 2" ,Id = 5,},
                new Customer{CustomerName = "Customer 2" ,Id = 5,},
                new Customer{CustomerName = "Customer 2" ,Id = 5,},
            };
var customerList = customers.Distinct(new ItemEqualityComparer<Customer>(nameof(Customer.Id))).ToList();

этот код удалить повторяющиеся элементы по , Idесли вы хотите удалить повторяющиеся элементы от другого имущества, вы можете изменить то nameof(YourClass.DuplicateProperty) же nameof(Customer.CustomerName)затем удалить повторяющиеся элементы по CustomerNameнедвижимости.

Реза Дженаби
источник
1
  public static void RemoveDuplicates<T>(IList<T> list )
  {
     if (list == null)
     {
        return;
     }
     int i = 1;
     while(i<list.Count)
     {
        int j = 0;
        bool remove = false;
        while (j < i && !remove)
        {
           if (list[i].Equals(list[j]))
           {
              remove = true;
           }
           j++;
        }
        if (remove)
        {
           list.RemoveAt(i);
        }
        else
        {
           i++;
        }
     }  
  }
Пол Ричардс
источник
1

Простая интуитивно понятная реализация:

public static List<PointF> RemoveDuplicates(List<PointF> listPoints)
{
    List<PointF> result = new List<PointF>();

    for (int i = 0; i < listPoints.Count; i++)
    {
        if (!result.Contains(listPoints[i]))
            result.Add(listPoints[i]);
        }

        return result;
    }
Моктар Хайз
источник
Этот метод также медленный. Создает новый список.
darkgaze