차이

2016-07-22 2 views
0

두 개의 목록을 비교 나는 다음과 같은 상황이 있습니다차이

class A 
{ 
    public A(string name, int age) { Name = name; Age = age; } 
    public string Name; 
    public int Age; 
} 

List<A> one = 
    new List<A>() { new A("bob", 15), new A("john", 10), new A("mary", 12) }; 
List<A> two = 
    new List<A>() { new A("bob", 15), new A("mary", 15), new A("cindy", 18) }; 

나는이 목록 사이 DIFF을하고, 존은 목록 1에있는 정보를 다시 좀하고 싶습니다, 신디는 목록에 2, mary는 두 목록에 있지만 정확히 일치하지는 않습니다 (나이가 다릅니다). 내 목표는이 정보를 나란히 비교하여 제시하는 것입니다.

누군가가 이것을 효율적으로 수행하는 방법을 제안 할 수 있습니까? (3 단계 통과, 처음에는 존재하지 않는 항목, 2 단계에는 존재하지 않는 항목, 존재하는 항목에 대해서는 3 등) 둘 다 다르지만)

중복 된 질문을 놓친 경우 죄송합니다. 실제 세부 정보가 아닌 부울 결과 만 처리 할 수있었습니다.

+0

당신에게 순서가 중요합니까? 어떤 이름이 두 번 이상 나타날 수 있습니까? –

+0

주문은 중요하지 않습니다. 동일한 목록 내에서 이름이 두 번 이상 나오지는 않습니다. 나는 실제로 이미 이름순으로 정렬 된 목록을 가지고있다. –

답변

1

패스는 암시 적이거나 "명시 적"입니다. 그리고 명시 적으로 일부 Linq 확장 메서드를 통해 숨겨진 뜻. 그래서 당신은 아래 수행 할 수 있습니다

var results = (from item in one.Concat(two).Select(x => x.Name).Distinct() 
       let inFirst = one.Find(x => x.Name == item) 
       let inSecond = two.Find(x => x.Name == item) 
       let location = inFirst != null 
          && inSecond != null 
           ? 2 : inSecond != null ? 1 : 0 
       select new 
       { 
        Name = item, 
        location = location == 0 ? "First" : location == 1 ? "Second" : "Both", 
        ExactMatch = (location != 2 || inFirst.Age == inSecond.Age) 
            ? "YES" : $"One: { inFirst.Age } | Two: { inSecond.Age }" 
       }).ToList(); 

결과 :

{ Name = bob, location = Both, ExactMatch = YES } 
{ Name = john, location = First, ExactMatch = YES } 
{ Name = mary, location = Both, ExactMatch = One: 12 | Two: 15 } 
{ Name = cindy, location = Second, ExactMatch = YES } 

를 사용하면, 성능에 대한 걱정 조회 O (1)에 대한 effiecient 데이터 구조를 사용하는 경우. 10000 항목 목록에 대한 33ms의 아래에 완료, 동안 주위 5000ms 위 마감 :

var oneLookup = one.ToLookup(x => x.Name, x => x); 
var twoLookup = two.ToLookup(x => x.Name, x => x); 

var results = (from item in one.Concat(two).Select(x => x.Name).Distinct() 
       let inFirst = oneLookup[item].FirstOrDefault() 
       let inSecond = twoLookup[item].FirstOrDefault() 
       let location = inFirst != null 
          && inSecond != null 
           ? 2 : inSecond != null ? 1 : 0 
       select new 
       { 
        Name = item, 
        location = location == 0 ? "First" : location == 1 ? "Second" : "Both", 
        ExactMatch = (location != 2 || inFirst.Age == inSecond.Age) 
            ? "YES" : $"One: { inFirst.Age } | Two: { inSecond.Age }" 
       }).ToList(); 
+0

감사합니다. 이것은 유망 해 보입니다! 나는 그것을 소용돌이 치고 답을 받아들이 기 위해 돌아올 것이다. –

+0

