Rubyでファイル探索ソフトを作りましょう

# 範囲
（わかりにくいところあれば是非教えてください！）
本記事では、Rubyを使って簡単なファイル探索プログラムを作ります。
この記事はグラフ理論の勉強を初めたばかり人向けです。
作るソフトの特徴はこちらです → 

- 文字列からファイルを探して、その文字列に当たるファイルの名前のリストを戻す
- ファイルの名前が全部当たらなくてもそのファイルを追加（例→ “ru” を探したら “ruby”というファイルも戻されます）

ソフトの範囲外ところはこちらです → 

- ファイルの名前だけを探すので拡張子が当たっても戻さない
- ダイレクトリーの名前が当たっても戻さない

ちなみに僕がWindowsだけでテストをできましたがMacでもLinuxでも動くはずです（すみませんm(_ _)m）

# ベージックなところ

じゃあ早速コードを見てみましょう！
こちらのgistでコードを見る事ができます → [gist](https://gist.github.com/Luigi123/1ac3021750f1760c08377ac3f37baf31) 

検索のコードは全部1つのクラスの中でやりますので、そのクラスを作りましょう。

```ruby:search.rb
class Search
  def initialize

  end
end
```

そのクラスは、動かされる（ファイルを探す）時に、探す文字列・どこから始まる・クラフの深さ（奥行き？）が必要なのでそのようなコンストラクタを作りましょう


```ruby:search.rb
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つのダイレクトリーを探すメソッドを作りましょう

```ruby:search.rb
  def search_dir(current_dir)
    Dir[current_dir + '/*'].each do |current_file|
	#ここにコード
    end
  end
```

search_dirというメソッドを定義して、そのメソッドはcurrent_dirという変数を引数で渡される。
渡されたダイレクトリーの中にあるファイル・ダイレクトリーを全部ゲットして（```current_dir + '/*'```）ループをしてます。
「```#ここにコード```」っていうところに、current_dirがダイレクトリーかファイルかをチェックして、ファイルならresultという配列に入れて、最後にその配列を戻しましょう。

```ruby:search.rb
  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```）だけをチェックすればいいです。

ルビーでそのコードはこうなります →

```ruby:search.rb
  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?関数を使いましょう

```ruby:search.rb
  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
```

それで今のダイレクトリーの検索は完成です！でも、、今のダイレクトリーだけじゃなくてサブディレクトリにも入ってそのダイレクトリーにも検索したいです。

# グラフ登場

それには、深さ優先探索アルゴリズムを使いましょう！
（このコードに使う物をできるだけ説明しますが、おすすめの読み物はこちらです（色々サンプルコードあります））→ 
 
- [グラフ理論](https://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%A9%E3%83%95%E7%90%86%E8%AB%96) 
- [深さ優先探索](https://ja.wikipedia.org/wiki/%E6%B7%B1%E3%81%95%E5%84%AA%E5%85%88%E6%8E%A2%E7%B4%A2) 
- [深さ制限探索](https://ja.wikipedia.org/wiki/%E6%B7%B1%E3%81%95%E5%88%B6%E9%99%90%E6%8E%A2%E7%B4%A2)

「```C:\```」とか「```\```」からするとファイルが多すぎるのでプログラムが遅くなりそうですし、深さを限らないと！だからクラスを作った時にsearch_depthを作りました。

理論・考えは → search_dirの関数は再帰にしましょう。ファイルはダイレクトリーか決めて、ダイレクトリーならそのダイレクトリーに入ってまたsearch_dirを呼び出しましょう。その中のダイレクトリーの結果を元の結果に追加して、最後に元の結果を戻しましょう（それを何回も、ダイレクトリー全部に！）。

最初に僕達が「```unless File.directory?(current_file)```」を使ってましたが、今ダイレクトリーならそのダイレクトリーに入ることにするからunlessをif-elseに変えましょう。

```ruby:search.rb
      if File.directory?(current_file)
       #ダイレクトリーに入る
      else
        result.push(current_file) if add?(current_file)
      end
```

そして再帰にする前、search_dirに深さを引数で送りましょう！定義はこうなります→

```ruby:search.rb
  def search_dir(current_dir, depth)
```

そしてcurrent_fileはダイレクトリーなら、depth - 1 の深さとそのダイレクトリーの名前でsearch_dirを呼び出しましょう。その次のsearch_dirの結果を今のsearch_dirの結果に連結したらコードはこうなります →

```ruby:search.rb
      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になってたら探索を始まる前にも空っぽな配列を戻しましょう。

```ruby:search.rb
  def search_dir(current_dir, depth)
    result = []
    return result if depth == 0
```

そして最後にresultを戻したら完成されたメソッドはこうなっています →

```ruby:search.rb
  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
```

今変えた事だけで、プログラムの複雑さがたくさん増えます。

## 例

![tree.jpg](https://qiita-image-store.s3.amazonaws.com/0/126962/781117b0-018f-1af3-a622-24a99ae75f3a.jpeg)

例えば、上の画像のダイレクドリーツリーをみて、今作ったコードの動きについて考えてみましょう。（グレイな物はダイレクドリー、青い物はファイル）

"biu"で探索しましょう。もちろんrootから。

* 最初にrootにすぐ次のダイレクドリー・ファイルの名前を分析しましょう。
* １というダイレクドリーがあった、そのダイレクドリーの内容を分析しましょう。
* １．１というダイレクドリーがあった、そのダイレクドリーの内容を分析しましょう。
* aiurというファイルがありましたが、名前が当たってないですのでなにもせずに空っぽな配列でも戻しましょう。
* ほかに空っぽだからなにも分析する事がない、１に戻りましょう（１に１．１しかなにもなかったので、rootに戻る）。
* rootからの分析の続きです。２に分析しましょう。
* ２．１に入りましょう。
* ３．１に入りましょう
* abcが名前当たってない、スキップしましょう。
* biuが名前当たってる、自分の結果に入れましょう。
* kuoが名前あったてない、スキップ！
* 自分の結果（biu入り）を２．１に戻して、そのままで２に戻しましょう。
* ２．２に入って、そのままでまた２に戻しましょう。
* ２の結果を（biu入り）をrootに戻して、他に探索ところはないですのでそのままで戻して、結果は```root/2/2.1/3.1/biu```になります！

最初に、このプログラムは１つだけのダイレクドリー探してた（この例では、１と２しか探さない事になる）のに、コード２行だけでこんなグラフ探索なコードになりました。

お話は終わり！コードに戻りましょう。

#最後の仕上げ

これではプログラムはほとんど完成だけど、search_dirは引数が必要ですし、クラスのコンストラクタでbase_dirとsearch_depthを定義しましたので、それを使って違うメソッドでsearch_dirを呼び出しましょう

```ruby:search.rb
  def do_search
    search_dir(@base_dir, @search_depth)
  end
```

最後の最後に、add?とsearch_dirをプライベートにして、完成されたクラスはこれです →

```ruby:search.rb
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
```

それでクラスの使いはこうなります → 

```ruby:search.rb
search = Search.new('rails', 'c:/', 10)
files = search.do_search
files.each {|file| puts file}
```

# 本物探索

はい！それでプログラムを使ってみましょう！僕がやるのは、、React.jsをダウンロードしかた覚えてないのでそのファイルを探してみましょう！

```ruby:search.rb
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でコメントツリーをゼロから作ったらいいと思います！それできたら簡単なゲーム（リバーシとか）の人工知能に挑戦したらどうでしょ？

それでは！以上です！