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

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

ただいまの
回答率

90.51%

  • C#

    8507questions

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

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

解決済

回答 3

投稿

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

patton

score 1

前提・実現したいこと

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

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

namespace WindowsFormsApplication6
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public class SampleClass
        {
            public string filename;
            public string lasttime;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            List<SampleClass> sampleList = GetFileStateList();

            WriteXML(sampleList);
        }

        private void button2_Click(object sender, EventArgs e)
        {
            List<SampleClass> sampleList = GetFileStateList();

            ReadXML();


        }

        private void button3_Click(object sender, EventArgs e)
        {
            List<SampleClass> sampleList = GetFileStateList();

            List<SampleClass> prevSampleList = ReadXML();
        
       //ここで比較処理   
            foreach (SampleClass a in prevSampleList)
            {
                foreach (SampleClass b in sampleList)
                {



                }
            }
        }


        private List<SampleClass> GetFileStateList()
        {
            List<SampleClass> sampleList = new List<SampleClass>();

            //filesに取得したファイル一覧を格納しlistboxに表示
            string[] files = Directory.GetFiles(@"C:\Users\TS14-12-077\Desktop\プログラミング大会", "*", SearchOption.AllDirectories);
            listBox1.Items.AddRange(files);

            foreach (string file in files)
            {
                string filetime = Directory.GetLastWriteTime(file).ToString();

                //ここからシリアライズ処理

                //保存するクラス(SampleClass)のインスタンスを作成
                SampleClass obj = new SampleClass();
                obj.filename = file;
                obj.lasttime = filetime;

                sampleList.Add(obj);
            }

            return sampleList;
        }

        private void WriteXML(List<SampleClass> sampleList)
        {
            //保存先のxmlファイル名
            string fileName = @"C:\Users\TS14-12-077\Desktop\プログラミング大会\sample.xml";

            //XmlSerializerオブジェクトを作成
            //オブジェクトの型を指定する
            System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(List<SampleClass>));


            //書き込むファイルを開く(UTF-8 BOM無し)
            StreamWriter sw = new StreamWriter(fileName, false, new UTF8Encoding(false));

            //シリアル化し、XMLファイルに保存する
            serializer.Serialize(sw, sampleList);

            //ファイルを閉じる
            sw.Close();
        }

        private List<SampleClass> ReadXML()
        {
            //保存元のファイル名
            string fileName = @"C:\Users\TS14-12-077\Desktop\プログラミング大会\sample.xml";

            //XmlSerializerオブジェクトを作成
            System.Xml.Serialization.XmlSerializer serializer =
                new System.Xml.Serialization.XmlSerializer(typeof(List<SampleClass>));


            //読み込むファイルを開く
            StreamReader sr = new StreamReader(
                fileName, new UTF8Encoding(false));

            //XMLファイルから読み込み、逆シリアル化する
            List<SampleClass> obj = (List<SampleClass>)serializer.Deserialize(sr);

            //ファイルを閉じる
            sr.Close();

            return obj;
        }
    }


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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 3

checkベストアンサー

+3

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

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

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

追記

