範囲
(わかりにくいところあれば是非教えてください!)
本記事では、Rubyを使って簡単なファイル探索プログラムを作ります。
この記事はグラフ理論の勉強を初めたばかり人向けです。
作るソフトの特徴はこちらです →
- 文字列からファイルを探して、その文字列に当たるファイルの名前のリストを戻す
- ファイルの名前が全部当たらなくてもそのファイルを追加(例→ “ru” を探したら “ruby”というファイルも戻されます)
ソフトの範囲外ところはこちらです →
- ファイルの名前だけを探すので拡張子が当たっても戻さない
- ダイレクトリーの名前が当たっても戻さない
ちなみに僕がWindowsだけでテストをできましたがMacでもLinuxでも動くはずです(すみませんm(_ _)m)
ベージックなところ
じゃあ早速コードを見てみましょう!
こちらのgistでコードを見る事ができます → gist
検索のコードは全部1つのクラスの中でやりますので、そのクラスを作りましょう。
class Search
def initialize
end
end
そのクラスは、動かされる(ファイルを探す)時に、探す文字列・どこから始まる・クラフの深さ(奥行き?)が必要なのでそのようなコンストラクタを作りましょう
class Search
def initialize(search_str, base_dir, search_depth)
@search_str = search_str
@base_dir = base_dir
@search_depth = search_depth
end
end
search_str は探す文字列
base_dirは始まるダイレクトリー(例えば「C:/
」、「C:/Users/OOOO/Desktop
」, 「/
」,「/users/
」など)
search_depthは深さです!どこまで探すか決める
次は1つのダイレクトリーを探すメソッドを作りましょう
def search_dir(current_dir)
Dir[current_dir + '/*'].each do |current_file|
#ここにコード
end
end
search_dirというメソッドを定義して、そのメソッドはcurrent_dirという変数を引数で渡される。
渡されたダイレクトリーの中にあるファイル・ダイレクトリーを全部ゲットして(current_dir + '/*'
)ループをしてます。
「#ここにコード
」っていうところに、current_dirがダイレクトリーかファイルかをチェックして、ファイルならresultという配列に入れて、最後にその配列を戻しましょう。
def search_dir(current_dir)
result = []
Dir[current_dir + '/*'].each do |current_file|
unless File.directory?(current_file)
result.push(current_file)
end
end
result
end
でも今のままは、ダイレクトリーの全部のファイルの名前を戻してますよね、クラスのsearch_strに当たってるファイルしかほしくないので、unlessの中で名前が当たってるかチェックしましょう。でも後から変えやすく、読みやすくなるようにそのチェックを他の関数に入れましょう
current_fileはこのような名前(ファイルパス+名前)になっています→「C:\users\OOO\desktop\myFile.jpeg
」。このままで「'desktop'」を探索したらデスクトップにあるファイルを全部戻されるよね?その文字列に入ってるから。だから探索の時はファイルの名前(myFile
)だけをチェックすればいいです。
ルビーでそのコードはこうなります →
def add?(full_path)
file_name = File.basename(full_path)
file_name.include?(@search_str)
end
full_pathからfile_nameをゲットして、そのfile_nameにクラスのコンストラクタでセットしたsearch_strがついてたらtrueを戻す!そうじゃないとfalseを!
前のsearch_dirというメソッドを変えてそのadd?関数を使いましょう
def search_dir(current_dir)
result = []
Dir[current_dir + '/*'].each do |current_file|
unless File.directory?(current_file)
result.push(current_file) if add?(current_file)
end
end
result
end
それで今のダイレクトリーの検索は完成です!でも、、今のダイレクトリーだけじゃなくてサブディレクトリにも入ってそのダイレクトリーにも検索したいです。
グラフ登場
それには、深さ優先探索アルゴリズムを使いましょう!
(このコードに使う物をできるだけ説明しますが、おすすめの読み物はこちらです(色々サンプルコードあります))→
「C:\
」とか「\
」からするとファイルが多すぎるのでプログラムが遅くなりそうですし、深さを限らないと!だからクラスを作った時にsearch_depthを作りました。
理論・考えは → search_dirの関数は再帰にしましょう。ファイルはダイレクトリーか決めて、ダイレクトリーならそのダイレクトリーに入ってまたsearch_dirを呼び出しましょう。その中のダイレクトリーの結果を元の結果に追加して、最後に元の結果を戻しましょう(それを何回も、ダイレクトリー全部に!)。
最初に僕達が「unless File.directory?(current_file)
」を使ってましたが、今ダイレクトリーならそのダイレクトリーに入ることにするからunlessをif-elseに変えましょう。
if File.directory?(current_file)
#ダイレクトリーに入る
else
result.push(current_file) if add?(current_file)
end
そして再帰にする前、search_dirに深さを引数で送りましょう!定義はこうなります→
def search_dir(current_dir, depth)
そしてcurrent_fileはダイレクトリーなら、depth - 1 の深さとそのダイレクトリーの名前でsearch_dirを呼び出しましょう。その次のsearch_dirの結果を今のsearch_dirの結果に連結したらコードはこうなります →
if File.directory?(current_file)
children = search_dir(current_file, depth - 1)
result.concat(children)
else
result.push(current_file) if add?(current_file)
end
今のまま、depthを変えてますがその変化はなにも効果ないですね。depthは0になったら探索を辞めないといけないですので、0になってたら探索を始まる前にも空っぽな配列を戻しましょう。
def search_dir(current_dir, depth)
result = []
return result if depth == 0
そして最後にresultを戻したら完成されたメソッドはこうなっています →
def search_dir(current_dir, depth)
result = []
return result if depth == 0
Dir[current_dir + '/*'].each do |current_file|
if File.directory?(current_file)
children = search_dir(current_file, depth - 1)
result.concat(children)
else
result.push(current_file) if add?(current_file)
end
end
result
end
今変えた事だけで、プログラムの複雑さがたくさん増えます。
例
例えば、上の画像のダイレクドリーツリーをみて、今作ったコードの動きについて考えてみましょう。(グレイな物はダイレクドリー、青い物はファイル)
"biu"で探索しましょう。もちろんrootから。
- 最初にrootにすぐ次のダイレクドリー・ファイルの名前を分析しましょう。
- 1というダイレクドリーがあった、そのダイレクドリーの内容を分析しましょう。
- 1.1というダイレクドリーがあった、そのダイレクドリーの内容を分析しましょう。
- aiurというファイルがありましたが、名前が当たってないですのでなにもせずに空っぽな配列でも戻しましょう。
- ほかに空っぽだからなにも分析する事がない、1に戻りましょう(1に1.1しかなにもなかったので、rootに戻る)。
- rootからの分析の続きです。2に分析しましょう。
- 2.1に入りましょう。
- 3.1に入りましょう
- abcが名前当たってない、スキップしましょう。
- biuが名前当たってる、自分の結果に入れましょう。
- kuoが名前あったてない、スキップ!
- 自分の結果(biu入り)を2.1に戻して、そのままで2に戻しましょう。
- 2.2に入って、そのままでまた2に戻しましょう。
- 2の結果を(biu入り)をrootに戻して、他に探索ところはないですのでそのままで戻して、結果は
root/2/2.1/3.1/biu
になります!
最初に、このプログラムは1つだけのダイレクドリー探してた(この例では、1と2しか探さない事になる)のに、コード2行だけでこんなグラフ探索なコードになりました。
お話は終わり!コードに戻りましょう。
最後の仕上げ
これではプログラムはほとんど完成だけど、search_dirは引数が必要ですし、クラスのコンストラクタでbase_dirとsearch_depthを定義しましたので、それを使って違うメソッドでsearch_dirを呼び出しましょう
def do_search
search_dir(@base_dir, @search_depth)
end
最後の最後に、add?とsearch_dirをプライベートにして、完成されたクラスはこれです →
class Search
def initialize(search_str, base_dir, search_depth)
@search_str = search_str
@base_dir = base_dir
@search_depth = search_depth
end
def do_search
search_dir(@base_dir, @search_depth)
end
private
def add?(full_path)
file_name = File.basename(full_path)
file_name.include?(@search_str)
end
def search_dir(current_dir, depth)
result = []
return result if depth == 0
Dir[current_dir + '/*'].each do |current_file|
if File.directory?(current_file)
children = search_dir(current_file, depth - 1)
result.concat(children)
else
result.push(current_file) if add?(current_file)
end
end
result
end
end
それでクラスの使いはこうなります →
search = Search.new('rails', 'c:/', 10)
files = search.do_search
files.each {|file| puts file}
本物探索
はい!それでプログラムを使ってみましょう!僕がやるのは、、React.jsをダウンロードしかた覚えてないのでそのファイルを探してみましょう!
search = Search.new('react', 'C:/Users/Luigi', 3)
files = search.do_search
files.each {|file| puts file}
コマンドプロンプトで実行したらこう出ました →
C:\Users\Luigi\Desktop\Ruby>ruby search.rb
C:/Users/Luigi/Desktop/react-15.0.2.zip
C:/Users/Luigi/Downloads/react-15.0.2 (1).zip
そのファイルが2回も入ってるみたい (^_^;)
もちろんもっと綺麗な呼び方とかあります(例えばcmdでsearch --base 'C:/Users/Luigi' --depth 3
)が、それのやり方は色んなサイトに書いてありますのでこの記事には要らないと思いました。
グラフとか、本日使った探索アルゴリズムとかは結構便利ですのでまだわからない方は是非勉強してみてください!
次、そんなアルゴリズムにチャレンジしたい人はRailsでコメントツリーをゼロから作ったらいいと思います!それできたら簡単なゲーム(リバーシとか)の人工知能に挑戦したらどうでしょ?
それでは!以上です!