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

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

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

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

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

Q&A

解決済

3回答

3647閲覧

2つの異なる型のList同士で、共通している要素(重複要素)を削除したい

galmacher

総合スコア37

C#

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

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

0グッド

0クリップ

投稿2019/04/27 00:56

編集2019/04/27 01:44

AppleクラスとOrangeクラスがあり、それぞれリンゴとミカンの性質(重さ、値段、色、出荷日…)をプロパティで保持しているとします。
二つのクラスは型も名称も同じプロパティを多く持っていますが、全プロパティが共通しているわけではありません。

List<Apple>とList<Orange>を生成し、
AppleとOrangeの「重さ」と「値段」の二つが同じ要素は削除したいです。
・重さ、値段はint型です
・AppleとOrangeどちらかを残すかという優先度はありません。
・Aplle、Orangeともに自クラス内では重さと値段で一意となっています。

思いついたのはList<Apple>から重さと値段の組み合わせを抽出して、
List<Orange>に一致するものがあれば消すという方法です。

しかし、List<AppleAndOrange>のような、
両方の型を入れられるようなListを作って、
重さと値段が一致する要素は初めの要素だけ残すやり方ができれば
よりスマートだなと思っています。
このようなやり方はできますでしょうか?

また、他により良いアイデアがあればご教示いただきたいです。

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

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

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

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

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

退会済みユーザー

退会済みユーザー

2019/04/27 01:42

「重さ」と「値段」の型は何ですか? double と decimal?
galmacher

2019/04/27 01:46

int型でお願いします。質問文にもその旨を明記いたしました。
guest

回答3

0

こんな構成のクラスを作っておいて

C#

1interface IFruits 2{ 3 decimal Weight { get; set; } 4 decimal Price { get; set; } 5} 6 7class EquatableFruits : IFruits, IEquatable<EquatableFruits> 8{ 9 public decimal Weight { get; set; } 10 public decimal Price { get; set; } 11 12 public override int GetHashCode() 13 { 14 return Weight.GetHashCode() ^ Price.GetHashCode(); 15 } 16 17 public virtual bool Equals(EquatableFruits other) 18 { 19 return 20 this.Price == other.Price 21 && 22 this.Weight == other.Weight; 23 } 24} 25 26class Orange : EquatableFruits 27{ 28 public bool IsNoSeed { get; set; } 29} 30 31class Apple : EquatableFruits 32{ 33 public bool WithHoney { get; set; } 34}

こんな感じで使います。

C#

1var fruits = new List<EquatableFruits>(); 2 3var apples = new List<Apple> 4{ 5 new Apple 6 { 7 Price = 100, 8 Weight = 200, 9 WithHoney = true 10 } 11}; 12 13var oranges = new List<Orange> 14{ 15 new Orange 16 { 17 Price = 100, 18 IsNoSeed = true, 19 Weight = 200 20 } 21}; 22 23fruits.AddRange(apples); 24fruits.AddRange(oranges); 25 26var result = fruits.Distinct().ToList();

--- 追記 --
あるいは基底クラス(EquatableFruits)を用いずにIFruitsの比較クラスを用意して

C#

1class FruitsEqualtyComparer : IEqualityComparer<IFruits> 2{ 3 public bool Equals(IFruits x, IFruits y) 4 { 5 return x.Price == y.Price && x.Weight == y.Weight; 6 } 7 8 public int GetHashCode(IFruits obj) 9 { 10 return obj.Price.GetHashCode() ^ obj.Weight.GetHashCode(); 11 } 12}

C#

1var fruits2 = new List<IFruits>(); 2fruits2.AddRange(apples); 3fruits2.AddRange(oranges); 4 5var result2 = fruits.Distinct(new FruitsEqualtyComparer()).ToList();

のほうが汎用的かもしれません。

投稿2019/04/27 01:48

編集2019/04/27 04:58
hihijiji

総合スコア4150

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

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

hihijiji

2019/04/27 03:11

Distinct() メソッドが使えるEquatableFruitsを作ったつもりでしたが、 EquatableFruitsの実装に間違いがありましたの修正しました。
galmacher

