C#でList <T>から重複を削除する


487

誰でもC#でジェネリックリストの重複を排除する簡単な方法がありますか?


4
結果の要素の順序を気にしますか?これにより、一部のソリューションが除外されます。
大佐パニック

1行のソリューション:ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Harald Coppoolse

回答:


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 MBのRAMが必要です。私のソリューションでは2.5秒と28 MBが必要です。ハッシュセットには0.1秒かかります!!! 11MBのRAM
sasjaq 2013年

3
HashSet にはインデックスがないため、常に使用できるとは限りません。重複のない巨大なリストを一度作成し、それをListView仮想モードで使用する必要があります。HashSet<>最初に作成してからに変換するのは非常に高速でしたList<>ListViewインデックスでアイテムにアクセスできます)。List<>.Contains()遅すぎる。
Sinatr 2013

58
この特定のコンテキストでハッシュセットを使用する方法の例がある場合に役立ちます。
Nathan McKaskle、2015年

23
これはどのようにして回答と見なすことができますか?これはリンクです
mcont

2
HashSetはほとんどの状況で最適です。ただし、DateTimeのようなオブジェクトがある場合、値ではなく参照で比較されるため、結果的に重複が発生します。
Jason McKindly、2015

813

.Net 3+を使用している場合は、Linqを使用できます。

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

14
.Distinct()がIEnumerable <T>を返すため、このコードは失敗します。それに.ToList()を追加する必要があります。
ljs 2008

このアプローチは、単純な値を持つリストにのみ使用できます。
Polaris

20
いいえ、あらゆるタイプのオブジェクトを含むリストで機能します。ただし、型のデフォルトの比較子をオーバーライドする必要があります。同様に:public override bool Equals(object obj){...}
BaBu

1
この種のことが機能するように、クラスでToString()とGetHashCode()をオーバーライドすることは常に良い考えです。
Bセブン

2
.DistinctBy()拡張メソッドを持つMoreLinQ Nugetパッケージを使用することもできます。かなり便利です。
yu_ominae 2013年

178

どうですか:

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

.net 3.5では?


リストが重複していますか?
-darkgaze

1
@darkgazeこれは、一意のエントリのみを持つ別のリストを作成するだけです。したがって、重複はすべて削除され、すべての位置に異なるオブジェクトがあるリストが残ります。
hexagod

これは、アイテムコードが重複しており、一意のリストを取得する必要があるリストアイテムのリストのリストで機能しますか
venkat

90

同じタイプのリストでHashSetを初期化するだけです。

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

または、リストを返す場合:

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

3
...そしてList<T>結果として必要な場合new HashSet<T>(withDupes).ToList()
Tim Schmelter

47

並べ替えてから、2つと2つを隣同士でチェックしてください。重複すると、まとまってしまいます。

このようなもの:

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
私が間違っていなければ、上記のアプローチのほとんどは、まさにこのルーチンの抽象化ですよね?私はここであなたのアプローチを採用しました、ラッセ、それは私が精神的にデータを移動する様子を描写しているからです。しかし、今はいくつかの提案のパフォーマンスの違いに興味があります。
Ian Patrick Hughes、

7
それらを実装して時間を計る、確実にする唯一の方法。Big-O表記でさえ、実際のパフォーマンスメトリックでは役立ちません。成長効果の関係のみです。
ラッセV.カールセン

1
私はこのアプローチが好きです。他の言語への移植性が高いです。
ジェリーリャン

10
それをしないでください。とても遅いです。RemoveAtの非常にコストのかかる操作ですList
2013

1
クレメントは正しいです。これをサルベージする方法は、列挙子を使用して生成し、個別の値のみを返すメソッドでこれをラップすることです。または、値を新しい配列またはリストにコピーすることもできます。
JHubbard80 2013年

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
MSDNページで報告されているように、DistinctはSystem.Collections.GenericではなくLinqにあります。
Almo、2014年

5
この回答(2012)は、このページの他の2つの回答(2008年のもの)と同じようです。
Jon Schneider

23

.Net 3.5でkronozが言ったように、使用できます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)っぽいことです。しかし、少なくともそれが何をしているのかは明らかです。
Tamas Czinege、2009年

