linq를 사용하고 중복에 대해 걱정하지 않고 목록을 사전으로 변환


163

Person 객체 목록이 있습니다. 키가 성과 이름 (연결)이고 값이 Person 객체 인 Dictionary로 변환하고 싶습니다.

문제는 중복 된 사람들이 있다는 것이므로이 코드를 사용하면 문제가 발생합니다.

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

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

나는 그것이 이상하게 들린다는 것을 알고 있지만 실제로는 중복 이름에 관심이 없습니다. 이름이 여러 개인 경우 하나만 잡고 싶습니다. 어쨌든이 코드를 작성할 수 있습니까? 그래서 이름 중 하나만 사용하고 복제본을 날려 버리지 않습니다.


1
복제본 (키를 기준으로 함)을 유지하거나 잃을 지 확실하지 않습니까? 그것들을 유지하려면 Dictionary<string, List<Person>>(또는 동등한) 필요합니다 .
— Anthony Pegram

@Anthony Pegram-그중 하나를 유지하고 싶습니다. 질문을보다 명확하게 업데이트했습니다.
— leora

잘 당신은 ToDictionary를 수행하기 전에 구별을 사용할 수 있습니다. 하지만 당신은 그 CLR이 사람 개체를 비교하는 방법을 알 수 있도록 사람 클래스 같음 () 및 GetHashCode () 메소드를 오버라이드 (override) 할 것
— Sujit.Warrier

@ Sujit.Warrier - 당신은 또한에 전달하는 같음 비교를 만들 수 있습니다Distinct
— Kyle Delaney

답변:


71

linq가 아닌 명백한 솔루션은 다음과 같습니다.

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

208
그게 2007 : 이렇게
— 레오 라

3
사건을 무시하지 않습니다
— 14:32의

예, 우리는 직장에서 .net 2.0 프레임 워크에서 업데이트 할 시간에 대해 ... @onof 정확하게 대소 문자를 무시하는 것은 아닙니다. 모든 키를 대문자로 추가하십시오.
— Carra

이 대소 문자를 구분하지 않는 방법
— leora

11
또는 대소 문자를 무시하는 StringComparer를 사용하여 사전을 작성하십시오. 필요한 경우 대소 문자를 무시하고 있는지 여부에 상관없이 추가 / 검사 코드는 신경 쓰지 않습니다.
— 이진 걱정

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

6
@LukeH 사소한 참고 사항 : 두 스 니펫은 동일하지 않습니다. LINQ 변형은 첫 번째 요소를 유지하고 비 LINQ 스 니펫은 마지막 요소를 유지합니까?
— toong

4
@ toong : 그건 사실이며 주목할 가치가 있습니다. (이 경우 OP는 어떤 요소로 끝나는 지 신경 쓰지 않는 것 같습니다.)
— LukeH

1
"첫 번째 값"의 경우 : nonLinq 솔루션은 사전 검색을 두 번 수행하지만 Linq는 중복 객체 인스턴스화 및 반복을 수행합니다. 둘 다 이상적이지 않습니다.
— SerG

@SerG 고맙게도 사전 조회는 일반적으로 O (1) 연산으로 간주되며 무시할만한 영향을 미칩니다.
— MHollis

43

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로 전달해야한다고 생각합니다.
— 사이먼 길비

5
내 이전 의견을 무시하십시오. 참조 stackoverflow.com/questions/543482/...
— 사이먼 Gillbee

구별이 결정되는 방식을 무시하려면 stackoverflow.com/questions/489258/…를
— James McMahon

30

이것은 람다 식과 함께 작동해야합니다.

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

2
그것은해야한다 :personList.Distinct().ToDictionary(i => i.FirstandLastName, i => i);
— Gh61

4
Person 클래스의 기본 IEqualityComparer가 대 / 소문자를 무시하고 이름과 성을 기준으로 비교하는 경우에만 작동합니다. 그렇지 않으면 해당 IEqualityComparer를 작성하고 관련 고유 과부하를 사용하십시오. 또한 ToDIctionary 방법은 대 / 소문자를 구분하지 않는 비교기를 사용하여 OP의 요구 사항을 충족해야합니다.
— Joe

13

당신은 또한 사용할 수 있습니다 ToLookup당신은 다음 사전과 거의 같은 의미로 사용할 수 있습니다 LINQ의 기능을.

_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

돌아 오는 사전에 모든 Person (하나의 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

다른 답변의 대부분의 문제는 그들이 사용하는 것입니다 Distinct, GroupBy또는 ToLookup후드 아래에 별도의 사전을 생성한다. 마찬가지로 ToUpper는 추가 문자열을 만듭니다. 이것은 내가 한 일이며 한 번의 변경을 제외하고는 거의 동일한 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;
    }

인덱서의 집합으로 인해 키가 추가되므로 키가 발생하지 않으며 하나의 키 조회 만 수행됩니다. 당신은 또한 그에게를 제공 할 수 있습니다 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.