2019/04/27 16:40

ご回答ありがとうございました。IEquatable、IEqualityComparerを使用したそれぞれの方法を挙げてくださり、大変参考になりました。
guest

0

ベストアンサー

みなさん Distinct を使われていますが、求められるのは和集合であり、価格と重さの組み合わせはもともとの集合の中では一意ということなので Union を使ってみました。

CustomEqualityComparer は比較用クラスですが、これを作っておけば実際に使用する際にはラムダ式から作成できるので何かと便利です。

LINQ は他にも集合演算ができます。今回は和集合を求める質問でしたが、もしも本来の目的が和集合ではなく差集合または排他的論理和集合であるなら、次の資料をご覧ください。

LINQの集合演算

C#

1using System; 2using System.Collections.Generic; 3using System.Linq; 4 5namespace ConsoleApp1 6{ 7 public class CustomEqualityComparer<T> : IEqualityComparer<T> 8 { 9 public CustomEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode = null) 10 { 11 this.equals = equals; 12 this.getHashCode = getHashCode; 13 } 14 private readonly Func<T, int> getHashCode; 15 private readonly Func<T, T, bool> equals; 16 17 public bool Equals(T x, T y) 18 { 19 return equals?.Invoke(x, y) ?? false; 20 } 21 22 public int GetHashCode(T obj) 23 { 24 return getHashCode?.Invoke(obj) ?? 0; 25 } 26 } 27 28 public interface IFruit 29 { 30 int Weight { get; set; } 31 int Price { get; set; } 32 } 33 34 public class Apple : IFruit 35 { 36 public int Weight { get; set; } 37 public int Price { get; set; } 38 } 39 40 public class Orange : IFruit 41 { 42 public int Weight { get; set; } 43 public int Price { get; set; } 44 } 45 46 class Program 47 { 48 static void Main(string[] args) 49 { 50 List<Apple> apples = new List<Apple>() 51 { 52 new Apple() { Weight = 10, Price = 10 }, 53 new Apple() { Weight = 20, Price = 20 }, 54 }; 55 List<Orange> oranges = new List<Orange>() 56 { 57 new Orange() { Weight = 20, Price = 20 }, 58 new Orange() { Weight = 30, Price = 30 }, 59 }; 60 var comparer = new CustomEqualityComparer<IFruit>( 61 (a, b) => a.Price == b.Price && a.Weight == b.Weight); 62 var fruits = apples.Union(oranges, comparer); 63 foreach (var fruit in fruits) 64 { 65 Console.WriteLine(fruit.Weight); 66 } 67 Console.ReadKey(); 68 } 69 } 70}

投稿2019/04/27 15:31

編集2019/04/27 15:56
Zuishin

総合スコア28660

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

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

galmacher

2019/04/27 16:41

ご回答ありがとうございました。ジェネリックとデリゲートのおかげで、呼び出し元から汎用的に比較を行える仕組みがとても参考になりました。
Zuishin

2019/04/28 04:09 編集

今回 GetHashCode の戻り値を 0 に固定していて、それで結果は問題ないんですが、大量のデータを扱う場合にはパフォーマンスが悪くなる場合があるので、そのような時にはここもきちんと設定してください。 比較する各数値の排他的論理和を使うのが簡単ですが、値に偏りがある場合には乗算と加算を組み合わせて使えば演算結果にバラつきが出てもう少しパフォーマンスが上がる可能性もあります。例えば排他的論理和の場合は (10, 20) と (20, 10) は同じ値になり、また小さな数字ばかりの時には上位ビットが遊んでいるのでバラつきは小さくなります。乗算と加算をうまく使えば上位ビットにも仕事をさせることができます。 このあたりはどのようなデータを扱うかによって最適解が変わります。データの数が少ない場合には差は出ませんから、速度が遅いと感じた時にチューニングしても間に合うと思います。
guest

0

しかし、List<AppleAndOrange>のような、両方の型を入れられるようなListを作って、

AppleAndOrange クラスは(名前、重さ、値段、色、出荷日…)というような構造になるのでしょうか? 

