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

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

ただいまの
回答率

90.52%

  • C#

    7093questions

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

  • アルゴリズム

    408questions

    アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

ベスト3を更新するロジック

受付中

回答 4

投稿 編集

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

tkmnusr

score 162

 前提・実現したいこと

フォルダ内のサイズを計算するコードを組んでいるのですが、フォルダ内を回している際、
パス名の長いファイル、フォルダのパス名それぞれのベスト3も格納したいと思っています。
もしかしたら、いろいろなやり方があるかもしれませんが、短いコードで書きたいです。
ご教示のほどお願い致します。

 試したこと

ベスト1だけを格納したい場合は、単純なif文で実装できました。
しかし、これがファイル、フォルダのそれぞれベスト3を格納するとなると、
どのように実装していいかわかりません。

        static string longfile = "";
        static string longdir = "";

        public static long GetDirectorySize(DirectoryInfo dirInfo)
        {
            long size = 0;

            //フォルダ内の全ファイルの合計サイズを計算する。
            foreach (FileInfo fi in dirInfo.GetFiles())
            {
                size += fi.Length;

                if (fi.FullName.Length > longfile.Length)
                {
                    longfile = fi.FullName;
                }
            }

            //サブフォルダのサイズを合計していく。
            foreach (DirectoryInfo di in dirInfo.GetDirectories())
            {
                size += GetDirectorySize(di);

                if (di.FullName.Length > longdir.Length)
                {
                    longdir = di.FullName;
                }
            }

            //結果を返す
            return size;
        }

 追記

フォルダを指定してエラーが出た件ですが、なんとなく原因がわかりました。
そのフォルダのパス名を短くしたら、表示される容量が変わったので、
そのフォルダ内に長すぎるファイルやパスがあるのが原因だと思います。

このような長いパスを知る為に、longfile.ElementAt(0)~longfile.ElementAt(3)で出力したかったのですが、
これはエラーが出てしまうから無理なのでしょうか?
不思議なのは、longfile.ElementAt(0)~longfile.ElementAt(3)をコメントアウトして、
long dirsize = GetDirectorySize(new DirectoryInfo(@"C:\〇〇\〇〇"));
のコードだけならば、例のフォルダを指定してもエラーにならない所です。

 追記②

papinianus様のご指示に従って、設計を変えてみましたが、
filelist.OrderByDescending(s => s.FullName.Length).Take(3);
のコードで赤線が付いてエラーになります。
ご教示のほどお願い致します。

        private void Form1_Load(object sender, EventArgs e)
        {
            Console.WriteLine("test");
            //long dirsize = GetDirectorySize(di);
            List<FileInfo> filelist = MyDirectoryInfo(@"C:\Users\〇〇\Desktop\My Documents");

       //下記でエラーになります。長いパス名ベスト3を取得したいです。
            IOrderedEnumerable<string> longlist = filelist.OrderByDescending(s => s.FullName.Length).Take(3);
        }

        public static List<FileInfo> MyDirectoryInfo(string dirpath)
        {
            DirectoryInfo di = new DirectoryInfo(dirpath);
            List<FileInfo> filelist = new List<FileInfo>();
            GetDirectoryInfo(di, ref filelist);
            return filelist;
        }

        public static void GetDirectoryInfo(DirectoryInfo dirInfo, ref List<FileInfo> list)
        {
            //long size = 0;

            //フォルダ内の全ファイルの合計サイズを計算する。
            foreach (FileInfo fi in dirInfo.GetFiles())
            {
                list.Add(fi);
                //size += fi.Length;
                //longfile = ((longfile.Concat(new[] { fi.FullName })).OrderByDescending(s => s.Length)).Take(3);
            }

            //サブフォルダのサイズを合計していく。
            foreach (DirectoryInfo di in dirInfo.GetDirectories())
            {
                GetDirectoryInfo(di, ref list);
                //size += GetDirectorySize(di);
            }

            //結果を返す
            //return size;
        }
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 4

+1

あらかじめディレクトリを走査してデータを取得し、リストに格納してください。
その後、ベスト 3 を求めたいそれぞれのデータについてリストを降順ソートし、先頭の三つを求めてください。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

+1

一旦コンテナ(LinkedList<T>かなんか)に全部突っ込んでおいて、
「最大のを見つけてコンテナから削除」を3回やる。

要素数が大きければ「全部sortして頭から3つ」より速いハズ。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/07/20 21:56

    そうですね。
    それも思いましたが、短いコードで書きたいという条件から外れる(LINQ に比べて)のと、ファイルなのでそれ程大きな要素数にはならないんじゃないかというのを思ってソートを選びました。

    キャンセル

  • 2018/07/20 21:58

    C++なら partial_sort でイッパツなんですけどねー...

    キャンセル

