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.