質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.36%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

LINQ

LINQとはLanguage INtegrated Queryの略で、「統合言語クエリ」という意味です。C#やVisual Basicといった言語のコード内に記述することができるクエリです。

Q&A

解決済

7回答

26361閲覧

2つのコレクションを比較し、特定のプロパティが異なる要素を取り出したい

ytabuchi

総合スコア335

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

LINQ

LINQとはLanguage INtegrated Queryの略で、「統合言語クエリ」という意味です。C#やVisual Basicといった言語のコード内に記述することができるクエリです。

1グッド

3クリップ

投稿2016/12/21 07:41

編集2016/12/21 07:46

###前提・実現したいこと
ローカルに保持したデータとWebから引っ張ってきたデータの値(ここではName)を比較したいと考えています。Idが各Listで異なるため(ここは現時点では仕様なので仕方ないものとして考えてください。)、ListのNameのみを比較して、差集合を得たいです。

以下のコードの例では、localPerson(1,"test1")webPerson(2,"test1")を同じ要素として扱いたい。という意図です。

Nameのみをstring[]に抜き出して差集合を出して再度別のListを作るという非常に汚らしいコードはできたのですが、よりエレガントな方法があれば知りたいです。

よろしくお願いいたします。

欲しい結果

List<Person>
の以下の要素です。
4,"test4"
5,"test5"

###該当のソースコード

csparp

