linqを使用してリストを辞書に変換し、重複を心配しない


163

Personオブジェクトのリストがあります。キーを姓と名(連結したもの)にして、値をPersonオブジェクトにした辞書に変換したいと思います。

問題は、重複した人が何人かいることです。このコードを使用すると、これは爆発します。

private Dictionary<string, Person> _people = new Dictionary<string, Person>();

_people = personList.ToDictionary(
    e => e.FirstandLastName,
    StringComparer.OrdinalIgnoreCase);

奇妙に聞こえるかもしれませんが、今のところ、重複する名前については特に気にしません。複数の名前がある場合は、1つだけ取得します。とにかく私は上記のコードを書くことができるので、それは名前の1つだけを取り、重複で爆破しないようにしますか?


1
重複(キーに基づく)、それらを保持するか、失うかはわかりませんか?それらを維持するには、Dictionary<string, List<Person>>(または同等の)が必要になります。
Anthony Pegram 2010

@Anthony Pegram-それらの1つを保持したいだけ。私は質問をより明確にするために更新しました
leora 10/07/23

ToDictionaryを実行する前に、distinctを使用できます。しかし、あなたはそのCLRが人のオブジェクトを比較する方法を知っているので、人のクラスのためのEquals()とGetHashCodeメソッド()メソッドをオーバーライドしなければならない
Sujit.Warrier

@ Sujit.Warrier-渡される等価比較子を作成することもできますDistinct
カイルデラニー

回答:


71

これが明らかな非linqソリューションです。

foreach(var person in personList)
{
  if(!myDictionary.Keys.Contains(person.FirstAndLastName))
    myDictionary.Add(person.FirstAndLastName, person);
}

207
それは2007年です:)
leora

3
これは大文字小文字を無視しません
10

ええ、私たちが仕事で.net 2.0フレームワークから更新する頃には... @onof大文字小文字を区別するのは難しいことではありません。すべてのキーを大文字で追加するだけです。
Carra

この大文字と小文字を区別しないようにする方法
leora

11
または、大文字と小文字を無視するStringComparerを使用してディクショナリを作成します。必要な場合は、大文字と小文字を無視するかどうかにかかわらず、追加/チェックコードは気にしません。
Binary Worrier 2010

423

LINQソリューション:

// Use the first value in group
var _people = personList
    .GroupBy(p => p.FirstandLastName, StringComparer.OrdinalIgnoreCase)
    .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);

// Use the last value in group
var _people = personList
    .GroupBy(p => p.FirstandLastName, StringComparer.OrdinalIgnoreCase)
    .ToDictionary(g => g.Key, g => g.Last(), StringComparer.OrdinalIgnoreCase);

非LINQソリューションを希望する場合は、次のようなことを行うことができます。

// Use the first value in list
var _people = new Dictionary<string, Person>(StringComparer.OrdinalIgnoreCase);
foreach (var p in personList)
{
    if (!_people.ContainsKey(p.FirstandLastName))
        _people[p.FirstandLastName] = p;
}

// Use the last value in list
var _people = new Dictionary<string, Person>(StringComparer.OrdinalIgnoreCase);
foreach (var p in personList)
{
    _people[p.FirstandLastName] = p;
}

2
+1非常にエレガント(
できるだけ早く

6
@LukeHマイナーなメモ:2つのスニペットは同等ではありません。LINQバリアントは最初の要素を保持し、非LINQスニペットは最後の要素を保持しますか?
toong

4
@toong:それは事実であり、間違いなく注目に値します。(この場合、OPは最終的にどの要素になるかを気にしていません。)
LukeH 2013

1
「最初の値」の場合:nonLinqソリューションは辞書検索を2回行いますが、Linqは冗長なオブジェクトのインスタンス化と反復を行います。どちらも理想的ではありません。
SerG

@SerGありがたいことに、辞書検索は一般にO(1)操作と見なされ、影響は無視できます。
MHollis

42

Distinct()を使用し、グループ化しないLinqソリューションは次のとおりです。

var _people = personList
    .Select(item => new { Key = item.Key, FirstAndLastName = item.FirstAndLastName })
    .Distinct()
    .ToDictionary(item => item.Key, item => item.FirstFirstAndLastName, StringComparer.OrdinalIgnoreCase);

LukeHのソリューションより優れているかどうかはわかりませんが、問題なく機能します。


それでいいですか?Distinctは、作成した新しい参照タイプをどのように比較しますか?意図したとおりに動作させるには、ある種のIEqualityComparerをDistinctに渡す必要があると思います。
Simon Gillbee、2014

5
以前のコメントを無視します。stackoverflow.com/questions/543482/…を
Simon Gillbee 14

上書きする場合はどのように明確なアウトと判定チェックですstackoverflow.com/questions/489258/...は
ジェームズ・マクマホン

30

これはラムダ式で動作するはずです:

personList.Distinct().ToDictionary(i => i.FirstandLastName, i => i);

2
それでなければなりません:personList.Distinct().ToDictionary(i => i.FirstandLastName, i => i);
Gh61

4
これは、PersonクラスのデフォルトのIEqualityComparerが大文字と小文字を無視して姓と名で比較する場合にのみ機能します。それ以外の場合は、そのようなIEqualityComparerを記述し、関連するDistinctオーバーロードを使用します。また、ToDIctionaryメソッドは、OPの要件に一致するように、大文字と小文字を区別しない比較演算子を使用する必要があります。
Joe

13

また、ToLookupLINQ関数を使用することもできます。この関数は、辞書とほぼ同じ意味で使用できます。

_people = personList
    .ToLookup(e => e.FirstandLastName, StringComparer.OrdinalIgnoreCase);
_people.ToDictionary(kl => kl.Key, kl => kl.First()); // Potentially unnecessary

これは基本的にLukeHの回答でGroupByを実行しますが、Dictionaryが提供するハッシュを提供します。したがって、おそらくそれをディクショナリに変換する必要はありませんがFirst、キーの値にアクセスする必要があるときは常にLINQ 関数を使用してください。


8

ToDictionary()と同様の拡張メソッドを作成できますが、重複を許可する点が異なります。何かのようなもの:

    public static Dictionary<TKey, TElement> SafeToDictionary<TSource, TKey, TElement>(
        this IEnumerable<TSource> source, 
        Func<TSource, TKey> keySelector, 
        Func<TSource, TElement> elementSelector, 
        IEqualityComparer<TKey> comparer = null)
    {
        var dictionary = new Dictionary<TKey, TElement>(comparer);

        if (source == null)
        {
            return dictionary;
        }

        foreach (TSource element in source)
        {
            dictionary[keySelector(element)] = elementSelector(element);
        }

        return dictionary; 
    }

この場合、重複がある場合、最後の値が優先されます。


7

重複の除去を処理するにIEqualityComparer<Person>は、Distinct()メソッドで使用できるを実装すると、辞書を簡単に取得できます。与えられた:

class PersonComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        return x.FirstAndLastName.Equals(y.FirstAndLastName, StringComparison.OrdinalIgnoreCase);
    }

    public int GetHashCode(Person obj)
    {
        return obj.FirstAndLastName.ToUpper().GetHashCode();
    }
}

