🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C#

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

LINQ

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

Q&A

解決済

1回答

1630閲覧

クラスのプロパティごとに重複を除いた辞書を作りたい。重複していたら加算を行いたい。

rtazaki

総合スコア69

C#

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

LINQ

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

0グッド

0クリップ

投稿2021/03/20 16:04

編集2021/03/21 07:58

前提・実現したいこと

yield returnされたクラス情報を基に、一つの辞書を作りたいです。
仕様としては以下の通りです。

仕様

  • 情報を呼び出すクラスProgramと呼ばれるクラスAnotherがいる。
  • Anotherクラス
  • Humanクラス: 国名、名前、ログイン回数をプロパティに持つ。
  • GetHumansメソッド: (出来ればプロパティにしたい)情報を順に取り出す。
  • Programクラス
  • GetHumans~~の中で、~~から国名、名前、ログイン回数の多段辞書を作る。(Jsonとして外に出す情報)

** 国名、名前が等しい場合、ログイン回数は加算する。**

発生している問題

Anotherクラス側で辞書を持たずにIEnumerable<Human>で順次出力させるような仕様としたのですが、
その情報を辞書化するにあたっての処理が泥臭いと感じています。
(国名があれば名前をチェックし、存在すれば+=、そうでなければ辞書をAddする)

開発環境

OS: Windows10
.NET Core ランタイムのバージョン: .NET SDK (5.0.100) // 3.1でもよい。
最近新規作成したプロジェクトなので、バージョンの制約はないです。

得られる辞書情報

Json

1{ 2 "JP": { 3 "Taro": 11, 4 "Jiro": 5 5 }, 6 "US": { 7 "Mike": 20 8 } 9}

得たい辞書情報

Json

1[ 2 { 3 "Country": "JP", 4 "PersonalData": [ 5 { 6 "Name": "Taro", 7 "Login": 11 8 }, 9 { 10 "Name": "Jiro", 11 "Login": 5 12 } 13 ] 14 }, 15 { 16 "Country": "US", 17 "PersonalData": [ 18 { 19 "Name": "Mike", 20 "Login": 20 21 } 22 ] 23 } 24]

該当のソースコード(元コード)

C#

1using System; 2using System.Collections.Generic; 3 4namespace test 5{ 6 public class Another 7 { 8 public class Human 9 { 10 public string country { get; set; } 11 public string name { get; set; } 12 public uint login { get; set; } 13 } 14 15 public IEnumerable<Human> GetHumans() 16 { 17 yield return new Human 18 { 19 country = "JP", 20 name = "Taro", 21 login = 10, 22 }; 23 yield return new Human 24 { 25 country = "JP", 26 name = "Jiro", 27 login = 5, 28 }; 29 yield return new Human 30 { 31 country = "US", 32 name = "Mike", 33 login = 20, 34 }; 35 yield return new Human 36 { 37 country = "JP", 38 name = "Taro", 39 login = 1, 40 }; 41 } 42 } 43 44 class Program 45 { 46 static void Main(string[] args) 47 { 48 var another = new Another(); 49 var dict = new Dictionary<string, Dictionary<string, uint>>(); 50 foreach (var human in another.GetHumans()) 51 { 52 if (dict.ContainsKey(human.country)) 53 if (dict[human.country].ContainsKey(human.name)) 54 dict[human.country][human.name] += human.login; 55 else 56 dict[human.country].Add(human.name, human.login); 57 else 58 dict.Add( 59 human.country, new Dictionary<string, uint> 60 { 61 [human.name] = human.login, 62 }); 63 } 64 Console.WriteLine(dict); 65 } 66 } 67}

試したこと

Linqを使って、国ごとのグループを作成することは成功したものの、
最後の

Json

1{ 2 "country": "JP", 3 "name": "Taro", 4 "login": 1 5}

が重複しており仕様が満たせないです。

C#

1var dict2 = another.GetHumans().GroupBy(x => x.country).ToDictionary(x => x.Key);

補足情報

現在試している環境: vscodeでdotnet new consoleしたものとなっております。

得られた回答から少し修正を加えたもの

グループ化した結果を最終的な出力にもっていくために、groupedをforeachして
国名のキーチェック、名前のキーチェックをして詰め直す作業をしていますが、
この方法もやはり冗長な気がしていて、Selectで射影する際にToDictionaryできないものかと
試行錯誤をしています。
(.GroupByの後、.Selectして.ToDictinaryする or 単にToDictionaryで結果を得られないだろうか)

C#

