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

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

ただいまの
回答率

90.51%

  • C#

    7147questions

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

  • 配列

    525questions

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

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

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 425

uecky

score 1

 前提・実現したいこと

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

 該当のソースコード

HashSet<int> A = new HashSet<int> { 1, 2, 6 };
HashSet<int> B = new HashSet<int> { 6, 8 };
HashSet<int> C = new HashSet<int> { 9, 5 };
HashSet<int> D = new HashSet<int> { 1, 3, 8 };
HashSet<int> E = new HashSet<int> { 7, 9 };
HashSet<int> F = new HashSet<int> { 4 };

/*欲しい結果
[配列A[1,2,3,6,8],配列B[4],配列C[5,7,9]]
のように、ダブりなく組み分けしたい。
ちなみに配列の中の数字はは毎回ランダムに変わります。
*/

 試したこと

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

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

    //ランダムな数字を持ったハッシュセット
    HashSet<int> A = new HashSet<int> { 1, 2, 6 };
    HashSet<int> B = new HashSet<int> { 6, 8 };
    HashSet<int> C = new HashSet<int> { 9, 5 };
    HashSet<int> D = new HashSet<int> { 1, 3, 8 };
    HashSet<int> E = new HashSet<int> { 7, 9 };
    HashSet<int> F = new HashSet<int> { 4 };

    //上記のハッシュセットをまとめたリスト
    List<HashSet<int>> listA;

  //最終でダブりのないハッシュセットをもつリスト
    List<HashSet<int>> listB;
  //補佐のリスト
    List<HashSet<int>> listC;
    //listBに追加するか否かのフラグ
    private bool NA = false;

    void Start () {
    //初期化
        listA = new List<HashSet<int>> { A, B, C, D, E, F };
        listB = new List<HashSet<int>> { };
        listC = new List<HashSet<int>> { };
    }

    public void Union()
    {
  //listAの中身を総当たりして、要素が被ってたら一番最初のハッシュセットにまとめる
        for (int i = 0; i < listA.Count; i++)
        {
            for (int j = 1; j < listA.Count; j++)
            {
                if (listA[i].Overlaps(listA[j]))
                {
                    listA[i].UnionWith(listA[j]);
                }
            }
            NA = true;
            if(listC.Count!=0){
                foreach(var k in listC){
                    //すでに要素が被ってるものがある場合はlistBに追加しない
                    if (listA[i].Overlaps(k)){
                        NA = false;
                    }
                 }
            }
            if(NA==true){listB.Add(listA[i]);}
            listC.Add(listA[i]);
        }

        print("最終チェック"+listB.Count);
        foreach (var l in listB)
        {
            print(l + "の中の数字は");
            foreach (var m in l)
            {
                print(m);
            }
        }
    }
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • pepperleaf

    2018/07/16 11:05

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

    キャンセル

  • uecky

    2018/07/16 17:51

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

    キャンセル

  • papinianus

    2018/07/17 10:29

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

    キャンセル

  • uecky

    2018/07/17 10:56

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

    キャンセル

回答 4

checkベストアンサー

+4

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

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

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

{
    var data = new[] { A, B, C, D, E, F };

    var result = data
        .GroupBy(x => x, new HashSetOverlapEqualityComparer<int>())
        .Select(x => x.Aggregate(Enumerable.Empty<int>(), (set, set2) => set.Union(set2)));
}
class HashSetOverlapEqualityComparer<T> : IEqualityComparer<HashSet<T>>
{
    public bool Equals(HashSet<T> x, HashSet<T> y) => x.Overlaps(y);
    public int GetHashCode(HashSet<T> obj) => 0;
}

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

foreach (var set in result)
    Console.WriteLine(string.Join(", ", set.OrderBy(x => x)));

/* output:
1, 2, 3, 6, 8
5, 7, 9
4
*/

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

public static class UnionExtension
{
    public static IEnumerable<IEnumerable<T>> GroupUnion<T>(this IEnumerable<IEnumerable<T>> source)
    {
        var result = source
            .GroupBy(x => x, new OverlapsEqualityComparer<T>())
            .Select(x => x.Aggregate((set, set2) => set.Union(set2)))
            .ToArray();
        return (result.Length == source.Count()) ? result : result.GroupUnion();
    }
    class OverlapsEqualityComparer<T> : IEqualityComparer<IEnumerable<T>>
    {
        public bool Equals(IEnumerable<T> x, IEnumerable<T> y) => x.Intersect(y).Count() != 0;
        public int GetHashCode(IEnumerable<T> obj) => 0;
    }
}
// つかう
var data = new[] { A, B, C, D, E, F };
var result = data.GroupUnion();

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+2

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

