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

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

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

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

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

Q&A

解決済

4回答

2275閲覧

C#で重複している配列同士をまとめたい

uecky

総合スコア9

C#

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

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

0グッド

0クリップ

投稿2018/07/16 01:46

編集2018/07/16 08:50

前提・実現したいこと

配列同士比べて、1つでも重複している要素があれば、配列同士をまとめるという事をしたいです。

該当のソースコード

C#

1 2 3HashSet<int> A = new HashSet<int> { 1, 2, 6 }; 4HashSet<int> B = new HashSet<int> { 6, 8 }; 5HashSet<int> C = new HashSet<int> { 9, 5 }; 6HashSet<int> D = new HashSet<int> { 1, 3, 8 }; 7HashSet<int> E = new HashSet<int> { 7, 9 }; 8HashSet<int> F = new HashSet<int> { 4 }; 9 10/*欲しい結果 11[配列A[1,2,3,6,8],配列B[4],配列C[5,7,9]] 12のように、ダブりなく組み分けしたい。 13ちなみに配列の中の数字はは毎回ランダムに変わります。 14*/

試したこと

HashSetで重複しない配列を作れるというのはわかったのですが、
判定して結合していくループをどう作ったら良いのかが全くわかりません。
ご教授いただければ幸いです。

新たに試してみたソースコード

C#

1 2 //ランダムな数字を持ったハッシュセット 3 HashSet<int> A = new HashSet<int> { 1, 2, 6 }; 4 HashSet<int> B = new HashSet<int> { 6, 8 }; 5 HashSet<int> C = new HashSet<int> { 9, 5 }; 6 HashSet<int> D = new HashSet<int> { 1, 3, 8 }; 7 HashSet<int> E = new HashSet<int> { 7, 9 }; 8 HashSet<int> F = new HashSet<int> { 4 }; 9 10 //上記のハッシュセットをまとめたリスト 11 List<HashSet<int>> listA; 12 13  //最終でダブりのないハッシュセットをもつリスト 14 List<HashSet<int>> listB; 15  //補佐のリスト 16 List<HashSet<int>> listC; 17 //listBに追加するか否かのフラグ 18 private bool NA = false; 19 20 void Start () { 21 //初期化 22 listA = new List<HashSet<int>> { A, B, C, D, E, F }; 23 listB = new List<HashSet<int>> { }; 24 listC = new List<HashSet<int>> { }; 25 } 26 27 public void Union() 28 { 29  //listAの中身を総当たりして、要素が被ってたら一番最初のハッシュセットにまとめる 30 for (int i = 0; i < listA.Count; i++) 31 { 32 for (int j = 1; j < listA.Count; j++) 33 { 34 if (listA[i].Overlaps(listA[j])) 35 { 36 listA[i].UnionWith(listA[j]); 37 } 38 } 39 NA = true; 40 if(listC.Count!=0){ 41 foreach(var k in listC){ 42 //すでに要素が被ってるものがある場合はlistBに追加しない 43 if (listA[i].Overlaps(k)){ 44 NA = false; 45 } 46 } 47 } 48 if(NA==true){listB.Add(listA[i]);} 49 listC.Add(listA[i]); 50 } 51 52 print("最終チェック"+listB.Count); 53 foreach (var l in listB) 54 { 55 print(l + "の中の数字は"); 56 foreach (var m in l) 57 { 58 print(m); 59 } 60 } 61 } 62

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

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

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

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

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

pepperleaf

2018/07/16 02:05

HashSet.Contains() と HashSet.RemoveWhere() を使えば、出来そう。参考まで (時間が取れれば、確認してみたい)
uecky

2018/07/16 08:51

ありがとうございます! 
恥ずかしながら、プログラムがかなりの初心者で、HashSet.Contains() と HashSet.RemoveWhere() を使う方法が考えてみたもののわからずじまいでした。 
overlapというのとUnionWithというのを使ってとりあえずやってみました。新たに試してみたソースコードというところに載せております。
とりあえずの結果が取れたっぽいのだけど、このソースだと例外でうまいこといかない場合があるのでは、、、と不安です。
間違っていたり、もう少し賢いやり方があればあればご指摘いただけると幸いです。。
papinianus

2018/07/17 01:29

欲しいものについて詳しい条件を伺いたいのですが、[1,3][3,5,][5,7]の3つは1つになってよいのでしょうか。つまり、1つめと3つめは互いに共通要素はないのですが、2つ目がいることによって連結される?それとも[1,3,5]と[3,5,7]の2つでしょうか?
uecky

2018/07/17 01:56

はい、2つ目がいることによって連結される が正しいです!
SNSのつながりみたいな感じで、繋がってたらそのグラフを1グループにするイメージです。
 [1,3][3,5,][5,7]の3つは1つにしたいです。 サンプルの例だと、Fだけ4は誰とも被ってないので1グループとなります。