@WillIAm O (N^2) 알고리즘은 최적의 성능과는 거리가 니 다. 이게 무엇을 찾고 있다면, 적어도 질문에서 ** ** 효율적인 ** 단어를 제거하십시오. –

+0

@ 아이반, 아직 모든 대답을 평가할 시간이 없었습니다. 그러나 내가 현재 필요로하는 것은 목록에서 약 200 개의 항목 만 다루고 있기 때문에이 단계에서 느려진다. 약 1 주일 후, 필자는 필요한 경우 성능을 검토하고 선택 항목을 업데이트 할 것입니다. –

1

사람이하지 않는 물건 세 가지 패스, 하나를 수행의 반대 (효율적으로이 작업을 수행하는 방법을 제안 할 수 있습니다 두 번째에 존재하지 않는 재료에 대해서는 다른 하나, 다른 둘에 존재하지만 다른 재료에 대해서는 세 번째)

두 패스가 차이가 나는 유일한 방법은 두 개의 시퀀스가 ​​정렬 된 경우입니다 ID 키 (사례의 이름)로 그러나 순서에 따라 추가 비용이 발생하며 프로세스를 LINQ로 코딩 할 수 없습니다.

자연스러운 LINQ 지원이없는 full outer join입니다. 따라서 고전적 접근법은 두 가지 패스가 필요합니다. left other join은 하나에, 둘째에는 존재하고, right antijoin은 두 번째에만 존재합니다. 이것은 지금까지 가장 효율적인 LINQ 방식입니다.

쿼리는 다음과 같이 할 수있다 : 당신의 샘플 데이터에서 다음과 같은 생산

var result = 
    (from e1 in one 
    join e2 in two on e1.Name equals e2.Name into match 
    from e2 in match.DefaultIfEmpty() 
    select new 
    { 
     e1.Name, 
     In = e2 == null ? "A" : "A,B", 
     Age = e2 == null || e1.Age == e2.Age ? e1.Age.ToString() : $"A:{e1.Age} B:{e2.Age}" 
    }) 
    .Concat 
    (from e2 in two 
    join e1 in one on e2.Name equals e1.Name into match 
    where !match.Any() 
    select new { e2.Name, In = "B", Age = e2.Age.ToString() }) 
    .ToList(); 

: 물론

{ Name = bob, In = A,B, Age = 15 } 
{ Name = john, In = A, Age = 10 } 
{ Name = mary, In = A,B, Age = A:12 B:15 } 
{ Name = cindy, In = B, Age = 18 } 

당신은 출력이 당신이 원하는 수 있습니다 뭐든간에. 보시다시피 두 요소가 일치해야하는 유일한 곳은 검색어의 첫 번째 부분입니다.

+0

감사합니다. Ivan. 내 목록은 이미 주문되었습니다. 리팩터링하는 동안 코드를 연결합니다. –

2
var result = 
    one.Select(a => Tuple.Create(a, "one")) // tag list one items 
    .Concat(two.Select(a => Tuple.Create(a, "two"))) // tag list two items 
    .GroupBy(t => t.Item1.Name) // group items by Name 
    .ToList(); // cache result 

var list_one_only = result.Where(g => g.Count() == 1 && g.First().Item2 == "one"); 
var list_two_only = result.Where(g => g.Count() == 1 && g.First().Item2 == "two"); 
var both_list_diff = result.Where(g => g.Count() == 2 && g.First().Item1.Age != g.Skip(1).First().Item1.Age); 

이 각각 내측 목록 항목 1 또는 동일한 아마도 2 개 항목 (같은 이름이있을 것 (본래 아이템 어느 것이에서의 목록 튜플)의 어느 쪽리스트의 목록을 반환 나이, 그리고 나이가 어느 목록에 있는지).

결과가 정확히 원하는 구조가 무엇인지 확실하지 않아서 거기에 놓았습니다.그렇지 않으면 두 목록 ("봅")에서 같은 레코드를 필터링하기 위해 다른 선택을 할 수 있습니다.

이 솔루션은 두 목록을 한 번만 반복해야합니다.