こんな感じになります。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Etude
{
    class Program
    {
        static void Main(string[] args)
        {
            string fileMask = "*.txt";
            foreach (var file in Directory.GetFiles(".", fileMask))
            {
                File.Delete(file);
            }

            File.Create("a.txt").Close();
            File.Create("b.txt").Close();
            File.Create("c.txt").Close();
            var dir = new DirectoryInfo(".");
            var before = dir
                .GetFiles(fileMask)
                .ToDictionary(a => a.FullName);
            Dump(nameof(before), before.Keys);

            File.Create("d.txt").Close();
            File.Delete("b.txt");
            File.Delete("c.txt");
            File.Create("c.txt").Close();
            var after = dir
                .GetFiles(fileMask)
                .ToDictionary(a => a.FullName);
            Dump(nameof(after), after.Keys);

            var added = after
                .Keys
                .Except(before.Keys)
                .Select(a => after[a])
                .ToArray();
            Dump(nameof(added), added.Select(a => a.FullName));

            var removed = before
                .Keys
                .Except(after.Keys)
                .Select(a => before[a])
                .ToArray();
            Dump(nameof(removed), removed.Select(a => a.FullName));

            var changed = before
                .Keys
                .Where(a => after.ContainsKey(a))
                .Where(a => after[a].LastWriteTime != before[a].LastWriteTime)
                .ToArray();
            Dump(nameof(changed), changed);

            Console.ReadKey();
        }

        static void Dump(string title, IEnumerable<string> files)
        {
            Console.WriteLine($"[{title}]");
            foreach (var file in files)
            {
                Console.WriteLine(file);
            }
            Console.WriteLine();
        }
    }
}

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/03/15 10:12

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

    キャンセル

  • 2019/03/15 10:22

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

    キャンセル

  • 2019/03/15 10:23 編集

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

    キャンセル

  • 2019/03/15 12:56

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

    キャンセル

  • 2019/03/15 14:44

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

    キャンセル

  • 2019/03/15 14:47

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

    キャンセル

  • 2019/03/15 15:07

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

    キャンセル

  • 2019/03/18 14:37

    あのね。

    あなたが質問したあなたの仕事でしょ?
    完全動作するコード貰ったんでしょ?
    何の対価も払ってないんでしょ?

    一から全部説明しろって何様なんだ?
    わからないところは説明してやるからどこまでわかってるかくらい書けよ。

    キャンセル

  • 2019/03/18 16:56

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

    キャンセル

  • 2019/03/18 17:02

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

    あと複数アカウントは規約違反だから。

    キャンセル

  • 2019/03/18 17:25

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

    キャンセル

  • 2019/03/18 17:28

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

    キャンセル

  • 2019/03/18 17:30

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

    キャンセル

  • 2019/03/18 18:14

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

    キャンセル

+3

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

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

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


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

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

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

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


using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var targetDir = new DirectoryInfo("./watching");
            var fileT0 = targetDir.EnumerateFiles().Select(x=> new SampleClass() {Name = x.FullName, ModifiedAt = x.LastAccessTime }).ToArray();
            var fileT1 = targetDir.EnumerateFiles().Select(x => new SampleClass() { Name = x.FullName, ModifiedAt = x.LastAccessTime }).ToArray();
            var removed = fileT0.Except(fileT1).ToArray();
            var added = fileT1.Except(fileT0).ToArray();
            var modified = fileT0.Intersect(fileT1).Except(fileT1,new SampleClassEqualityComparer()).ToArray();
            Console.ReadKey();
        }
    }
    class SampleClass : IEquatable<SampleClass>
    {
        public string Name { get; set; } = string.Empty;
        public DateTime ModifiedAt { get; set; } = default(DateTime);

        public override bool Equals(object obj)
        {
            return Equals(obj as SampleClass);
        }

        public bool Equals(SampleClass other)
        {
            return other != null &&
                   Name == other.Name;
        }

        public override int GetHashCode()
        {
            return 539060726 + EqualityComparer<string>.Default.GetHashCode(Name);
        }
    }
    class SampleClassEqualityComparer : IEqualityComparer<SampleClass>
    {
        public bool Equals(SampleClass s1, SampleClass s2)
        {
            if (s2 == null && s1 == null)
                return true;
            if (s1 == null || s2 == null)
                return false;
            if (s1.Name == s2.Name && s1.ModifiedAt == s2.ModifiedAt)
                return true;
                return false;
        }

        public int GetHashCode(SampleClass s)
        {
            var hashCode = -22583235;
            hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(s.Name);
            hashCode = hashCode * -1521134295 + s.ModifiedAt.GetHashCode();
            return hashCode;
        }
    }

}

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/03/15 11:59

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

    キャンセル

  • 2019/03/15 15:26

    画面の操作については、MS Docの下記をごらんください(コードを書く必要はないです)
    https://docs.microsoft.com/ja-jp/visualstudio/ide/reference/generate-equals-gethashcode-methods?view=vs-2017

    繰り返しになっちゃいますが、IEqutable<T>をやっても、時刻を見る・見ないの使い分けが必要なんで。

    キャンセル

  • 2019/03/15 16:30

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

    キャンセル

0

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

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

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

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

  • C#

    8507questions

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