guest

回答4

0

ベストアンサー

こんにちは。
なんだか面白そうな問題だったので回答してみます。

(下のコードにはバグがみつかったので、さらに下に追記してます。↓↓↓)

自分はIEqualityComparerを悪用してさっぱり実現してみました。たぶんこれが一番短いと思います(?)

csharp

1{ 2 var data = new[] { A, B, C, D, E, F }; 3 4 var result = data 5 .GroupBy(x => x, new HashSetOverlapEqualityComparer<int>()) 6 .Select(x => x.Aggregate(Enumerable.Empty<int>(), (set, set2) => set.Union(set2))); 7} 8class HashSetOverlapEqualityComparer<T> : IEqualityComparer<HashSet<T>> 9{ 10 public bool Equals(HashSet<T> x, HashSet<T> y) => x.Overlaps(y); 11 public int GetHashCode(HashSet<T> obj) => 0; 12}

HashSet<T>のOverlapsメソッドによる重複検知を「イコールである」と判定してしまうようなIEqualityComparerを実装することで、GroupByのグルーピング機能に丸投げしています。
大分斜め上なハックですけど、おそらく可読性とメンテナンス性という意味では優れている方なんじゃないかなーと思います。
要素の順序などは特に考慮していないので、必要なら好きにOrderByするなりしてください。

csharp

1foreach (var set in result) 2 Console.WriteLine(string.Join(", ", set.OrderBy(x => x))); 3 4/* output: 51, 2, 3, 6, 8 65, 7, 9 74 8*/

追記
はわわ……上の実装にはバグがありました……OrderByは一度取ったキーが以後更新されないので「2つ目以降のSetとのみ重複している場合」のマージ処理が行われません……
筋肉(再帰)で解決!!!

csharp

1public static class UnionExtension 2{ 3 public static IEnumerable<IEnumerable<T>> GroupUnion<T>(this IEnumerable<IEnumerable<T>> source) 4 { 5 var result = source 6 .GroupBy(x => x, new OverlapsEqualityComparer<T>()) 7 .Select(x => x.Aggregate((set, set2) => set.Union(set2))) 8 .ToArray(); 9 return (result.Length == source.Count()) ? result : result.GroupUnion(); 10 } 11 class OverlapsEqualityComparer<T> : IEqualityComparer<IEnumerable<T>> 12 { 13 public bool Equals(IEnumerable<T> x, IEnumerable<T> y) => x.Intersect(y).Count() != 0; 14 public int GetHashCode(IEnumerable<T> obj) => 0; 15 } 16}

csharp

1// つかう 2var data = new[] { A, B, C, D, E, F }; 3var result = data.GroupUnion();

投稿2018/07/17 10:15

編集2018/07/17 11:59
tamoto

総合スコア4103

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

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

0

逆に素直に実装してみましたがどうでしょうか

csharp

1HashSet<int> A = new HashSet<int> { 1, 2 }; 2HashSet<int> B = new HashSet<int> { 3, 4 }; 3HashSet<int> C = new HashSet<int> { 2, 3 }; 4HashSet<int> D = new HashSet<int> { 5, 6, 8 }; 5HashSet<int> E = new HashSet<int> { 7, 9 }; 6HashSet<int> F = new HashSet<int> { 4 }; 7 8var allList = new List<HashSet<int>> { A, B, C, D, E, F, }; 9 10for (int i = 0; i < allList.Count; i++) 11{ 12 for (int j = 0; j < allList.Count; j++) 13 { 14 if (i != j && allList[i].Where(d => allList[j].Contains(d)).Any()) 15 { 16 allList[j].ToList().ForEach(d => allList[i].Add(d)); 17 allList[j].Clear(); 18 } 19 } 20} 21 22//result 23var result = allList.Where(d => d.Any()); 24 25//print 26foreach (var set in result) 27{ 28 System.Diagnostics.Debug.WriteLine(string.Join(",", set)); 29} 30

最初は、こんな感じで

csharp

