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

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

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

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

Q&A

解決済

3回答

1784閲覧

c#、foreachでのリストの比較

patton

総合スコア13

C#

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

1グッド

0クリップ

投稿2019/03/15 00:57

前提・実現したいこと

現在ファイルの更新を通知するプログラムを作成しています
10分ごとに該当のフォルダ内のすべてのファイルの名前と更新日時を取得し、listに格納する処理を行い、それを10分前のlistと比較、日次の変更・ファイルの追加・ファイルの消去の3つを検知し、メッセージを出したいと思っています

二つのリストの比較はforeachで行おうと思っているのですが、二つのリストを比較したときに上の3つをそれぞれ検知する条件をどのように設定すればいいでしょうか?

C#

1namespace WindowsFormsApplication6 2{ 3 public partial class Form1 : Form 4 { 5 public Form1() 6 { 7 InitializeComponent(); 8 } 9 10 public class SampleClass 11 { 12 public string filename; 13 public string lasttime; 14 } 15 16 private void button1_Click(object sender, EventArgs e) 17 { 18 List<SampleClass> sampleList = GetFileStateList(); 19 20 WriteXML(sampleList); 21 } 22 23 private void button2_Click(object sender, EventArgs e) 24 { 25 List<SampleClass> sampleList = GetFileStateList(); 26 27 ReadXML(); 28 29 30 } 31 32 private void button3_Click(object sender, EventArgs e) 33 { 34 List<SampleClass> sampleList = GetFileStateList(); 35 36 List<SampleClass> prevSampleList = ReadXML(); 37         38       //ここで比較処理    39 foreach (SampleClass a in prevSampleList) 40 { 41 foreach (SampleClass b in sampleList) 42 { 43 44 45 46 } 47 } 48 } 49 50 51 private List<SampleClass> GetFileStateList() 52 { 53 List<SampleClass> sampleList = new List<SampleClass>(); 54 55 //filesに取得したファイル一覧を格納しlistboxに表示 56 string[] files = Directory.GetFiles(@"C:\Users\TS14-12-077\Desktop\プログラミング大会", "*", SearchOption.AllDirectories); 57 listBox1.Items.AddRange(files); 58 59 foreach (string file in files) 60 { 61 string filetime = Directory.GetLastWriteTime(file).ToString(); 62 63 //ここからシリアライズ処理 64 65 //保存するクラス(SampleClass)のインスタンスを作成 66 SampleClass obj = new SampleClass(); 67 obj.filename = file; 68 obj.lasttime = filetime; 69 70 sampleList.Add(obj); 71 } 72 73 return sampleList; 74 } 75 76 private void WriteXML(List<SampleClass> sampleList) 77 { 78 //保存先のxmlファイル名 79 string fileName = @"C:\Users\TS14-12-077\Desktop\プログラミング大会\sample.xml"; 80 81 //XmlSerializerオブジェクトを作成 82 //オブジェクトの型を指定する 83 System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(List<SampleClass>)); 84 85 86 //書き込むファイルを開く(UTF-8 BOM無し) 87 StreamWriter sw = new StreamWriter(fileName, false, new UTF8Encoding(false)); 88 89 //シリアル化し、XMLファイルに保存する 90 serializer.Serialize(sw, sampleList); 91 92 //ファイルを閉じる 93 sw.Close(); 94 } 95 96 private List<SampleClass> ReadXML() 97 { 98 //保存元のファイル名 99 string fileName = @"C:\Users\TS14-12-077\Desktop\プログラミング大会\sample.xml"; 100 101 //XmlSerializerオブジェクトを作成 102 System.Xml.Serialization.XmlSerializer serializer = 103 new System.Xml.Serialization.XmlSerializer(typeof(List<SampleClass>)); 104 105 106 //読み込むファイルを開く 107 StreamReader sr = new StreamReader( 108 fileName, new UTF8Encoding(false)); 109 110 //XMLファイルから読み込み、逆シリアル化する 111 List<SampleClass> obj = (List<SampleClass>)serializer.Deserialize(sr); 112 113 //ファイルを閉じる 114 sr.Close(); 115 116 return obj; 117 } 118 } 119 120 121} 122 123
bochan2👍を押しています

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

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

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

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

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

guest

回答3

0

Exceptを使うという点はZuishin様と同じなんですが、具体的に。

SampleClassにIEquatable<T>を実装してこう書くと↓

csharp

1var なくなったやつ = prevSampleList.Except(sampleList); 2var 追加されたやつ = sampleList.Except(prevSampleList);

なくなったやつ、追加されたやつ、に時刻がかわったやつが含まれるように思いました。
(もちろん等価性で時刻も判定する前提です)

IEqualityComparerを取ることができるオーバーロードがあるので、これで名前だけ比較するComparerと時刻も見るComparerを使い分けて、

  • prevからsampleを名前だけ見て除く→消えたものが分かる
  • sampleからprevを名前だけ見て除く→増えたものが分かる
  • prevとsampleの名前だけ見たIntersectから、prev(sample)を名前と時刻を見て除く→時刻がかわったものが取れる

という感じでどうでしょうか。


csharp

1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.IO; 5 6namespace ConsoleApp 7{ 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 var targetDir = new DirectoryInfo("./watching"); 13 var fileT0 = targetDir.EnumerateFiles().Select(x=> new SampleClass() {Name = x.FullName, ModifiedAt = x.LastAccessTime }).ToArray(); 14 var fileT1 = targetDir.EnumerateFiles().Select(x => new SampleClass() { Name = x.FullName, ModifiedAt = x.LastAccessTime }).ToArray(); 15 var removed = fileT0.Except(fileT1).ToArray(); 16 var added = fileT1.Except(fileT0).ToArray(); 17 var modified = fileT0.Intersect(fileT1).Except(fileT1,new SampleClassEqualityComparer()).ToArray(); 18 Console.ReadKey(); 19 } 20 } 21 class SampleClass : IEquatable<SampleClass> 22 { 23 public string Name { get; set; } = string.Empty; 24 public DateTime ModifiedAt { get; set; } = default(DateTime); 25 26 public override bool Equals(object obj) 27 { 28 return Equals(obj as SampleClass); 29 } 30 31 public bool Equals(SampleClass other) 32 { 33 return other != null && 34 Name == other.Name; 35 } 36 37 public override int GetHashCode() 38 { 39 return 539060726 + EqualityComparer<string>.Default.GetHashCode(Name); 40 } 41 } 42 class SampleClassEqualityComparer : IEqualityComparer<SampleClass> 43 { 44 public bool Equals(SampleClass s1, SampleClass s2) 45 { 46 if (s2 == null && s1 == null) 47 return true; 48 if (s1 == null || s2 == null) 49 return false; 50 if (s1.Name == s2.Name && s1.ModifiedAt == s2.ModifiedAt) 51 return true; 52 return false; 53 } 54 55 public int GetHashCode(SampleClass s) 56 { 57 var hashCode = -22583235; 58 hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(s.Name); 59 hashCode = hashCode * -1521134295 + s.ModifiedAt.GetHashCode(); 60 return hashCode; 61 } 62 } 63 64}

投稿2019/03/15 01:22

編集2019/03/15 07:31
papinianus

総合スコア12705

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

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

patton

2019/03/15 02:59

IEquatable<T>の実装ってどうやるのでしょうか?
papinianus

2019/03/15 07:30

追記しました(IEquatableは↑のとおりVSの機能で作ってます) 前・後のところで丁寧にファイルを作るテストまで書いてないです。その辺ごめんなさい。
guest

0

ベストアンサー

この場合 LINQ が簡単だと思います。

Enumerable.Except Method を使えば、一方のリストに含まれているが他方には無い項目が取得できます。
これをファイル名を対象に二回使えば追加されたファイルと削除されたファイルがわかります。

変更は Dictionary<TKey,TValue> Class を使うと良いでしょう。まずは片方のリストを、ファイル名をキーに辞書に入れ、次のリストに含まれる項目の一つ一つに対してその辞書に同じファイル名の項目が入っているかどうかを調べます。入っていた場合にはその二つの項目を比較し、更新日時が違えば変更されたことがわかります。

追記

こんな感じになります。

C#

1using System; 2using System.Collections.Generic; 3using System.IO; 4using System.Linq; 5 6namespace Etude 7{ 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 string fileMask = "*.txt"; 13 foreach (var file in Directory.GetFiles(".", fileMask)) 14 { 15 File.Delete(file); 16 } 17 18 File.Create("a.txt").Close(); 19 File.Create("b.txt").Close(); 20 File.Create("c.txt").Close(); 21 var dir = new DirectoryInfo("."); 22 var before = dir 23 .GetFiles(fileMask) 24 .ToDictionary(a => a.FullName); 25 Dump(nameof(before), before.Keys); 26 27 File.Create("d.txt").Close(); 28 File.Delete("b.txt"); 29 File.Delete("c.txt"); 30 File.Create("c.txt").Close(); 31 var after = dir 32 .GetFiles(fileMask) 33 .ToDictionary(a => a.FullName); 34 Dump(nameof(after), after.Keys); 35 36 var added = after 37 .Keys 38 .Except(before.Keys) 39 .Select(a => after[a]) 40 .ToArray(); 41 Dump(nameof(added), added.Select(a => a.FullName)); 42 43 var removed = before 44 .Keys 45 .Except(after.Keys) 46 .Select(a => before[a]) 47 .ToArray(); 48 Dump(nameof(removed), removed.Select(a => a.FullName)); 49 50 var changed = before 51 .Keys 52 .Where(a => after.ContainsKey(a)) 53 .Where(a => after[a].LastWriteTime != before[a].LastWriteTime) 54 .ToArray(); 55 Dump(nameof(changed), changed); 56 57 Console.ReadKey(); 58 } 59 60 static void Dump(string title, IEnumerable<string> files) 61 { 62 Console.WriteLine($"[{title}]"); 63 foreach (var file in files) 64 { 65 Console.WriteLine(file); 66 } 67 Console.WriteLine(); 68 } 69 } 70}

