違い

2016-07-22 1 views
0

のための2つのリストを比較すると、私は次のような状況があります。違い

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

私はこれらのリスト間の差分を行い、ジョンのみリスト1にある情報を取り戻すしたいと思い、シンディはリストだけです2、maryはどちらのリストにもありますが、正確には一致しません(年齢は異なります)。私の目標は、この情報を並べて比較することです。

誰かが中に存在するもののため3つのパス、最初に存在しないもののための1、第二に存在しないもののための別を行うのではなく、第三として(これを効率的に行う方法を提案することができますどちらも違います)

重複した質問が見つからなかった場合は申し訳ありませんが、実際の詳細ではなくブール値の結果しか扱えませんでした。

+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データ構造を使用しますので、下記行うことができます。周り5000ms上記仕上げながら、以下、10000項目リストの33msで終了:

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

誰かが3つのパス、ないもののための1つを行うのとは対照的に(これを効率的に行う方法を提案することができます最初に存在するもの、2番目に存在しないもののためのもの、両方に存在するものの3番目のものは異なるが異なる)

2つのシーケンスが順序付けられているかどうかは、アイデンティティキー(あなたのケースの名前)によって。ただし、注文すると追加コストが発生し、プロセスをLINQでコーディングすることもできません。あなたが本当に必要なもの

には自然のLINQをサポートしていませんfull outer join、です。唯一の第二に存在するもののための1に存在するもののため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 } 

をあなたが好きな出力をすることができます。ご覧のとおり、一致する要素が2つあることを考慮する必要があるのは、クエリの最初の部分だけです。

+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つのアイテム(同じ名前が存在することになる(元の項目とそれからだリストをタプル)であろういずれかのリストのリストを返します年齢、およびどの年齢がどのリストからのものであるか)。

私はあなたが正確に結果を望んでいたので、私はそこにそれを残したものを構造確認されませんでした。そうでなければ、別の選択を行って両方のリスト( "ボブ")などの同じレコードを除外することができます。

この解決策は、両方のリストを1回だけ反復する必要があります。