Linq kullanarak ve kopyalar için endişelenmeden listeyi sözlüğe dönüştür


163

Person nesnelerinin bir listesi var. Anahtar adı ve soyadı (birleştirilmiş) ve değeri Person nesnesidir bir sözlüğe dönüştürmek istiyorum.

Sorun şu ki bazı yinelenen insanlar var, bu yüzden bu kodu kullanırsanız bu patlar:

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

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

Garip geldiğini biliyorum ama şimdilik yinelenen isimleri umursamıyorum. Birden fazla isim varsa, sadece bir tane almak istiyorum. Zaten bu kodları yazabilir miyim, bu yüzden sadece isimlerden birini alır ve kopyalara patlamaz mı?


1
Kopyaları (anahtara göre), saklamak mı yoksa kaybetmek mi istediğinizden emin değil misiniz? Onları tutmak bir Dictionary<string, List<Person>>(veya eşdeğerini) gerektirir.
Anthony Pegram

Anthony Pegram - sadece birini tutmak istiyorum. daha açık olması için soruyu güncelledim
leora

ToDictionary yapmadan önce farklı kullanabilirsiniz. ancak
CLR'nin

@ Sujit.Warrier - Geçmek için bir eşitlik karşılaştırıcısı da oluşturabilirsinizDistinct
Kyle Delaney

Yanıtlar:


71

İşte bariz, linq olmayan çözüm:

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

208
thats so 2007 :)
leora

3
davayı görmezden gelmez
onof

Evet, işte .net 2.0 çerçevesinden güncelleme zamanı geldi ... @onof Davayı görmezden gelmek tam olarak zor değil. Tüm tuşları büyük harfe eklemeniz yeterlidir.
Carra

nasıl bu davayı duyarsız yapar
leora

11
Ya da söz konusu durumu, görmezden geleceğiniz bir StringComparer ile oluşturun, ihtiyacınız olan şeyse, ekleme / denetleme kodunuz büyük / küçük harf göz ardı edip etmediğinizi umursamaz.
İkili Worrier

423

LINQ çözümü:

// 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 olmayan bir çözümü tercih ederseniz, böyle bir şey yapabilirsiniz:

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

6
@ LukeH Küçük not: iki snippet'iniz eşdeğer değildir: LINQ varyantı ilk öğeyi, LINQ olmayan snippet son öğeyi korur mu?
toong

4
@toong: Bu doğru ve kesinlikle kayda değer. (Bu durumda OP hangi elemanla sonuçlandıkları umurumda olmasa da)
LukeH

1
"İlk değer" durumu için: nonLinq çözümü sözlük aramasını iki kez yapar, ancak Linq gereksiz nesne örnekleme ve yineleme yapar. Her ikisi de ideal değil.
SerG

@SerG Neyse ki sözlük araması genellikle O (1) işlemi olarak kabul edilir ve ihmal edilebilir bir etkisi vardır.
MHollis

43

Distinct () kullanan ve gruplama bulunmayan bir Linq çözümü:

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

LukeH'nin çözümünden daha iyi olup olmadığını bilmiyorum ama işe yarıyor.


Bunun işe yaradığından emin misin? Distinct, oluşturduğunuz yeni referans türünü nasıl karşılaştıracak? Bu işi amaçlandığı gibi elde etmek için bir çeşit IEqualityComparer'ı Distinct'e geçirmeniz gerektiğini düşünürdüm.
Simon Gillbee

5
Önceki yorumumu yoksay. Bkz. Stackoverflow.com/questions/543482/…
Simon Gillbee

Farklılığın nasıl belirlendiğini geçersiz kılmak istiyorsanız stackoverflow.com/questions/489258/…
James McMahon

30

Bu lambda ifadesi ile çalışmalıdır:

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

2
Bu olmalı:personList.Distinct().ToDictionary(i => i.FirstandLastName, i => i);
Gh61

4
Bu, yalnızca Person sınıfı için varsayılan IEqualityComparer'ın ad ve soyadıyla karşılaştırılması durumunda büyük / küçük harf dikkate alınmaz. Aksi takdirde böyle bir IEqualityComparer yazın ve ilgili Farklı aşırı yükü kullanın. Ayrıca ToDIctionary yönteminiz, OP'nin gereksinimlerini karşılamak için büyük / küçük harfe duyarlı olmayan bir karşılaştırıcı almalıdır.
Joe

13

Ayrıca ToLookupdaha sonra bir Sözlük ile neredeyse birbirinin yerine kullanabileceğiniz LINQ işlevini de kullanabilirsiniz .

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

Bu aslında GroupBy'yi LukeH'nin cevabında yapacak, ancak bir Sözlüğün sağladığı hash'i verecektir. Bu nedenle, muhtemelen bir Sözlüğe dönüştürmeniz gerekmez, ancak Firstanahtarın değerine erişmeniz gerektiğinde LINQ işlevini kullanın .


8

ToDictionary () 'a benzer bir uzantı yöntemi oluşturarak farkın yinelenmesine izin vermesidir. Gibi bir şey:

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

Bu durumda, kopyalar varsa, son değer kazanır.


7

Yinelenenleri ortadan kaldırmak için IEqualityComparer<Person>, Distinct()yöntemde kullanılabilecek bir tane uygulayın ve ardından sözlüğünüzü almak kolay olacaktır. Verilen:

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

Sözlüğünüzü alın:

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

Minimal, Complete ve doğrulanabilir örneği okuyarak örneğinizi geliştirmenizi öneririm .
IlGala

2

Dönen sözlükte tüm Kişiyi (yalnızca bir Kişi yerine) istiyorsak, şunları yapabiliriz:

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

1
Maalesef, inceleme düzenlememi yoksay (İnceleme düzenlememi nerede sileceğimi bulamıyorum). Sadece g.Select (x => x) yerine g.First () kullanımı hakkında bir öneri eklemek istedim.
Alex 75

1

Diğer cevapların çoğu sorunu kullandıkları olmasıdır Distinct, GroupByya ToLookupbaşlık altında fazladan Sözlük yaratan,. Eşit şekilde ToUpper ekstra dize oluşturur. Yaptığım şey bu, bir değişiklik dışında Microsoft'un kodunun neredeyse tam bir kopyası:

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

Dizinleyicideki bir küme anahtar eklemesine neden olduğundan, atmaz ve yalnızca bir anahtar araması yapar. Ayrıca IEqualityComparer, örneğin birStringComparer.OrdinalIgnoreCase


0

Carra'nın çözümünden başlayarak şunları da yazabilirsiniz:

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

3
Hiç kimse bunu kullanmaya çalışamaz, ama kullanmaya kalkışmaz. Koleksiyonları yinelediğiniz gibi değiştirmek kötü bir fikirdir.
kidmosey
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.