1for (int i = 0; i < allList.Count - 1; i++) 2{ 3 for (int j = i + 1; j < allList.Count; j++) 4 {

一方向だけ見てたら
A {1,2} B {2,3} C {3,4} はよくても
A {1,2} B {3,4} C {2,3} がダメで
結局両方向になりました。

追記:
インデクサはやっぱり嫌なので、ごり押しで短くしました

csharp

1var allList = new List<HashSet<int>> { A, B, C, D, E, F, }; 2 3allList.ForEach(outer => allList.ForEach(inner => { 4 if (outer != inner && outer.Where(d => inner.Contains(d)).Any()) { 5 inner.ToList().ForEach(d => outer.Add(d)); 6 inner.Clear(); 7 } 8})); 9 10//result 11var result = allList.Where(d => d.Any());

投稿2018/07/17 05:57

編集2018/07/17 06:07
sh_akira

総合スコア380

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

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

0

書換えてみました。
他の方ほど、シンプルでありませんが、一応。

C#

1List<HashSet<int>> hashList = new List<HashSet<int>>() {A, B, C, D, E, F }; // 全リスト 2List<HashSet<int>> orgList = new List<HashSet<int>>(); 3bool exitFlag = false; 4while (!exitFlag) { // 重複データがある間、ループする 5 orgList = hashList; 6 hashList = new List<HashSet<int>>(); 7 exitFlag = true; 8 // orgList --> hashList へコピーしながら、検査 9 foreach (var item in orgList) { 10 bool found = false; 11 foreach (var newitem in hashList) { 12 if (item.Overlaps(newitem)) { // 重複データがあるか 13 newitem.UnionWith(item); // あれば、マージ 14 found = true; 15 break; 16 } 17 } 18 if (!found) hashList.Add(item); // 重複が見つからないので、追加 19 exitFlag &= !found; // 重複があったか? 20 } 21}

-------- 最初のコード ----------------------------
以下のコードではどうでしょうか?

C#

1List<HashSet<int>> hashList = new List<HashSet<int>>() {A, B, C, D, E, F }; // 全リスト 2 3HashSet<int> allSet = new HashSet<int>(); // 全てのデータを格納する 4foreach (var hash in hashList) { 5 hash.ExceptWith(allSet); // 重複データを除く 6 ~~foreach (var item in hash) { // 確認済みデータに追加 7 allSet.Add(item); 8 }~~ 9 allSet.UnionWith(hash); // これで良かった。 10} 11

もう少しスマートにできるかと思ったのですが、、、。

投稿2018/07/16 13:28

編集2018/07/17 14:21
pepperleaf

総合スコア6383

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

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

uecky

2018/07/17 00:53

す、すごい!!!!!めちゃめちゃ短くなってる!!!!! 初質問で緊張していたのですが、ここで質問して本当によかったです! ご回答ありがとうございます!
uecky

2018/07/17 01:15

上記コード試してみました! 全てのHashSetのintの中身が被らないように、 新たにHashSet<int> を生成してくださったのですね。 書き方がややこしかったのですが、 実現したい形式がちょっと違っておりました。 [ハッシュセットA[1,2,3,6,8] , ハッシュセットB[4], ハッシュセットC[5,7,9]] のように、共有している数同士を結合し、ダブりなく組み分けした状態にしたかったのです。 こちらもスマートなやり方でなんとか解決できないでしょうか・・・(; _ ;)
uecky

2018/07/18 01:04

元の自分で試したソースと比べて、こういう風にループを組めばいいのか!!!とすごく勉強になりました。こういう出力がすぐできるように、勉強頑張りたいと思います。ありがとうございます!
pepperleaf

2018/07/19 11:38

少しは役に立ったようで良かったです。 他の方の方法も確認したいのですが、只今、その余裕無しの状態。 (暑さ+その他で)
guest

0

Linq大好きLinqerの私としてはループを使わず書きたかったのですが、ちょっと骨ですね。

C#

1static void Main(string[] args) 2{ 3 HashSet<int> A = new HashSet<int> { 1, 2, 6 }; 4 HashSet<int> B = new HashSet<int> { 6, 8 }; 5 HashSet<int> C = new HashSet<int> { 9, 5 }; 6 HashSet<int> D = new HashSet<int> { 1, 3, 8 }; 7 HashSet<int> E = new HashSet<int> { 7, 9 }; 8 HashSet<int> F = new HashSet<int> { 4 }; 9 10 var hashQueue = new Queue<HashSet<int>>(new[] { A, B, C, D, E, F }); 11 12 var result = new List<HashSet<int>>(); 13 14 while(hashQueue.Count > 0) 15 { 16 HashSet<int> current = hashQueue.Dequeue(); 17 18 var count = hashQueue.Count; 19 for (int i = 0; count > i; i++) 20 { 21 var target = hashQueue.Dequeue(); 22 23 if(current.Where(c => target.Contains(c)).Any()) 24 { 25 current = new HashSet<int>(current.Union(target).OrderBy(idx => idx)); 26 } 27 else 28 { 29 hashQueue.Enqueue(target); 30 } 31 } 32 result.Add(current); 33 } 34 35 result.ForEach(r => Console.WriteLine(r.Select(i => i.ToString()) 36 .Aggregate((a,b) => a + "," + b))); 37}

投稿2018/07/17 02:38

hihijiji

総合スコア4150

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

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

papinianus

2018/07/17 04:25

こういうことを考えたのですが、やっぱり2段階ループするしかないですよね。2回目のループをforにして、queueが残っていても終了させる点が大変参考になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問