class Person
{
    public string FirstAndLastName { get; set; }
}

辞書を入手:

List<Person> people = new List<Person>()
{
    new Person() { FirstAndLastName = "Bob Sanders" },
    new Person() { FirstAndLastName = "Bob Sanders" },
    new Person() { FirstAndLastName = "Jane Thomas" }
};

Dictionary<string, Person> dictionary =
    people.Distinct(new PersonComparer()).ToDictionary(p => p.FirstAndLastName, p => p);

2
        DataTable DT = new DataTable();
        DT.Columns.Add("first", typeof(string));
        DT.Columns.Add("second", typeof(string));

        DT.Rows.Add("ss", "test1");
        DT.Rows.Add("sss", "test2");
        DT.Rows.Add("sys", "test3");
        DT.Rows.Add("ss", "test4");
        DT.Rows.Add("ss", "test5");
        DT.Rows.Add("sts", "test6");

        var dr = DT.AsEnumerable().GroupBy(S => S.Field<string>("first")).Select(S => S.First()).
            Select(S => new KeyValuePair<string, string>(S.Field<string>("first"), S.Field<string>("second"))).
           ToDictionary(S => S.Key, T => T.Value);

        foreach (var item in dr)
        {
            Console.WriteLine(item.Key + "-" + item.Value);
        }

最小限の完全で検証可能な例を読んで、例を改善することをお勧めします。
IlGala

2

返されるディクショナリに(1人だけではなく)すべてのPersonが必要な場合は、次のようにします。

var _people = personList
.GroupBy(p => p.FirstandLastName)
.ToDictionary(g => g.Key, g => g.Select(x=>x));

1
申し訳ありませんが、私のレビュー編集を無視してください(レビュー編集を削除する場所が見つかりません)。g.Select(x => x)の代わりにg.First()を使用することについての提案を追加したかっただけです。
Alex 75

1

他の回答のほとんどの問題は、彼らが使用していることであるDistinctGroupByまたはToLookupフードの下の余分な辞書を作成します。同様に、ToUpperは追加の文字列を作成します。これは私がやったことであり、1つの変更を除いて、Microsoftのコードのほぼ正確なコピーです。

    public static Dictionary<TKey, TSource> ToDictionaryIgnoreDup<TSource, TKey>
        (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer = null) =>
        source.ToDictionaryIgnoreDup(keySelector, i => i, comparer);

    public static Dictionary<TKey, TElement> ToDictionaryIgnoreDup<TSource, TKey, TElement>
        (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer = null)
    {
        if (keySelector == null)
            throw new ArgumentNullException(nameof(keySelector));
        if (elementSelector == null)
            throw new ArgumentNullException(nameof(elementSelector));
        var d = new Dictionary<TKey, TElement>(comparer ?? EqualityComparer<TKey>.Default);
        foreach (var element in source)
            d[keySelector(element)] = elementSelector(element);
        return d;
    }

インデクサーのセットによってキーが追加されるため、スローされず、キーの検索も1回だけ行われます。IEqualityComparerたとえば、それを与えることもできますStringComparer.OrdinalIgnoreCase


0

Carraのソリューションから始めて、次のように書くこともできます。

foreach(var person in personList.Where(el => !myDictionary.ContainsKey(el.FirstAndLastName)))
{
    myDictionary.Add(person.FirstAndLastName, person);
}

3
誰もがこれを使用しようとするわけではありませんが、これを使用しようとしないでください。反復しながらコレクションを変更することは悪い考えです。
kidmosey
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.