0

色々やり方があります。
案1:降順にソートして先頭の3つを見る
案2:一番を探し、それを取り除いて、残りの中の一番を探してそれが二番、以下同じ
案3:一番用、二番用、三番用に3つの変数を作って、順番にずらしていく

案3
if( x>top3 ) { top3=x; }
if( x>top2 ) { top3=top2; top2=x; }
if( x>top1 ) { top2=top1; top1=x; }


(見やすいため並べましたが、実際には1つ目が偽なら以降も偽なので、ネストすることになるかと)
案2と案3は、ソート処理の必要最小限部分だけを自分で実行することに相当します。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

0

一般論としてはZuishinさんのおっしゃるようにsomelist.OrderByDescending(s=>s.Length).Take(3);だと思います。

質問者様のコードですと、ifですっげーがんばる(原理的には今のコードのlongfileをlongest, secondlongest, thirdlongestにしてifをめっちゃがんばる)、たとえば

var tmp = fi.FullName;
if(tmp.Length > third.Length) { third = tmp; }
if(third.Length > second.Length) { tmp = second; second = third; third = tmp; }
//longestも↑と同じことやる、とかね。


もしくは

static IEnumerable<string> longfile = Enumerable.Reapeat("", 3);
//中略。以下はifブロックをけして、そこに書きます
longfile = longfile.Concat(new []{fi.FullName}).OrderByDescending(s=>s.Length).Take(3);

ただ、サイズを測定するという関数が、その副作用として、ファイル名の長いものをリストアップするなんてことをなぜしたいかがまったく分からないです。

-- 追記(stackoverflow)
コンソールアプリで、下記を実行しましたがエラーになりませんでした。コードの確認をお願いします。また再現しなかったので、エラーのstacktraceとか他の情報をいただかないと分からないです。
(E:\somewhereはファイル数4800以上フォルダ数1200以上サイズ213MBです)

        static void Main(string[] args)
        {
            long dirsize = GetDirectorySize(new DirectoryInfo(@"E:\somewhere"));
            Console.WriteLine(longfile.ElementAt(0));
            Console.WriteLine(longfile.ElementAt(1));
            Console.WriteLine(longfile.ElementAt(2));
        }
        static IEnumerable<string> longfile = Enumerable.Repeat("", 3);

        public static long GetDirectorySize(DirectoryInfo dirInfo)
        {
            long size = 0;

            //フォルダ内の全ファイルの合計サイズを計算する。
            foreach (FileInfo fi in dirInfo.GetFiles())
            {
                size += fi.Length;

                longfile = longfile.Concat(new[] { fi.FullName }).OrderByDescending(s => s.Length).Take(3);
            }

            //サブフォルダのサイズを合計していく。
            foreach (DirectoryInfo di in dirInfo.GetDirectories())
            {
                size += GetDirectorySize(di);

            }

            //結果を返す
            return size;
        }

--- 追記
コンパルエラーについて、エラー文のところだけ書きますが、次のいずれかで解決できます。

            //下記でエラーになります。長いパス名ベスト3を取得したいです。
            IOrderedEnumerable<FileInfo> longlist = filelist.OrderByDescending(s => s.FullName.Length).Take(3);
            // (1)IEnumerableにする
            //IEnumerable<string> longlist = filelist.OrderByDescending(s => s.FullName.Length).Take(3).Select(s => s.FullName); //パターンA 文字列にSelectする
            //IEnumerable<FileInfo> longlist = filelist.OrderByDescending(s => s.FullName.Length).Take(3); //パターンB FileInfoを型で受けとる
            // (2)IOrderedEnumerableのままにする
            //IOrderedEnumerable<FileInfo> longlist = filelist.OrderByDescending(s => s.FullName.Length); //パターンCTakeしない
            //IOrderedEnumerable<FileInfo> longlist = filelist.OrderByDescending(s => s.FullName.Length).Take(3).OrderByDescending(s=>s.FullName.Length); //パターンD再度ソートする


大きな区分けとして、IEnumerableでいくのか、IOrderedEnumerableでいくのかを決めてください。
Aですが、filelistはFileInfo型なのに、左辺は<string>になっていることを、selectで解決しています。
Bについては、Aと異なり、generic型のほうを変えています。
Cでは、Orderedにすることを考えると、Takeが余計なので、単純に取りました。またgeneric型もFileInfoにしています。
Dは、遠回りですが、再度ソートすれば、takeして、OrderedEnumerableにすることができます。