1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Text.Json; 5 6namespace test 7{ 8 public class Another 9 { 10 public class Human 11 { 12 public string Country { get; set; } 13 public string Name { get; set; } 14 public uint Login { get; set; } 15 } 16 17 public IEnumerable<Human> GetHumans() 18 { 19 yield return new Human 20 { 21 Country = "JP", 22 Name = "Taro", 23 Login = 10, 24 }; 25 yield return new Human 26 { 27 Country = "JP", 28 Name = "Jiro", 29 Login = 5, 30 }; 31 yield return new Human 32 { 33 Country = "US", 34 Name = "Mike", 35 Login = 20, 36 }; 37 yield return new Human 38 { 39 Country = "JP", 40 Name = "Taro", 41 Login = 1, 42 }; 43 } 44 } 45 46 class Program 47 { 48 static void Main(string[] args) 49 { 50 var another = new Another(); 51 var dict = new Dictionary<string, Dictionary<string, uint>>(); 52 foreach (var human in another.GetHumans()) 53 { 54 if (dict.ContainsKey(human.Country)) 55 if (dict[human.Country].ContainsKey(human.Name)) 56 dict[human.Country][human.Name] += human.Login; 57 else 58 dict[human.Country].Add(human.Name, human.Login); 59 else 60 dict.Add( 61 human.Country, new Dictionary<string, uint> 62 { 63 [human.Name] = human.Login, 64 }); 65 } 66 Console.WriteLine(dict); 67 68 var grouped = another.GetHumans() 69 .GroupBy(h => new { Country = h.Country, Name = h.Name }) 70 .Select(g => new Another.Human 71 { 72 Country = g.Key.Country, 73 Name = g.Key.Name, 74 Login = (uint)g.Sum(x => x.Login) 75 }); 76 var json = JsonSerializer.Serialize(grouped); 77 Console.WriteLine(json); 78 79 var dict2 = new Dictionary<string, Dictionary<string, uint>>(); 80 foreach (var item in grouped) 81 if (dict2.ContainsKey(item.Country)) 82 dict2[item.Country].Add(item.Name, item.Login); 83 else 84 dict2.Add( 85 item.Country, new Dictionary<string, uint> 86 { 87 [item.Name] = item.Login, 88 }); 89 var json2 = JsonSerializer.Serialize(dict2); 90 Console.WriteLine(json2); 91 } 92 } 93}

さらに追記

  • 辞書は外部にてJsonデータとして使えるようにフィールド名を持つこと。
  • AnotherからIEnumerableで1件ずつ来る仕様は変えてはならない。

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

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

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

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

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

退会済みユーザー

退会済みユーザー

2021/03/20 23:51

IEnumerable<Human> を Linq を使って country と name でグループ化して login を合算したいということですか? 開発環境(OS, .NET か Core どっちかとそのバージョンなど)を書いてください。
guest

回答1

0

ベストアンサー

IEnumerable<Human> を Linq を使って country と name でグループ化して login を合算したいということであろうと想像してレスします。想像が違っている場合がどこがどう違うかを書いてください。

Windows 10, Visual Syudio 2019, .NET Framework 4.8 のコンソールアプリで以下のようにして、

using System; using System.Collections.Generic; using System.Linq; namespace ConsoleAppLinq { class Program { static void Main(string[] args) { IEnumerable<Human> human = new List<Human> { new Human{ Country = "JP", Name = "Taro", Login = 10}, new Human{ Country = "JP", Name = "Jiro", Login = 5}, new Human{ Country = "US", Name = "Mike", Login = 20}, new Human{ Country = "JP", Name = "Taro", Login = 1} }; var grouped = human .GroupBy(h => new { Country = h.Country, Name = h.Name }) .Select(g => new Human { Country = g.Key.Country, Name = g.Key.Name, Login = (uint)g.Sum(x => x.Login) }); foreach (var item in grouped) { Console.WriteLine($"Country: {item.Country}, Name: {item.Name}, Login: {item.Login}"); }    } } public class Human { public string Country { get; set; } public string Name { get; set; } public uint Login { get; set; } } }

結果は:

イメージ説明

【追記】

下の 2021/03/21 17:08 の私のコメントで「上の回答で書いたコードの grouped を Country でもう一度グループ化します。後で回答欄に追記しておきます」と書いた件です。上の回答は .NET Framework 4.8 ですが、質問者さんは .NET 5 とのことですので .NET 5 で書きました。

コードの下の方にある Human2, Person クラスに IEnumerable<Human2> として取得しています。それを JSON にシリアライズした結果も書いておきます(2021/03/21 15:11 のコメントのものと同じです)。

