Chuyển đổi danh sách sang từ điển bằng linq và không lo lắng về các bản sao


163

Tôi có một danh sách các đối tượng Person. Tôi muốn chuyển đổi thành Từ điển trong đó khóa là tên và họ (được nối) và giá trị là đối tượng Person.

Vấn đề là tôi có một số người trùng lặp, vì vậy điều này sẽ nổ tung nếu tôi sử dụng mã này:

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

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

Tôi biết điều này nghe có vẻ kỳ lạ nhưng hiện tại tôi không thực sự quan tâm đến các tên trùng lặp. Nếu có nhiều tên tôi chỉ muốn lấy một cái. Có cách nào tôi có thể viết mã này ở trên để nó chỉ lấy một trong các tên và không nổ tung trên các bản sao không?


1
Các bản sao (dựa trên khóa), tôi không chắc bạn muốn giữ chúng hay mất chúng? Giữ chúng sẽ cần một Dictionary<string, List<Person>>(hoặc tương đương).
Anthony Pegram

@Anthony Pegram - chỉ muốn giữ một trong số họ. tôi đã cập nhật câu hỏi để rõ ràng hơn
leora

tốt, bạn có thể sử dụng khác biệt trước khi làm ToDipedia. nhưng bạn sẽ phải ghi đè các phương thức Equals () và GetHashCode () cho lớp người để CLR biết cách so sánh các đối tượng người
Sujit.Warrier

@ Sujit.Warrier - Bạn cũng có thể tạo một trình so sánh bình đẳng để vượt quaDistinct
Kyle Delaney

Câu trả lời:


70

Đây là giải pháp rõ ràng, không linq:

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

206
đó là vì vậy 2007 :)
leora

3
không bỏ qua trường hợp nào
vào

Vâng, về thời gian chúng tôi cập nhật từ khung .net 2.0 tại nơi làm việc ... @onof Chính xác không khó để bỏ qua trường hợp. Chỉ cần thêm tất cả các phím viết hoa.
Carra

Làm thế nào tôi có thể làm cho trường hợp này không nhạy cảm
leora

11
Hoặc tạo từ điển với StringComparer sẽ bỏ qua trường hợp này, nếu đó là những gì bạn cần, thì việc thêm / kiểm tra mã của bạn không quan tâm nếu bạn bỏ qua trường hợp hay không.
Nhị phân nhị phân

423

Giải pháp 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);

Nếu bạn thích một giải pháp không phải LINQ thì bạn có thể làm một cái gì đó như thế này:

// 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 rất thanh lịch (tôi sẽ bỏ phiếu càng sớm càng tốt - không có thêm phiếu nào cho ngày hôm nay :))
vào

6
@LukeH Lưu ý nhỏ: hai đoạn mã của bạn không tương đương: biến thể LINQ giữ lại phần tử đầu tiên, đoạn mã không LINQ giữ lại phần tử cuối cùng?
toong

4
@toong: Đó là sự thật và chắc chắn đáng chú ý. (Mặc dù trong trường hợp này, OP dường như không quan tâm đến yếu tố nào họ kết thúc.)
LukeH

1
Đối với trường hợp "giá trị đầu tiên": giải pháp nonLinq thực hiện tra cứu từ điển hai lần nhưng Linq thực hiện tức thời đối tượng dự phòng và lặp lại. Cả hai đều không lý tưởng.
SerG

@SerG Rất may tra cứu từ điển thường được coi là thao tác O (1) và có tác động không đáng kể.
MHollis

42

Một giải pháp Linq sử dụng Phân biệt () và không có nhóm là:

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

Tôi không biết liệu nó có đẹp hơn giải pháp của LukeH hay không nhưng nó cũng hoạt động.


Bạn có chắc chắn rằng nó hoạt động? Làm thế nào là khác biệt để so sánh các loại tham chiếu mới mà bạn tạo ra? Tôi nghĩ bạn sẽ cần phải vượt qua một số loại IEqualityComparer vào Phân biệt để có được công việc này như dự định.
Simon Gillbee

