Rubyで配列から要素を1つ取り出す良い方法はありませんか?
─ 問題 ─
次のファイルリストから
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