投稿2019/03/15 01:11

編集2019/03/15 03:33
Zuishin

総合スコア28660

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

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

Zuishin

2019/03/15 01:12

LINQ を使わない場合は、List<T>.Contains でそのリストに項目が含まれているかどうかわかりますから、含まれていないものを別のリストに保存することで、含まれていないものリストが作成できます。
patton

2019/03/15 01:22

解答ありがとうございます Enumerable.Except Methodを2回使うというのがどういうことなのかよくわからないです
papinianus

2019/03/15 01:26 編集

2回の件は私の回答にあるように、prev側からsampleを引く、と、sample側からprevを引く、の2回だと思います。 この回答の方針を取るなら、最初から、辞書にしているはずなので、prev/sampleは、Dictionary<string, SampleClass>の方になり、Exceptは `prev.Keys.Exept(sample.Keys);sample.Keys.Exept(prev.Keys);` のような実装になると思われます
Zuishin

2019/03/15 03:56

補足ありがとうございます。コードを書きました。
patton

2019/03/15 05:44

コードの追記ありがとうございます コードの内容がいまいちわからないのでどのような処理を行っているのか教えていただけないでしょうか?
Zuishin

2019/03/15 05:47

まずわかるところまで説明してみてください。
Zuishin

2019/03/15 06:07

