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

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

Ответ 1

Возможно, вам стоит подумать об использовании 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 }
 */

Ответ 2

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

List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();

Ответ 3

Как насчет:

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

В .net 3.5?

Ответ 4

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

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

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

var noDupsList = new HashSet<T>(withDupes).ToList();

Ответ 5

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

Примерно так:

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

Примечание:

  • Сравнение выполняется задом наперед, чтобы избежать необходимости использовать список после каждого удаления
  • Этот пример теперь использует кортежи значений С# для замены, замените соответствующим кодом, если вы не можете использовать этот
  • Конечный результат больше не сортируется

Ответ 6

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

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

Заменить "Тип" на нужный тип, например. внутр.

Ответ 7

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

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

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

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

Ответ 8

Как сказал кронок в .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(), так и этот пример), чем было бы удалять из нее элементы.

Ответ 9

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

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

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

List<int> myFilteredList = unfilteredList.Deduplicate();

Ответ 10

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

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.

Ответ 11

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

List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();

Ответ 12

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

public static List<T> Distinct<T>(this List<T> list)
{
    return (new HashSet<T>(list)).ToList();
}

Ответ 13

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

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

Код

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

using System.Linq;

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

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

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

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

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

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

Ответ 14

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

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

Или путь Linq:

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

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

Ответ 15

Здесь используется метод расширения для удаления соседних дубликатов in-situ. Сначала вызовите Сортировка() и перейдите в тот же IComparer. Это должно быть более эффективным, чем версия Lasse V. Karlsen, которая многократно вызывает 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);
}

Ответ 16

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

IEnumerable<Catalogue> distinctCatalogues = catalogues.DistinctBy(c => c.CatalogueCode); 

Ответ 17

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

if(items.IndexOf(new_item) < 0) 
    items.add(new_item)

Ответ 18

Другой способ в .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();
    }

Ответ 19

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

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

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

Ответ 20

Здесь простое решение, которое не требует каких-либо трудно читаемых 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
                }
            }
        }
    }

Ответ 21

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

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

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

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

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

Ответ 22

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

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

Ответ 23

Если у вас есть класс класса " 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) затем удалить дубликаты элементов по nameof(Customer.CustomerName) CustomerName.

Ответ 24

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

Ответ 25

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

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

Ответ 26

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

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

// 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 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: Поскольку мы не можем установить счетчик списка и избежать использования функций удаления, я не знаю точно скорость этой операции, но думаю, что это самый быстрый способ.