※ 本記事はDMM WEBCAMP mentor Advent Calendar 2022 4日目のエントリーです。
カッコつけたコードとは
- 可読性を度外視した、知識をひけらかしてスキルをアピールするためのコードです
- 実用面は微妙ですが、自分用のアプリや競プロなんかではちゃんと役に立つかも
- 何より、Rubyの記法の自由度の高さを楽しむことを第一にやっていきます
普通のコードを書き換えていくハンズオン形式なので、是非手を動かしながらやってみてください。
今回書き換えるコード
入力された文字列を、所謂「Leet」表記の文字列に置換して出力するプログラムです。
leet(リート、1337、l33t)は、主に英語圏においてインターネット上で使われるアルファベットの表記法である。(Wikipediaより)
つまり、「HELLO」を「H3LL0」と書くような表記で、名前の被りを避けるためにたまにライブラリ名等でも採用されています。
Leetにも色々ありますが、今回は単純に以下のように、英語大文字→数字のみの置換を考えます。
対象 | 置換先 |
---|---|
O | 0 |
I | 1 |
Z | 2 |
E | 3 |
A | 4 |
S | 5 |
G | 6 |
T | 7 |
B | 8 |
P | 9 |
これを愚直にコードに表すとこうなります。できる限りコメントで説明はしています。
最後のputs
は実行時の見栄えのためです。
input = gets.chomp # gets: 入力を受け取る, chomp: (入力の末尾には改行がつくので)改行を取り除く
arr = input.split('') # split(''): splitに空文字を渡すと一文字ずつ区切った配列になる
arr.each do |x|
case x
when 'O'
print 0
when 'I'
print 1
when 'Z'
print 2
when 'E'
print 3
when 'A'
print 4
when 'S'
print 5
when 'G'
print 6
when 'T'
print 7
when 'B'
print 8
when 'P'
print 9
else
print x
end
end
puts
実行例は以下の通りです。
$ ruby main.rb
YUKIHIRO MATSUMOTO (←入力)
YUK1H1R0 M475UM070 (←出力)
さて、上手く動いてはいますがコードが長すぎますよね。
これからどんどんコードを短くおしゃれにしていきますが、続きを読む前に是非考えてみてください。
書き換え開始
前述したコードは、パッと見では、print
を何度も記述しているのがナンセンスです。それから、可読性を無視すれば、変数の代入は減らせそうですね。
さらに言えば、せっかくchompで改行を消しているのに最後にputs
で改行を足しているのはカッコ悪いです。
上記点を修正したものがこちら。
def leet(x)
# 置換後の文字だけを返すことでprintを何度も書かなくてもよくなった
case x
when 'O'
0
when 'I'
1
when 'Z'
2
when 'E'
3
when 'A'
4
when 'S'
5
when 'G'
6
when 'T'
7
when 'B'
8
when 'P'
9
else
x
end
end
gets.split('').each do |x| # 入力を受け取ってそのまま分割してループ
print leet x # 最後はxに改行が入るのでputsは不要
end
print
という文字が減ったことによって、コードの「圧」が随分と和らぎました。
ただ、もっと改善できそうですね。今度はwhen
を何度も書いているのが気になってきました。
え?そこは改善しようがない?
いいえ。まだまだ行けます。ハッシュを使いましょう。
def leet(x)
h = { O: 0, I: 1, Z: 2, E: 3, A: 4, S: 5, G: 6, T: 7, B: 8, P: 9 }
res = h[x.to_sym]
if res
res
else
x
end
end
gets.split('').each do |x|
print leet x
end
Python等と違ってRubyのハッシュは、キーにクォーテーションを付ける必要がなく、タイピング数がかなり減ります。
キーにクォーテーションをつければ、to_symが不要になるのですが……クォーテーションをつける方が面倒ですよね。Shiftキーを押すのが好きな人はいないと思います。
もう説明しませんが、変数の代入、それから単純に値を返すだけのif文はやめちゃいましょう。省略すると以下のようになります。
def leet(x)
print { O: 0, I: 1, Z: 2, E: 3, A: 4, S: 5, G: 6, T: 7, B: 8, P: 9 }[x.to_sym] || x
end
gets.split('').each do |x|
leet x
end
省略したついでにprintをleet内に移動しました。こっちに書いても1回で済むようになりましたからね。
最初に比べるとかなり短くなりました。
もう無理でしょうか。まだ頑張りましょう。
今回のleetメソッドが返す値が「数値」もしくは「引数そのまま」であることに注目しましょう。
配列のindexを使えばもっと簡単に書けそう。そんな気はしませんか?
def leet(x)
print %w[O I Z E A S G T B P].index(x) || x
end
gets.split('').map do |x|
leet x
end
%w
は括弧内にスペース区切りで入力した文字列を元に配列を作る記法です。普通に配列を作る時にクォーテーション、コンマを入力する必要がなくなっているのが強いですね。
index
はその名の通り引数のindexを返すメソッドで、ここで引数の括弧は省略できないことにもやっとしますが、まあ良いでしょう(省略するとx || x
が引数だとみなされてしまう)。
それからeach
をmap
に変更しています。特に意味はないですが、タイピング数が一文字減っていますし、敢えてmapを使うと玄人感が出ておすすめです。パフォーマンスは悪くなりますが、誤差レベルなので気にしなくても大丈夫です。
さて、これで完成です!と、言いたいところですが、まだもう1ステップ頑張りましょう。
これで最後です。
class String
def leet
print %w[O I Z E A S G T B P].index(self) || self
end
end
gets.split('').map(&:leet)
見る人が見ればアレルギー症状が出るかもしれない、そんなコードです。
さっきより短くなっているかと言われれば微妙ですが、カッコ付けるために短さが犠牲になることも時にはあるでしょう。
さて、このコードには特に大事なテクニックが2つ入っています。
まずは、map(&:leet)
です。これは、
map do |x|
x.leet
end
の省略形と考えていいです。競プロ界隈では「入力を分割して、全てを(文字列から)数値に変換して受け取る」といったコードをgets.split.chomp.map(&:to_i)
という一つの定型句として用いるので、知っている人も多いかと思います。
もちろん、mapをeachにしても全く同じ書き方ができます。
この省略記法では「その配列要素に対して定義されたメソッド」、つまり「その配列要素にドットで繋げられるメソッド」しか使えないことに注意しましょう。
要するに、
def leet
print %w[O I Z E A S G T B P].index(self) || self
end
gets.split('').map(&:leet)
というコードは、例えば"A".leet
というメソッドが存在しないために実行できない、ということです。
かのマリー・アントワネットは言いました。『メソッドがないなら、生やせばいいじゃない』と。
Rubyでは大体のものがClassで実装されています。文字列はStringクラスです。
RubyではビルトインのClassにもメソッドを生やすことができます。これが2つ目のテクニックです。
さて、全体をもう一度見てみましょう。
class String
def leet
print %w[O I Z E A S G T B P].index(self) || self
end
end
gets.split('').map(&:leet)
何度見ても美しいですね。
まとめ
本記事では主に
- mapメソッドの省略記法
- 組み込みのStringクラスへのメソッドの追加
という2つのテクニックを使いました。後者は大抵の場合はアンチパターンですが、前者は実際の開発でも十分に活躍できます!
またこんな感じの記事をアップすると思うので、TwitterやQiitaのフォローをしておいてもらえると嬉しいです!!