List<Apple> と List<Orange> から List<AppleAndOrange> を作るということでよければ、以下の記事の 1 または 2 いずれかの方法で Distinct メソッドで「重さ」と「値段」の重複をチェックできるようにしていかがですか?

匿名型と Distinct メソッド
http://surferonwww.info/BlogEngine/post/2015/12/08/anonymous-type-and-distinct-method.aspx

不明点がある場合、やってみたがうまくいかない場合は連絡ください。

【追記】

上に紹介した記事「匿名型と Distinct メソッド」の 1. の方法で Distinct メソッドによる重複排除ができるようにしたサンプルを書いておきます。これでは質問者さんの目的が果たせない場合は、どこをどう直すべきか具体的に連絡ください。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleAppDistinct { public class Apple { public int Weight { get; set; } public int Price { get; set; } } public class Orange { public int Weight { get; set; } public int Price { get; set; } } public class AppleAndOrange : IEquatable<AppleAndOrange> { public string Name { get; set; } public int Weight { get; set; } public int Price { get; set; } public bool Equals(AppleAndOrange other) { if (Object.ReferenceEquals(other, null)) return false; if (Object.ReferenceEquals(this, other)) return true; return Price.Equals(other.Price) && Weight.Equals(other.Weight); } // If Equals() returns true for a pair of objects // then GetHashCode() must return the same value for these objects. public override int GetHashCode() { return Price.GetHashCode() ^ Weight.GetHashCode(); } } class Program { static void Main(string[] args) { List<Apple> apples = new List<Apple> { new Apple { Price = 100, Weight = 100 }, new Apple { Price = 110, Weight = 110 }, new Apple { Price = 120, Weight = 120 }, new Apple { Price = 130, Weight = 130 }, new Apple { Price = 140, Weight = 140 } }; List<Orange> oranges = new List<Orange> { new Orange { Price = 100, Weight = 110 }, new Orange { Price = 105, Weight = 100 }, new Orange { Price = 120, Weight = 120 }, new Orange { Price = 130, Weight = 125 }, new Orange { Price = 140, Weight = 140 } }; List<AppleAndOrange> applesAndOranges = new List<AppleAndOrange>(); foreach(Apple a in apples) { applesAndOranges.Add(new AppleAndOrange { Name = "Apple", Price = a.Price, Weight = a.Weight }); } foreach (Orange o in oranges) { applesAndOranges.Add(new AppleAndOrange { Name = "Orange", Price = o.Price, Weight = o.Weight }); } foreach (AppleAndOrange x in applesAndOranges) { Console.WriteLine($"Name: {x.Name}, Price: {x.Price}, Weight: {x.Weight}"); } Console.WriteLine("-------------------------"); foreach (AppleAndOrange x in applesAndOranges.Distinct()) { Console.WriteLine($"Name: {x.Name}, Price: {x.Price}, Weight: {x.Weight}"); } } } }

実行結果は以下の通りとなります。

Name: Apple, Price: 100, Weight: 100
Name: Apple, Price: 110, Weight: 110
Name: Apple, Price: 120, Weight: 120
Name: Apple, Price: 130, Weight: 130
Name: Apple, Price: 140, Weight: 140
Name: Orange, Price: 100, Weight: 110
Name: Orange, Price: 105, Weight: 100
Name: Orange, Price: 120, Weight: 120
Name: Orange, Price: 130, Weight: 125
Name: Orange, Price: 140, Weight: 140
------------------
Name: Apple, Price: 100, Weight: 100
Name: Apple, Price: 110, Weight: 110
Name: Apple, Price: 120, Weight: 120
Name: Apple, Price: 130, Weight: 130
Name: Apple, Price: 140, Weight: 140
Name: Orange, Price: 100, Weight: 110
Name: Orange, Price: 105, Weight: 100
Name: Orange, Price: 130, Weight: 125

投稿2019/04/27 02:27

編集2019/04/27 03:46
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

galmacher

2019/04/27 16:39

ご回答ありがとうございました。1,2の方法ともに参考になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問