前半に書いた通りのことをしているだけです。 a.txt, b.txt, c.txt を作って before に入れ、b.txt を削除し、c.txt を書き換え、d.txt を追加して after に入れ、それを比較して追加されたものを add 削除されたものを removed 変更されたものを changed にそれぞれ入れています。 Dump はそれらを表示するメソッドです。
Zuishin

2019/03/18 05:37

あのね。 あなたが質問したあなたの仕事でしょ? 完全動作するコード貰ったんでしょ? 何の対価も払ってないんでしょ? 一から全部説明しろって何様なんだ? わからないところは説明してやるからどこまでわかってるかくらい書けよ。
patton

2019/03/18 07:56

返信遅れて申し訳ありません コードありがとうございました もらったコードをどう自分のプログラムに組み込めばいいかが分からないので質問しました 申し訳ありませんでした
Zuishin

2019/03/18 08:02

どこからどこまでわかってるかこっちにはわかんないから、全部説明しろって言われても無理でしょ。 「using とは」からやんの? 「Main とは」は? わかんないことは具体的に聞かなきゃどうにもならないでしょ。 あなたの親じゃなくて知らない人なんだから。 あと複数アカウントは規約違反だから。
patton

2019/03/18 08:25

まずtxtファイルをなぜ作るのかからわからないです すいません
papinianus

2019/03/18 08:28

横からですが、通知が来たので。 ファイルを作ったり消したりしているのは、動作検証のためです。サンプルでは10分まっていませんので、私のコードのようなことをしてしまうと差分が発生しません。動作検証がやりづらいです。毎回必ず、全ファイルを消して、何度デバッグ実行しても、確認が行えるように、という配慮です。
patton

2019/03/18 08:30

わかりました ありがとうございます
Zuishin

2019/03/18 09:14

papinianus さん、解説ありがとうございます。
guest

0

他の回答者様と異なる方法、つまり一回のスキャンで処理を行う方法であれば、一度両方のコレクションをファイル名でソートし、古いファイル用と新しいファイル用の2つのループ変数を用いてループで比較していけば実現できるかと思います。

例えば比較はファイル名が同じかどうかでしていくことになるかと思いますが、異なる場合、古い方のファイル名が
位置的に上であればそれは削除されたということですし、下であれば追加されたということになります(当たり前ですが位置の比較はソートで使用している方法と合わせてください)。
削除されていた場合は古いファイル用のループ変数のみ更新し、追加されている場合は新しいファイル用のループ変数のみ更新し、continueします。
その後ファイル名が同じという保証ができれば日時の比較を行い、両方のループ変数を更新します。
またこの方法の場合、ループ変数がコレクションの範囲外かどうかもチェックする必要があります。例えば先頭で確認した場合、両方が範囲外であれば処理終了。古いファイル用のループ変数が範囲外であれば残りの新しいファイルのコレクションは全て追加されたものになります。新しいファイル用のループ変数が範囲外であれば残りの古いファイルのコレクションは全て削除されたものとなります。

測定していませんし、どれくらいの規模かもわかりませんので、これでどの程度処理速度が変わるのかは不明ですが、見やすさ優先であれば他の回答者様のものを利用するのが良いかと。

投稿2019/03/16 11:42

arisuke

総合スコア13

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問