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

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

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

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

アルゴリズム

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

Q&A

4回答

1684閲覧

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

退会済みユーザー

退会済みユーザー

総合スコア0

C#

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

アルゴリズム

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

0グッド

0クリップ

投稿2018/07/20 02:57

編集2018/07/21 08:12

前提・実現したいこと

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

試したこと

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

C#

1 static string longfile = ""; 2 static string longdir = ""; 3 4 public static long GetDirectorySize(DirectoryInfo dirInfo) 5 { 6 long size = 0; 7 8 //フォルダ内の全ファイルの合計サイズを計算する。 9 foreach (FileInfo fi in dirInfo.GetFiles()) 10 { 11 size += fi.Length; 12 13 if (fi.FullName.Length > longfile.Length) 14 { 15 longfile = fi.FullName; 16 } 17 } 18 19 //サブフォルダのサイズを合計していく。 20 foreach (DirectoryInfo di in dirInfo.GetDirectories()) 21 { 22 size += GetDirectorySize(di); 23 24 if (di.FullName.Length > longdir.Length) 25 { 26 longdir = di.FullName; 27 } 28 } 29 30 //結果を返す 31 return size; 32 }

追記

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

このような長いパスを知る為に、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);
のコードで赤線が付いてエラーになります。
ご教示のほどお願い致します。

C#

1 private void Form1_Load(object sender, EventArgs e) 2 { 3 Console.WriteLine("test"); 4 //long dirsize = GetDirectorySize(di); 5 List<FileInfo> filelist = MyDirectoryInfo(@"C:\Users\〇〇\Desktop\My Documents"); 6 7       //下記でエラーになります。長いパス名ベスト3を取得したいです。 8 IOrderedEnumerable<string> longlist = filelist.OrderByDescending(s => s.FullName.Length).Take(3); 9 } 10 11 public static List<FileInfo> MyDirectoryInfo(string dirpath) 12 { 13 DirectoryInfo di = new DirectoryInfo(dirpath); 14 List<FileInfo> filelist = new List<FileInfo>(); 15 GetDirectoryInfo(di, ref filelist); 16 return filelist; 17 } 18 19 public static void GetDirectoryInfo(DirectoryInfo dirInfo, ref List<FileInfo> list) 20 { 21 //long size = 0; 22 23 //フォルダ内の全ファイルの合計サイズを計算する。 24 foreach (FileInfo fi in dirInfo.GetFiles()) 25 { 26 list.Add(fi); 27 //size += fi.Length; 28 //longfile = ((longfile.Concat(new[] { fi.FullName })).OrderByDescending(s => s.Length)).Take(3); 29 } 30 31 //サブフォルダのサイズを合計していく。 32 foreach (DirectoryInfo di in dirInfo.GetDirectories()) 33 { 34 GetDirectoryInfo(di, ref list); 35 //size += GetDirectorySize(di); 36 } 37 38 //結果を返す 39 //return size; 40 }

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

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

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

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

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

guest

回答4

0

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

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

投稿2018/07/20 12:35

episteme

総合スコア16614

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

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

Zuishin

2018/07/20 12:56

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

2018/07/20 12:58

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

0

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

投稿2018/07/20 03:02

Zuishin

総合スコア28660

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

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

0

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

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

csharp

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

もしくは

csharp

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

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

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

csharp

1 static void Main(string[] args) 2 { 3 long dirsize = GetDirectorySize(new DirectoryInfo(@"E:\somewhere")); 4 Console.WriteLine(longfile.ElementAt(0)); 5 Console.WriteLine(longfile.ElementAt(1)); 6 Console.WriteLine(longfile.ElementAt(2)); 7 } 8 static IEnumerable<string> longfile = Enumerable.Repeat("", 3); 9 10 public static long GetDirectorySize(DirectoryInfo dirInfo) 11 { 12 long size = 0; 13 14 //フォルダ内の全ファイルの合計サイズを計算する。 15 foreach (FileInfo fi in dirInfo.GetFiles()) 16 { 17 size += fi.Length; 18 19 longfile = longfile.Concat(new[] { fi.FullName }).OrderByDescending(s => s.Length).Take(3); 20 } 21 22 //サブフォルダのサイズを合計していく。 23 foreach (DirectoryInfo di in dirInfo.GetDirectories()) 24 { 25 size += GetDirectorySize(di); 26 27 } 28 29 //結果を返す 30 return size; 31 }

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

csharp

1 //下記でエラーになります。長いパス名ベスト3を取得したいです。 2 IOrderedEnumerable<FileInfo> longlist = filelist.OrderByDescending(s => s.FullName.Length).Take(3); 3 // (1)IEnumerableにする 4 //IEnumerable<string> longlist = filelist.OrderByDescending(s => s.FullName.Length).Take(3).Select(s => s.FullName); //パターンA 文字列にSelectする 5 //IEnumerable<FileInfo> longlist = filelist.OrderByDescending(s => s.FullName.Length).Take(3); //パターンB FileInfoを型で受けとる 6 // (2)IOrderedEnumerableのままにする 7 //IOrderedEnumerable<FileInfo> longlist = filelist.OrderByDescending(s => s.FullName.Length); //パターンC、Takeしない 8 //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 03:57

編集2018/07/24 01:50
papinianus

総合スコア12705

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

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

papinianus

2018/07/20 03:59

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

退会済みユーザー

2018/07/20 06:42 編集

ご回答ありがとうございます。 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)、自分でフォルダの内容を調べるツールを作っています。
papinianus

2018/07/20 08:41

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

2018/07/20 08:41

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

2018/07/20 09:16

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

退会済みユーザー

2018/07/20 11:57 編集

>YAmaGNZ様 ご回答ありがとうございます。 コマンドプロンプトでdirコマンドを実行してもリンクの表記が見られなく、またエクスプローラー上で見てもアイコンにショートカットのマークもなしと言った状況でしたので、元質問では原因はリンクではないと思い込んでいました。何にリンクが設定されているのか(本当にリンクが設定されているのか)調べたいと思います。 >papinianus様 ご回答ありがとうございます。 調べるフォルダを他のフォルダのパスに設定したらエラーが起きず、ElementAt(0)~ElementAt(3)が出力されました。ありがとうございます。 可能でしたら、お時間のあるときにご回答いただいた設計のコードをご教示いただきたいです。 また、前回エラーが起きた原因ですが、調べたフォルダ内にリンクが貼られていたかもしれない(本当にリンクが貼られているのかよくわからない)状態でして、こちらのフォルダを調べます。
退会済みユーザー

退会済みユーザー

2018/07/21 05:47

追記しました。よろしくお願いします。
退会済みユーザー

退会済みユーザー

2018/07/21 08:13

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

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は、ソート処理の必要最小限部分だけを自分で実行することに相当します。

投稿2018/07/20 03:56

編集2018/07/20 04:03
otn

総合スコア84538

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問