5
Bỏ qua bình luận trước đây của tôi. Xem stackoverflow.com/questions/543482/ Mạnh
Simon Gillbee

Nếu bạn muốn ghi đè mức độ khác biệt được xác định, hãy kiểm tra stackoverflow.com/questions/489258/iêu
James McMahon

30

Điều này sẽ làm việc với biểu thức lambda:

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

2
Nó phải là:personList.Distinct().ToDictionary(i => i.FirstandLastName, i => i);
Gh61

4
Điều này sẽ chỉ hoạt động nếu IEqualityComparer mặc định cho lớp Person so sánh bằng tên và họ, bỏ qua trường hợp. Mặt khác, viết một IEqualityComparer như vậy và sử dụng quá tải riêng biệt có liên quan. Ngoài ra, phương pháp ToDIctionary của bạn nên có một bộ so sánh không phân biệt chữ hoa chữ thường để phù hợp với yêu cầu của OP.
Joe

13

Bạn cũng có thể sử dụng ToLookupchức năng LINQ, sau đó bạn có thể sử dụng gần như hoán đổi với Từ điển.

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

Điều này về cơ bản sẽ làm GroupBy trong câu trả lời của LukeH , nhưng sẽ đưa ra cách băm mà Từ điển cung cấp. Vì vậy, có lẽ bạn không cần phải chuyển đổi nó thành Từ điển mà chỉ cần sử dụng Firstchức năng LINQ bất cứ khi nào bạn cần truy cập giá trị cho khóa.


8

Bạn có thể tạo một phương thức mở rộng tương tự ToDipedia () với sự khác biệt là nó cho phép trùng lặp. Cái gì đó như:

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

Trong trường hợp này, nếu có trùng lặp, thì giá trị cuối cùng sẽ thắng.


7

Để xử lý loại bỏ trùng lặp, thực hiện một phương pháp IEqualityComparer<Person>có thể được sử dụng trong Distinct()phương thức và sau đó lấy từ điển của bạn sẽ dễ dàng. Được:

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

Lấy từ điển của bạ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);
        }

Tôi đề nghị bạn cải thiện ví dụ của mình bằng cách đọc ví dụ Tối thiểu, Hoàn chỉnh và có thể kiểm chứng .
IlGala

2

Trong trường hợp chúng tôi muốn tất cả Người (thay vì chỉ một Người) trong từ điển trả về, chúng tôi có thể:

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

1
Xin lỗi, bỏ qua phần chỉnh sửa đánh giá của tôi (Tôi không thể tìm thấy nơi để xóa phần chỉnh sửa đánh giá của mình). Tôi chỉ muốn thêm một đề xuất về việc sử dụng g.First () thay vì g.Select (x => x).
Alex 75

1

Vấn đề với hầu hết các câu trả lời khác là chúng sử dụng Distinct, GroupByhoặc ToLookup, tạo ra một Từ điển bổ sung dưới mui xe. Tương tự ToUpper tạo chuỗi bổ sung. Đây là những gì tôi đã làm, gần như là một bản sao chính xác của mã Microsoft ngoại trừ một thay đổi:

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

Bởi vì một bộ trên bộ chỉ mục làm cho nó thêm khóa, nó sẽ không ném và cũng sẽ chỉ thực hiện một tra cứu khóa. Bạn cũng có thể cho nó một IEqualityComparerví dụStringComparer.OrdinalIgnoreCase


0

Bắt đầu từ giải pháp của Carra, bạn cũng có thể viết nó dưới dạng:

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

3
Không phải ai cũng từng cố gắng sử dụng cái này, nhưng đừng cố sử dụng cái này. Sửa đổi các bộ sưu tập khi bạn đang lặp đi lặp lại chúng là một ý tưởng tồi.
trẻ con
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.