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

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

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

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

Q&A

解決済

2回答

627閲覧

クラスリストを比較して増減、差異があるか確認したい

omochi-_-

総合スコア3

C#

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

1グッド

0クリップ

投稿2022/07/30 10:52

編集2022/07/31 13:27

C#で以前取得したデータとの差異を確認するプログラムを作ろうとしていて、やり方が分からないので教えてほしいです。

DATAが新しく増えたり消えているか、RECOADに変化があるかを判定したいです。

foreachで多重ループを作って一件ずつ==で確認して、elseで書き出すにしようと思ったんですけど関係ないデータまで含まれます。

調べてもこれだ!と言う情報が見つからなかったので質問しました、解凍お願いします。

ソースコード

C#

1 2using System.Text.Json;//追記2 JsonをStoreClsでデシリアライズして使います 3 4//LIST<StoreCls> NEW; 5//LIST<StoreCls> OLD; 6 7public class StoreCls 8 { 9 public string DATE { get; set; } 10 public Store[] DATA { get; set; } 11 } 12 13 public class Store 14 { 15 public string ID { get; set; } 16 public string NAME { get; set; } 17 public string RECORD { get; set; }//追記2 数字1~ の数値 18 } 19 20//追記 21     var Flag = 0; 22 string TenpName = ""; 23 string TenpRECORD = ""; 24 foreach (var oldDATA in LastTimeStoreList) 25 { 26 foreach (var oldStore in oldDATA.DATA) 27 { 28 foreach (var CurrentData in StoreLIST) 29 { 30 31 foreach (var CurrentStore in CurrentData.DATA) 32 { 33 Flag = 0; 34 if (CurrentStore.NAME == oldStore.NAME) 35 { 36 Flag = 0; 37 if (!(CurrentStore.RECORD == oldStore.RECORD)) 38 { 39 StoreInfo += CurrentStore.NAME + "\r\n" + oldStore.RECORD + CurrentStore.RECORD; 40 41 } 42 break; 43 } 44 else 45 { 46 Flag = 1; 47 TenpName = CurrentStore.NAME; 48 49 TenpRECORD = CurrentStore.RECORD; 50 continue; 51 } 52 } 53 if (Flag == 1) 54 { 55 StoreInfo += TenpTname + "\r\n" + oldStore.RECORD + TenpRECORD; 56 } 57 58 } 59 60 } 61 }
TN8001👍を押しています

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

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

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

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

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

fumu7

2022/07/30 11:11

「foreachで多重ループを作って一件ずつ==で確認して、elseで書き出すにしようと思った」に対応するプログラムを質問に書いてください。 ”ソースコード”には、foreachも、多重ループも、含まれていないですよね。
omochi-_-

2022/07/30 11:33

すみません、書き忘れていました。今追加しました。
退会済みユーザー

退会済みユーザー

2022/07/30 23:13

あなたの言う「差異」とは何ですか? 具体的に、何がどう違うと「差異」があると見なすのですか? 元データは何ですか? MySQL とか SQL Server などの DB ですか?
Zuishin

2022/07/30 23:44

「違う」ということだけわかればいいのか、それともレーベンシュタイン距離が必要なのか、それとも他の何かがしたいのかわかりませんが、もしかすると多重ループではなく HashSet を使うのが良いかもしれません。
KOZ6.0

2022/07/31 11:40 編集

回答に転記しました。
KOZ6.0

2022/07/31 11:40 編集

回答に転記しました。
omochi-_-

2022/07/31 13:24

>>元データは? Jsonです >>差異とは? (これも書き忘れていました)RECOADは数字のstringで入っているのでその増減 DATA(store)はその要素があるかないか、IDで確認して 新しい→古い で比較して消えているか 古い→新しい で比較して古いほうになかったものが増えているかの差異です。 >>HashSet HashSet知らなかったので調べてきました、同じ要素が入らないならどの位置で起きているかを調べると出来そうですねありがとうございます!
guest

回答2

0

ベストアンサー

DATAが新しく増えたり消えているか、RECOADに変化があるかを判定したいです。

Storeの増減・変更だけわかればいい」と考えていいのでしょうか?(「StoreClsの増減やStoreの移動はないのか。あったとしてもStoreだけ気にしていればいいのか」ということです)

であるならStoreをフラット化して、Exceptするのが簡単そうです。
Enumerable.Except メソッド (System.Linq) | Microsoft Docs