using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; namespace ConsoleAppLinq { class Program { static void Main(string[] args) { IEnumerable<Human> human = new List<Human> { new Human{ Country = "JP", Name = "Taro", Login = 10}, new Human{ Country = "JP", Name = "Jiro", Login = 5}, new Human{ Country = "US", Name = "Mike", Login = 20}, new Human{ Country = "JP", Name = "Taro", Login = 1} }; var grouped = human .GroupBy(h => new { Country = h.Country, Name = h.Name }) .Select(g => new Human { Country = g.Key.Country, Name = g.Key.Name, Login = (uint)g.Sum(x => x.Login) }); foreach (var item in grouped) { Console.WriteLine($"Country: {item.Country}, Name: {item.Name}, Login: {item.Login}"); } // ここまでは前回の回答と同じ Console.WriteLine("------------------------------"); var grouped2 = grouped .GroupBy(h => h.Country) .Select(g => new Human2 { Country = g.Key, PersonalData = g.Select(p => new Person { Name = p.Name, Login = p.Login }) }); var json = JsonSerializer.Serialize<IEnumerable<Human2>>(grouped2, new JsonSerializerOptions { WriteIndented = true }); Console.WriteLine(json); } } public class Human { public string Country { get; set; } public string Name { get; set; } public uint Login { get; set; } } public class Human2 { public string Country { get; set; } public IEnumerable<Person> PersonalData { get; set; } } public class Person { public string Name { get; set; } public uint Login { get; set; } } }

結果は:

イメージ説明

投稿2021/03/21 00:44

編集2021/03/21 08:16
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

rtazaki

2021/03/21 04:03

得たい結果については、ご想像の通りです。(Loginが合算された) 最終出力がIEnumerable<Human>のグループではなく、 Dictionary<string, Dictionary<string, uint>>のような入れ子で扱いたいです。
退会済みユーザー

退会済みユーザー

2021/03/21 04:42

> Dictionary<string, Dictionary<string, uint>>のような入れ子で扱いたいです。 そこはまだ途中の話で、最終的には質問に書いてあった以下の JSON 文字列として取得したいのですか? { "JP": { "Taro": 11, "Jiro": 5 }, "US": { "Mike": 20 } } 多分その後 C# のオブジェクトにデシリアライズして使うと想像してますが、その形ではデシリアライズできないのでは?
rtazaki

2021/03/21 06:10

> そこはまだ途中の話で、最終的には質問に書いてあった以下の JSON 文字列として取得したいのですか? 文字列として取得したいというより、Jsonでパース可能なオブジェクトとして扱いたいという感じです。 > 多分その後 C# のオブジェクトにデシリアライズして使うと想像してますが、その形ではデシリアライズできないのでは? C#でデシリアライズせずに、RESTのPOST応答で使う予定です。 (NET.Core Web API の ObjectResultでresponseとしてオブジェクトのまま突っ込むと、 System.Text.Jsonもしくは、NewtonsoftJsonで勝手にシリアライズしてくれるので。) その後、クライアントサイドが受け取ったJsonをどのように使いたいかはわかりませんが、 プリミティブなものであれば、それぞれの言語の適当なJsonパーサーでパースして使えるよね。 という感じです。
退会済みユーザー

退会済みユーザー

2021/03/21 06:12 編集

使える JSON にするなら以下の例のようにしないとダメなのでは? [ { "Country": "JP", "PersonalData": [ { "Name": "Taro", "Login": 11 }, { "Name": "Jiro", "Login": 5 } ] }, { "Country": "US", "PersonalData": [ { "Name": "Mike", "Login": 20 } ] } ]
退会済みユーザー

退会済みユーザー

2021/03/21 06:23

> プリミティブなものであれば、それぞれの言語の適当なJsonパーサーでパースして使えるよね。 { "name":"value"} の name があらかじめわかっていれば使えるかもしれませんが、たぶん US, JP だけではない、Taro, Jiro, Mike だけではなくて不定だと想像してますが、それでも使えますか? name さえ分かれば以下のようにして取得できますが name が分からないとそこで困ってしまいませんか? JSON 文字列から指定した name の value を取得 http://surferonwww.info/BlogEngine/post/2021/02/11/find-value-by-name-in-json-string.aspx
rtazaki

2021/03/21 06:51

おっしゃる通り、nameなしというのは仕様としてまずいのは理解しました。 表現できることと使えることとは意味が異なりますね。 多少ぼかして書いているため、データ構造としてまずい例となってしまいました。
退会済みユーザー

退会済みユーザー

2021/03/21 07:02

上の私の回答で書いた grouped をそのまま JSON 文字列にシリアライズすれば、name はあらかじめ分かっているので C# でも JavaScript でもそれを受け取った側で容易に利用できると思うのですが・・・ もしくは、上の私の 2021/03/21 15:11 のコメントで書いたようにするとか。興味があれば連絡ください。
rtazaki

2021/03/21 07:41

15:11のコメントの例ですと、Countryクラスをグループ化するようなイメージでしょうか。 それとも先にPersonalDataクラスのリストでグループ化するような感じでしょうか。 質問を少しいじります。
退会済みユーザー

退会済みユーザー

2021/03/21 08:08

上の回答で書いたコードの grouped を Country でもう一度グループ化します。後で回答欄に追記しておきます。
rtazaki

2021/03/21 08:32

早速のご回答ありがとうございます。 2回目のグループ化の方法が全く想定できていませんでしたので、大変参考になりました。 ベストアンサーとして、本件解決済みと致します。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問