1class Class1 2{ 3 static void Main(string[] args) 4 { 5 var local = new List<Person> 6 { 7 new Person(1,"test1"), 8 new Person(2,"test2"), 9 new Person(3,"test3"), 10 new Person(4,"test4"), 11 new Person(5,"test5"), 12 }; 13 14 var web = new List<Person> 15 { 16 new Person(2,"test1"), 17 new Person(3,"test2"), 18 new Person(4,"test3"), 19 }; 20 21 // エレガントな方法はないのでしょうか? 22 string[] arrLocal = local.Select(x => x.Name).ToArray(); 23 string[] arrWeb = web.Select(x => x.Name).ToArray(); 24 var diff = arrLocal.Except(arrWeb).ToArray(); 25 26 var diffList = new List<Person>(); 27 foreach (var item in diff) 28 { 29 diffList.Add(local.FirstOrDefault(x => x.Name == item)); 30 } 31 } 32} 33public class Person 34{ 35 public int Id { get; private set; } 36 public string Name { get; private set; } 37 38 public Person(int id, string name) 39 { 40 this.Id = id; 41 this.Name = name; 42 } 43}

###試したこと
foreachを入れ子にしたり、Hashsetを使ったりを考えたのですが、条件設定がうまくいかず、欲しい結果が得られませんでした。

###補足情報(言語/FW/ツール等のバージョンなど)
C# 6.0 です。

takagi69👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答7

0

var hash = new HashSet<string>(web.Select(n => n.Name));
var diffList = local.Where(n => hash.Contains(n.Name) == false).ToArray();
これでいいのでは?

HashSetは探すことでは、早いものとして知られています。

投稿2016/12/21 08:03

kiichi54321

総合スコア1984

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

webとlocalの双方から余分な要素を取得する事まで考慮するならば、和集合と積集合の差集合である排他的論理和をLinqで以下のように求められます。
※webにtest6を追加して実行するとtest4, test5, test6を返します

c#

1var union = local.Union(web, person => person.Name); //和集合 2var intersect = local.Intersect(web, person => person.Name); //積集合 3var diffList = union.Except(intersect, person => person.Name); //和集合と積集合の差集合

ここで第2引数は既出の通りIEqualityComparer<T>なのですが、自前で作るのは面倒なのでneueccさん作成のAnonymousComparerの利用がお薦めです。
AnonymousComparerの拡張メソッドによりラムダ式で簡単に記述できます。

投稿2016/12/21 14:16

編集2016/12/21 15:21
yamamo

総合スコア37

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

こんにちは。

ExceptメソッドをIEqualityComparer<T>付きで使えば良いと思います。
自分の場合は以下のように書きます。


こんな感じのユーティリティクラスを予め用意しておいて、

csharp

1 2public class LambdaEqualityComparer<T> : EqualityComparer<T> 3{ 4 private readonly Func<T, T, bool> _equals; 5 6 private readonly Func<T, int> _getHash; 7 8 public LambdaEqualityComparer(Func<T, T, bool> equals) : this(equals, x => x.GetHashCode()) 9 { } 10 11 public LambdaEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHash) 12 { 13 this._equals = equals; 14 this._getHash = getHash; 15 } 16 17 public override bool Equals(T x, T y) 18 => this._equals(x, y); 19 20 public override int GetHashCode(T obj) 21 => this._getHash(obj); 22}

こんな感じで、

csharp

1var diffList = local.Except(web, new LambdaEqualityComparer<Person>((x, y) => x.Name == y.Name, x => x.Name.GetHashCode()));

どうですかね?


もう一つの方法として、既存のクラスに変更を加えることが可能なら、
PersonクラスにIEquatable<T>を実装し、object.Equals(object)とobject.GetHashCode()をオーバーライドすることで、Exceptの動作を意図したものにすることが可能です。

csharp

1public class Person : IEquatable<Person> 2{ 3 public int Id { get; private set; } 4 public string Name { get; private set; } 5 6 public Person(int id, string name) 7 { 8 this.Id = id; 9 this.Name = name; 10 } 11 12 public bool Equals(Person obj) => this.Name == obj?.Name; 13 14 public override bool Equals(object obj) => this.Equals(obj as Person); 15 16 public override int GetHashCode() => this.Name.GetHashCode(); 17}

csharp

1var diffList = local.Except(web);

この場合、Nameが同じであるPersonは本当に「同じモノである」として扱われることになるので、それによって何か不都合が生じないように注意する必要があります。

投稿2016/12/21 07:58

編集2016/12/21 09:02
tamoto

総合スコア4232

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

前述で書かれているのと差がないですが、
MSDNのIEqualityComparer<T>(日本語は翻訳内容がキツイので英語を...)的という意味で、IEqualityComparerを使った基礎な書き方の紹介です。

charp

1public class PersonComparer : IEqualityComparer<Person> 2{ 3 public int GetHashCode(Person p) => p?.Name.GetHashCode() ?? 0; 4 5 public bool Equals(Person p1, Person p2) 6 { 7 if (object.ReferenceEquals(p1, p2)) return true; 8 if (object.ReferenceEquals(p1, null) || object.ReferenceEquals(p2, null)) return false; 9 10 return p1.Name == p2.Name; 11 } 12}

で、

csharp

1var diff = local.Except(web, new PersonComparer()).ToList();

という感じです。
(LINQ自体遅いけど標準的という意味で)速度的にも悪くないはずです。

この動作をおさえた上で、めんどくて燃えそうと感じた場合、Generics対応するなり拡張メソッド作っちゃうとかです♪
そこら辺は、「generic」「IEqualityComparer」でググると英語の記事だと基本的なのがたくさんでます。あとはneue先生の記事が参考になると(個人的に)感じてます♪

投稿2016/12/22 01:41

BEACHSIDE

総合スコア294

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

IEqualityComparer を作らないといけないですが、次のようなコードはどうでしょうか?

c#

1var set = new HashSet<Person>(local, PersonEquality.Instance); 2set.SymmetricExceptWith(web); 3// set に差集合が格納されています。 4 5// 手抜きです 6class PersonEquality : IEqualityComparer<Person> 7{ 8 public bool Equals(Person x, Person y) => x.Name == y.Name; 9 public int GetHashCode(Person obj) => obj.Name.GetHashCode(); 10 public static PersonEquality Instance { get; } = new PersonEquality(); 11}

投稿2016/12/21 14:53

編集2016/12/21 15:52
espresso3389

総合スコア31

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

c#

1 var diffList = 2 (from l in local 3 join w in web on l.Name equals w.Name into x 4 where x.Count() == 0 5 select l).ToList();

というのが好きで使っています。

投稿2016/12/21 12:00

NakamuraYoichi

総合スコア374

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

むしろコード増えちゃいました。

C#

1using System; 2using System.Collections.Generic; 3using System.Linq; 4 5class Class1 6{ 7 static void Main(string[] args) 8 { 9 var local = new List<Person> 10 { 11 new Person(1,"test1"), 12 new Person(2,"test2"), 13 new Person(3,"test3"), 14 new Person(4,"test4"), // これと 15 new Person(5,"test5"), // これがほしい 16 }; 17 18 var web = new List<Person> 19 { 20 new Person(2,"test1"), 21 new Person(3,"test2"), 22 new Person(4,"test3"), 23 }; 24 25 var result = local.Except(web, new PersonComparer()); 26 foreach(var x in result) 27 Console.WriteLine($"{x.Id},{x.Name}"); 28 29 30 } 31} 32public class Person 33{ 34 public int Id { get; } 35 public string Name { get; } 36 37 public Person(int id, string name) 38 { 39 this.Id = id; 40 this.Name = name; 41 } 42} 43 44public class PersonComparer : IEqualityComparer<Person>{ 45 public bool Equals(Person p1, Person p2) 46 { 47 // nullチェック省略 48 return (p1.Name == p2.Name); 49 } 50 51 public int GetHashCode(Person p) 52 { 53 return p.Name.GetHashCode(); 54 } 55}

投稿2016/12/21 08:17

ozwk

総合スコア13551

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問