命名が分かりにくいので、ありがちなクラスに置き換えさせていただきました(.NET6です^^

cs

1using static System.Text.Json.JsonSerializer; 2 3 4var oldDepartments = new List<Department> 5{ 6 new Department 7 { 8 Name = "foo", 9 Employees = new List<Employee> 10 { 11 new Employee { Name = "hoge", Age = 65, }, 12 new Employee { Name = "fuga", Age = 41, }, 13 } 14 }, 15 new Department 16 { 17 Name = "bar", 18 Employees = new List<Employee> 19 { 20 new Employee { Name = "piyo", Age = 37, }, 21 } 22 }, 23}; 24 25var newDepartments = Deserialize<List<Department>>(Serialize(oldDepartments))!; // deep copy 26newDepartments[1].Employees[0].Age++; // change piyo 27newDepartments[0].Employees.RemoveAt(0); // del hoge 28newDepartments[0].Employees.Add(new Employee { Name = "hogera", Age = 24, }); // add hogera 29 30 31var oldEmployees = oldDepartments.SelectMany(x => x.Employees).ToList(); 32var newEmployees = newDepartments.SelectMany(x => x.Employees).ToList(); 33var message = ""; 34 35foreach (var oldEmployee in oldEmployees) 36{ 37 foreach (var newEmployee in newEmployees) 38 { 39 if (oldEmployee.Name == newEmployee.Name && oldEmployee.Age != newEmployee.Age) 40 { 41 message += $"change:\t{newEmployee.Name} {oldEmployee.Age} -> {newEmployee.Age}\n"; 42 break; 43 } 44 } 45} 46 47var delEmployees = oldEmployees.Except(newEmployees, new NameComparer()); 48foreach (var delEmployee in delEmployees) 49{ 50 message += $"del:\t{delEmployee.Name} {delEmployee.Age}\n"; 51} 52 53var addEmployees = newEmployees.Except(oldEmployees, new NameComparer()); 54foreach (var addEmployee in addEmployees) 55{ 56 message += $"add:\t{addEmployee.Name} {addEmployee.Age}\n"; 57} 58 59Console.WriteLine(message); 60 61 62 63class NameComparer : IEqualityComparer<Employee> 64{ 65 //public bool Equals(Employee? x, Employee? y) => x?.Name == y?.Name; 66 public bool Equals(Employee? x, Employee? y) 67 { 68 return x?.Name == y?.Name; 69 } 70 71 //public int GetHashCode(Employee obj) => obj.Name?.GetHashCode() ?? 0; 72 public int GetHashCode(Employee obj) 73 { 74 if (obj.Name == null) return 0; 75 return obj.Name.GetHashCode(); 76 } 77} 78 79#pragma warning disable CS8618 // null 非許容のフィールドには、コンストラクターの終了時に null 以外の値が入っていなければなりません。Null 許容として宣言することをご検討ください。 80public class Department 81{ 82 public string Name { get; set; } 83 public List<Employee> Employees { get; set; } 84} 85 86public class Employee 87{ 88 public string Name { get; set; } 89 public int Age { get; set; } 90}
change: piyo 37 -> 38 del: hoge 65 add: hogera 24

投稿2022/07/31 12:37

編集2022/08/01 16:58
TN8001

総合スコア9317

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

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

omochi-_-

2022/07/31 14:06

回答ありがとうございます。 >>「Storeの増減・変更だけわかればいい」と考えていい まさにこれです SelectManyでフラット化(っていうんですね知りませんでした) わざわざ外から確認しないでフラット化していれば簡単ですもんね・・・。 集合演算もできるんですね、LINQは便利とよく見かける理由が分かりました。 出力も含めて求めてた答えそのものでしたありがとうございます!m(__)m。 new NameComparer()は構造体などを使う時用?の構造を指定している?で合ってますか? .Nameしか指定していないのに.Ageも比較されるのでしょうか? リファレンスを見たんですけど、ラムダ式がまだできないのもあって理解しきれてないので教えてほしいです。
TN8001

2022/08/01 17:09 編集

> new NameComparer()は構造体などを使う時用?の構造を指定している?で合ってますか? Enumerable.Exceptメソッドはリストだけを渡すと、既定の等値比較をします。 ただのクラスの場合は、同一オブジェクトかどうかの比較になります(ReferenceEquals) KOZ6.0さんの回答のようにそれをカスタマイズすることもできます。 Exceptメソッドに比較方法を渡すと、元のクラスはいじらずにカスタマイズした比較が可能になります。 それがNameComparerです。 > .Nameしか指定していないのに.Ageも比較されるのでしょうか? いいえ。名前の通りNameだけの比較です。 IdがすべてのStoreでユニークならば、Idでの比較のほうがいいでしょう。あるいはIdとName両方使ってもいいでしょう(その辺ちょっとわからなかったのでIdは省略しましたが) Employee(Store)の変更は前段の2重ループで見ています。 Age(RECORD)が変わっていれば、ここで検出します。 Employee(Store)の増減をExceptで調べています。 Age(RECORD)が変わっていてもそこには含まれて欲しくないので、Nameだけの比較をしているわけです(Ageも含んでしまったらchange:add:del:全部に出てしまいます)
omochi-_-

2022/08/07 11:32

遅くなりましたが、解説ありがとうございます。 分からなかったところが分かりました!ありがとうございました
guest

0

まずは、比較を簡単にするために、Store クラスと StoreCls クラスの演算子をオーバーロードしましょう。

「自作クラスの演算子をオーバーロードする」
https://dobon.net/vb/dotnet/beginner/operator.html

C#

1public class Store : IEquatable<Store> 2{ 3 public string ID { get; set; } 4 public string NAME { get; set; } 5 public string RECORD { get; set; } 6 7 public override bool Equals(object obj) { 8 Store other = obj as Store; 9 if (other == null) return false; 10 return Equals(other); 11 } 12 13 public override int GetHashCode() { 14 return ToString().GetHashCode(); 15 } 16 17 public override string ToString() { 18 return $"ID={ID},NAME={NAME},RECORD={RECORD}"; 19 } 20 21 public bool Equals(Store other) { 22 return this == other; 23 } 24 25 public static bool operator ==(Store a, Store b) { 26 if (ReferenceEquals(a, b)) return true; 27 if (a is null || b is null) return false; 28 return a.ID == b.ID && a.NAME == b.NAME && a.RECORD == b.RECORD; 29 } 30 31 public static bool operator !=(Store a, Store b) { 32 return !(a == b); 33 } 34} 35 36public class StoreCls : IEquatable<StoreCls> 37{ 38 public string DATE { get; set; } 39 public Store[] DATA { get; set; } 40 41 public override bool Equals(object obj) { 42 StoreCls other = obj as StoreCls; 43 if (other == null) return false; 44 return Equals(other); 45 } 46 47 public override int GetHashCode() { 48 return ToString().GetHashCode(); 49 } 50 51 public override string ToString() { 52 return $"DATE={DATE},DATA.Length={DATA.Length}"; 53 } 54 55 public bool Equals(StoreCls other) { 56 return this == other; 57 } 58 59 public static bool operator ==(StoreCls a, StoreCls b) { 60 if (ReferenceEquals(a, b)) return true; 61 if (a is null || b is null) return false; 62 if (a.DATE != b.DATE) return false; 63 if (ReferenceEquals(a.DATA, b.DATA)) return true; 64 if (a.DATA == null || b.DATA == null) return false; 65 if (a.DATA.Length != b.DATA.Length) return false; 66 for (int i = 0; i < b.DATA.Length; i++) { 67 if (a.DATA[i] != b.DATA[i]) return false; 68 } 69 return true; 70 } 71 72 public static bool operator !=(StoreCls a, StoreCls b) { 73 return !(a == b); 74 } 75}

こうしておけば、StoreCls のリストが一致しているかどうかは次のように書けます。

C#

1public static bool IsSameData(List<StoreCls> stores1, List<StoreCls> stores2) { 2 if (stores1.Count != stores2.Count) return false; 3 for (int i = 0; i < stores1.Count; i++) { 4 if (stores1[i] != stores2[i]) return false; 5 } 6 return true; 7}

次に

DATAが新しく増えたり消えているか、RECOADに変化があるかを判定したいです。

ということですが、これはとても難しいです。

というのは、先頭から比較していって、違うものがあった場合、その違いが削除されたことによるものか、追加されたことによるものかは、その後のデータを見ていくしかないからです。
「変更された」という判断はほぼ不可能です。(DELETE されて INSERT されたと判断するしかない)

diff という差分を求めるツールがあるので、そのロジックを参考にしてください。

Google が C# のソースを公開しているので、これを参考にすればできるかもしれません。
https://github.com/google/diff-match-patch/blob/master/csharp/DiffMatchPatch.cs

string は、char[] とほぼ等しいので、StoreCls[] と同じようなクラスを作って string と置き換えればイケるかも。

投稿2022/07/31 11:39

KOZ6.0

総合スコア2626

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

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

omochi-_-

2022/07/31 13:13

回答ありがとうございます! 演算子もオーバーロードすることができるんですね・・・今まで知りませんでした。 (配列自体の比較まで・・・) インスタンス自体の比較も知りませんでした。 リンク先のdiff-match-patchを調べてみました、文字列のどちらかにしかない、両方にあるを求められるんですね。 >>先頭から比較していって、違うものがあった場合 なるほど・・・確かに順番が変わってしまったら元々あるのかわからないですもんね・・・ 色々知らないことが分かりましたありがとうございます!。
KOZ6.0

2022/07/31 13:49

>なるほど・・・確かに順番が変わってしまったら元々あるのかわからないですもんね・・・ 並び替えが発生するんですか? キーとなる項目があるのであれば、その順に並べてしまえば追加/削除/変更の検出は容易になります。
omochi-_-

2022/07/31 14:14

.DATAの真ん中が抜ければ並びが変わると持ってたんですけど >キーとなる項目があるのであれば IDがキーです そのIDを持つ要素があるか、IDを持つ要素同士のRECOAD==で比較もできますね、一番思いつきそうなのに全然考えてませんでした・・・ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問