stackoverflowexceptionについては、考えてみます。
elementatをしないとエラーにならないのは、Linqが遅延評価だからです、実際に値が必要になるまで演算が延期されているせいです。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/07/20 12:59

    ifですっげーがんばるとこのやつはotnさんのロジックも参考に。ifですっげーがんばろうとしたらバグしか生まれないと再認識しました。

    キャンセル

  • 2018/07/20 15:40 編集

    ご回答ありがとうございます。
    if文はバグの原因になるとのご指摘なので、if文での実装はやめようと思いました。
    Linqにあまり慣れてなくて質問なのですが、
    文法的に意味を分かりやすくするためにカッコを付けるとしたら、下記で合っていますか?
    longfile = ((longfile.Concat(new[] { fi.FullName })).OrderByDescending(s =>s.Length)).Take(3);
    (Concat後にOrderByDescendingして、その後にTakeしているという認識で合っていますか?)

    また、GetDirectorySize()呼び出し後に下記のようにそれぞれの要素を取り出そうとしたら、エラーになってしまいました。
    修正方法のご教示をお願い致します。
    long dirsize = GetDirectorySize(di);
    Console.WriteLine(longfile.ElementAt(0));
    Console.WriteLine(longfile.ElementAt(1));
    Console.WriteLine(longfile.ElementAt(2));

    System.StackOverflowException
    HResult=0x800703E9
    Message=種類 'System.StackOverflowException' の例外がスローされました。

    >サイズを測定するという関数が、その副作用として、ファイル名の長いものをリストアップするなんてことをなぜしたいかがまったく分からない
    確かにメソッド名が不適切でした。Windowsのファルダの内容が正しく取得できないバグ(?)がありまして(https://teratail.com/questions/136237)、自分でフォルダの内容を調べるツールを作っています。

    キャンセル

  • 2018/07/20 17:41

    ifについては、私があほなだけなので。

    > (Concat後にOrderByDescendingして、その後にTakeしているという認識で合っていますか?)
    その認識であっています。Linqは「.」を「して」と読めるのでカッコをなくしても、元になるリストを用意して、1要素だけの配列をConcatして、長さでOrderByDescendingして、3つTakeする。と読めるはず。

    やりたいことは一応理解しましたが、カウントに名前は不要では?
    また、ファイルサイズをカウントする処理が内部で、関数外部にあるリストを更新するというのは設計として好ましくないです。
    今回の目的とやろうとしていることからすると、名前とサイズのペア(valuetupleかそういうクラスを持つか、単にfileinfoのリストを返してもいい)の非常に大きいリストを取るような関数を作ったうえで、サイズが欲しいときはそれをLinqのSumにかければ答えが求まり、名前の長いリストが欲しいならそれをOrderByDescendingすればTOP1でもTOP3でもTOP10でも(Take次第で)任意に求まる、ようなそういうものができると思います。
    手があいたら、↑の概念コードを補足します。

    キャンセル

  • 2018/07/20 17:41

    エラーについては、コードを追記する必要があるので、回答を補充します。

    キャンセル

  • 2018/07/20 18:16

    StackOverflowExceptionは調べているフォルダでシンボリックリンク等で循環参照が起こってるとか
    AllDirectoriesオプションを指定したときに無限ループが起こりうる注意書きがありますし、元質問でも指摘されていましたし

    キャンセル

  • 2018/07/20 20:57 編集

    >YAmaGNZ様
    ご回答ありがとうございます。
    コマンドプロンプトでdirコマンドを実行してもリンクの表記が見られなく、またエクスプローラー上で見てもアイコンにショートカットのマークもなしと言った状況でしたので、元質問では原因はリンクではないと思い込んでいました。何にリンクが設定されているのか(本当にリンクが設定されているのか)調べたいと思います。

    >papinianus様
    ご回答ありがとうございます。
    調べるフォルダを他のフォルダのパスに設定したらエラーが起きず、ElementAt(0)~ElementAt(3)が出力されました。ありがとうございます。
    可能でしたら、お時間のあるときにご回答いただいた設計のコードをご教示いただきたいです。
    また、前回エラーが起きた原因ですが、調べたフォルダ内にリンクが貼られていたかもしれない(本当にリンクが貼られているのかよくわからない)状態でして、こちらのフォルダを調べます。

    キャンセル

  • 2018/07/21 14:47

    追記しました。よろしくお願いします。

    キャンセル

  • 2018/07/21 17:13

    追記しました。よろしくお願いします。

    キャンセル

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

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

関連した質問

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

  • C#

    7093questions

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

  • アルゴリズム

    408questions

    アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。