1
@DrJokepu-実際には、HashSetコンストラクターが重複排除されていることに気付きませんでした。ただし、これはソート順を保持しますが、これはHashSetできません。
キース

1
HashSet <T> 3.5で導入されました
ソーン

1
本当に?追跡するのが難しい。その場合は、Dictionary<T, object>代わりにa を使用し、.Containswith .ContainsKeyおよび.Add(item)with で置き換えることができます.Add(item, null)
キース

@キース、私のテストによると、HashSet順序Distinct()は保持されますが、保持されません。
デニスT-モニカの復活

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に置き換えるだけです。


5
C#では次のようになります:List <T> noDupes = new List <T>(new HashSet <T>(list)); list.Clear(); list.AddRange(noDupes);
12

C#では、この方法で簡単にできます:var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);:)
nawfal 2014年

10

これは、重複しない要素(要素を複製しない要素)を取り、それを再度リストに変換します。

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

9

LinqのUnionメソッドを使用します。

注:このソリューションは、それが存在することを除いて、Linqの知識を必要としません。

コード

まず、クラスファイルの先頭に以下を追加します。

using System.Linq;

これで、次を使用して、というオブジェクトから重複を削除できますobj1

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

注:obj1オブジェクトの名前に名前を変更します。

使い方

  1. Unionコマンドは、2つのソースオブジェクトの各エントリの1つをリストします。obj1は両方ともソースオブジェクトであるため、obj1を各エントリの1つに減らします。

  2. ToList()新しいリストを返します。LinqコマンドUnionは、元のリストを変更したり、新しいリストを返すのではなく、IEnumerableの結果として結果を返すため、これが必要です。


7

ヘルパーメソッドとして(Linqなし):

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

Distinctはすでに採用されていると思います。それとは別に(メソッドの名前を変更した場合)、動作するはずです。
Andreas Reiff、2015年

6

順序を気にしない場合は、アイテムをに押し込むだけで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)ソート方法が劣っていること(それは一目見ただけであったように)、それはとても私にははっきりしていないので、スペース(私の一時的な反対票の謝罪...)


6

これは、隣接する重複をその場で削除するための拡張メソッドです。最初にSort()を呼び出し、同じIComparerを渡します。これは、RemoveAtを繰り返し呼び出すLasse V. Karlsenのバージョンよりも効率的です(結果として、複数のブロックメモリが移動します)。

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

Nugetを介してMoreLINQパッケージをインストールすると、プロパティによってオブジェクトリストを簡単に区別できます

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

3

重複がリストに追加されていないことを確認するだけの方が簡単かもしれません。

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

1
私は現在このようにしていますが、エントリが多いほど、重複のチェックに時間がかかります。
Robert Strauch 2013年

ここでも同じ問題があります。私はList<T>.Contains毎回この方法を使用していますが、1,000,000を超えるエントリがあります。このプロセスにより、アプリケーションの速度が低下します。List<T>.Distinct().ToList<T>()代わりに最初のものを使用しています。
RPDeshaies 2014年

この方法は非常に遅い
darkgaze

3

Unionを使用できます

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

7
なぜそれが機能するのという説明は間違いなくこの答えをより良くします
Igor B

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

2

解決するには多くの方法があります-リストの重複の問題は、以下の1つです。

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の場合は、outerIndex + 1から開始せず、毎回最初から開始しないのはなぜですか?
NolmëのInformatique

2

David J.の答えは良い方法であり、追加のオブジェクトや並べ替えなどは必要ありません。ただし、次のように改善できます。

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

したがって、外側のループはリスト全体で一番下になりますが、内側のループは「外側のループ位置に到達するまで」下になります。

外側のループはリスト全体が処理されることを確認し、内側のループは実際の重複を検出します。これらは、外側のループがまだ処理していない部分でのみ発生します。

または、内側のループをボトムアップしたくない場合は、内側のループをouterIndex + 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に関する注意: リストのカウントを設定できず、Remove関数の使用を回避できないため、この操作の速度は正確にはわかりませんが、それが最も速い方法だと思います。


2

クラスが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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.