─ 問題 ─

次のファイルリストからgemspecファイルだけを抜き出し、それをgemspec変数で、残りをfiles変数でアクセスできるようにする簡単な方法を示しなさい。但し、gemspecのファイル名はプロジェクトによって変わりうることを考慮しなさい。

files = ['Gemfile', 'LICENSE.txt', 'README.md', 'Rakefile', 'bin', 'lib', 'maliq.gemspec', 'pkg', 'spec']



─ 僕の通った道 ─

Array#deleteを使ってみる

files = ['Gemfile', 'LICENSE.txt', 'README.md', 'Rakefile', 'bin', 'lib', 'maliq.gemspec', 'pkg', 'spec']

gemspec = files.delete('maliq.gemspec')
gemspec # => "maliq.gemspec"
files # => ["Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin", "lib", "pkg", "spec"]

gemspecのファイル名が固定ならこれで終わりだけど、もちろんこれは問の但し書き条件を満たしていないよ。だからArray#deleteが、

gemspec = files.delete('*.gemspec')
gemspec = files.delete(/\.gemspec$/)

みたいにワイルドカードを取れたり、正規表現を取れたりできればよかったんだけど、残念ながらdeleteの引数は==同値判定っきりなんだよ。

Array#delete_ifまたはreject!

deleteが駄目だってんなら、次はArray#delete_ifまたはArray#reject!だよね。

files = ['Gemfile', 'LICENSE.txt', 'README.md', 'Rakefile', 'bin', 'lib', 'maliq.gemspec', 'pkg', 'spec']

gemspec = files.delete_if { |f| f.match(/\.gemspec$/) }
gemspec # => ["Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin", "lib", "pkg", "spec"]
files # => ["Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin", "lib", "pkg", "spec"]

gemspec = files.reject! { |f| f.match(/\.gemspec$/) }
gemspec # => ["Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin", "lib", "pkg", "spec"]
files # => ["Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin", "lib", "pkg", "spec"]

ところが残念なことに、delete_ifは常にselfを返すんだよ。つまりヒットしたgemspecファイルを完全に捨て去っちゃうんだよ。

ところで、delete_ifとreject!ではヒットしないときの返り値が違うって知ってた?前者はselfが後者はnilが返るんだって。

files = ['Gemfile', 'LICENSE.txt', 'README.md', 'Rakefile', 'bin', 'lib', 'maliq.gemspec', 'pkg', 'spec']

gemspec = files.delete_if { |f| f.match(/\.rb$/) }
gemspec # => ["Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin", "lib", "maliq.gemspec", "pkg", "spec"]
gemspec = files.reject! { |f| f.match(/\.rb$/) }
gemspec # => nil

nilが返るならその整合性としてヒットしたときはそれを返してくれてもと思うのは僕だけかな?

Enumerable#detectまたはfind

次はEnumerable#detectを試してみるよ。

files = ['Gemfile', 'LICENSE.txt', 'README.md', 'Rakefile', 'bin', 'lib', 'maliq.gemspec', 'pkg', 'spec']

gemspec = files.detect { |f| f.match(/\.gemspec$/) } # => "maliq.gemspec"
gemspec # => "maliq.gemspec"
files # => ["Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin", "lib", "maliq.gemspec", "pkg", "spec"]

filesにgemspecファイルが残っちゃうから、これはもちろん駄目だよね。

Enumerable#partition

次に辿り着いたのはEnumerable#partitionだよ。partitionはブロック指定条件で集合を2つのグループに分けてくれるメソッドだよ。

files = ['Gemfile', 'LICENSE.txt', 'README.md', 'Rakefile', 'bin', 'lib', 'maliq.gemspec', 'pkg', 'spec']

gemspec, files = files.partition { |f| f.match(/\.gemspec$/) }
gemspec # => ["maliq.gemspec"]
files # => ["Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin", "lib", "pkg", "spec"]

惜しい!gemspec変数には配列が入るから最終的には次のようにする必要があるよ。

gemspecs, files = files.partition { |f| f.match(/\.gemspec$/) }
gemspec = gemspecs.first
gemspec # => "maliq.gemspec"
files # => ["Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin", "lib", "pkg", "spec"]

なんかスマートじゃないよね。

Enumerable#partition + 括弧付き多重代入

で、Rubyの多重代入をちょっと工夫してみたんだよ。カッコを付けてね。

files = ['Gemfile', 'LICENSE.txt', 'README.md', 'Rakefile', 'bin', 'lib', 'maliq.gemspec', 'pkg', 'spec']

(gemspec, *_), files = files.partition { |f| f.match(/\.gemspec$/) }
gemspec # => "maliq.gemspec"
files # => ["Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin", "lib", "pkg", "spec"]

括弧を使うと、partitionの返り値の第一配列が展開されてその先頭要素をgemspecに上手く取れるんだ。

どう、カッコイイ?それとも分かりづらい?

これが僕の回答だけど、もっとスマートなやり方があったら教えてね。


サンワダイレクト パーティション 2枚セット 間仕切り パーテーション W800×H1600 100-SPT001-2



blog comments powered by Disqus
ruby_pack8

100円〜で好評発売中!
M'ELBORNE BOOKS