List<HashSet<int>> hashList = new List<HashSet<int>>() {A, B, C, D, E, F };    // 全リスト
List<HashSet<int>> orgList = new List<HashSet<int>>();
bool exitFlag = false;
while (!exitFlag) {        // 重複データがある間、ループする
    orgList = hashList;
    hashList = new List<HashSet<int>>();
    exitFlag = true;
    // orgList --> hashList へコピーしながら、検査
    foreach (var item in orgList) {
        bool found = false;
        foreach (var newitem in hashList) {
            if (item.Overlaps(newitem)) {        // 重複データがあるか
                newitem.UnionWith(item);        // あれば、マージ
                found = true;
                break;
            }
        }
        if (!found)    hashList.Add(item);        // 重複が見つからないので、追加
        exitFlag &= !found;        // 重複があったか?
    }
}

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

List<HashSet<int>> hashList = new List<HashSet<int>>() {A, B, C, D, E, F };    // 全リスト

HashSet<int> allSet = new HashSet<int>();    // 全てのデータを格納する
foreach (var hash in hashList) {
    hash.ExceptWith(allSet);    // 重複データを除く
    ~~foreach (var item in hash) {    // 確認済みデータに追加
        allSet.Add(item);
    }~~
    allSet.UnionWith(hash);    // これで良かった。
}

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/17 09:53

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

    キャンセル

  • 2018/07/17 10:15

    上記コード試してみました!
    全てのHashSetのintの中身が被らないように、
    新たにHashSet<int> を生成してくださったのですね。

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

    キャンセル

  • 2018/07/18 10:04

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

    キャンセル

  • 2018/07/19 20:38

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

    キャンセル

+2

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

HashSet<int> A = new HashSet<int> { 1, 2 };
HashSet<int> B = new HashSet<int> { 3, 4 };
HashSet<int> C = new HashSet<int> { 2, 3 };
HashSet<int> D = new HashSet<int> { 5, 6, 8 };
HashSet<int> E = new HashSet<int> { 7, 9 };
HashSet<int> F = new HashSet<int> { 4 };

var allList = new List<HashSet<int>> { A, B, C, D, E, F, };

for (int i = 0; i < allList.Count; i++)
{
    for (int j = 0; j < allList.Count; j++)
    {
        if (i != j && allList[i].Where(d => allList[j].Contains(d)).Any())
        {
            allList[j].ToList().ForEach(d => allList[i].Add(d));
            allList[j].Clear();
        }
    }
}

//result
var result = allList.Where(d => d.Any());

//print
foreach (var set in result)
{
    System.Diagnostics.Debug.WriteLine(string.Join(",", set));
}


最初は、こんな感じで

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


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

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

var allList = new List<HashSet<int>> { A, B, C, D, E, F, };

allList.ForEach(outer => allList.ForEach(inner => {
    if (outer != inner && outer.Where(d => inner.Contains(d)).Any()) {
        inner.ToList().ForEach(d => outer.Add(d));
        inner.Clear();
    }
}));

//result
var result = allList.Where(d => d.Any());

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

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

static void Main(string[] args)
{
    HashSet<int> A = new HashSet<int> { 1, 2, 6 };
    HashSet<int> B = new HashSet<int> { 6, 8 };
    HashSet<int> C = new HashSet<int> { 9, 5 };
    HashSet<int> D = new HashSet<int> { 1, 3, 8 };
    HashSet<int> E = new HashSet<int> { 7, 9 };
    HashSet<int> F = new HashSet<int> { 4 };

    var hashQueue = new Queue<HashSet<int>>(new[] { A, B, C, D, E, F });

    var result = new List<HashSet<int>>();

    while(hashQueue.Count > 0)
    {
        HashSet<int> current = hashQueue.Dequeue();

        var count = hashQueue.Count;
        for (int i = 0; count > i; i++)
        {
            var target = hashQueue.Dequeue();

            if(current.Where(c => target.Contains(c)).Any())
            {
                current = new HashSet<int>(current.Union(target).OrderBy(idx => idx));
            }
            else
            {
                hashQueue.Enqueue(target);
            }
        }
        result.Add(current);
    }

    result.ForEach(r => Console.WriteLine(r.Select(i => i.ToString())
        .Aggregate((a,b) => a + "," + b)));
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/17 13:25

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

    キャンセル

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

  • ただいまの回答率 90.51%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C#

    7147questions

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

  • 配列

    525questions

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