LeetCode復習 (8. String to Integer (atoi))
最近LeetCodeをコツコツやっている。
こちらの問題で、自分では数十行書いていた処理が他の人の解答ではたったの6行で感動した。 後学のために、処理内容を1行ずつ見ていく。
問題
StringをIntegerに変換する。
- 数値は正負の符号を含む
- 文字列には数値以外に単語が含まれることもある
- 文字列は空白を含む
- 空白を除く最初の文字が数値ではない場合、0を返す
- 数値は符号付きで32ビットで表現するため、
[−2 ** 31, 2 ** 31 − 1]
の範囲を超える場合は、INT_MAX (2 ** 31 − 1)
orINT_MIN (−2 ** 31)
を返す
例1
Input: "42" Output: 42
例2
Input: " -42" Output: -42 Explanation: The first non-whitespace character is '-', which is the minus sign. Then take as many numerical digits as possible, which gets 42.
例3
Input: "4193 with words" Output: 4193 Explanation: Conversion stops at digit '3' as the next character is not a numerical digit.
例4
Input: "words and 987" Output: 0 Explanation: The first non-whitespace character is 'w', which is not a numerical digit or a +/- sign. Therefore no valid conversion could be performed.
例5
Input: "-91283472332" Output: -2147483648 Explanation: The number "-91283472332" is out of the range of a 32-bit signed integer. Thefore INT_MIN (−231) is returned.
解答
6行の解答例(ブロック内の行数)
# @param {String} str # @return {Integer} def my_atoi(str) arr = str.strip.split(/\s+/) if arr[0] =~ /^([+-]?[0-9]+)/ i = $1.to_i return i >= 0 ? [2147483647, i].min : [-2147483648, i].max end 0 end
1行ずつ見ていく。
1行目
文字列から前後の空白を取り除く→空白で区切って配列に入れる。
# str = " -42" arr = str.strip.split(/\s+/) # => ["-42"]
細かく見る
strip
は文字列先頭と末尾の空白文字を全て取り除いた文字列を生成して返す。
instance method String#strip (Ruby 2.6.0)
arr = str.strip => "-42"
\s
は「タブなどを含む空白文字全般にマッチするメタ文字列」、+
は1回以上という意味なので、/\s+/
は空白が1回以上、の正規表現。
つまり、split(/\s+/)
は1回以上空白を含むもので区切って配列を作る、という意味。
> "123 45 67 8 9 foo".split(/\s+/) => ["123", "45", "67", "8", "9", "foo"]
ちなみにsplit(' ')
でも同じ結果だった。(複数の空白がどう扱われるか気になったけど1つと同じ扱い)
> "123 45 67 8 9 foo".split(' ') => ["123", "45", "67", "8", "9", "foo"]
2行目
配列の1つ目の値が頭に+-が0回か1回ついており、その後1回以上数字が連なる場合、ブロック内の処理を行う。
if arr[0] =~ /^([+-]?[0-9]+)/ end
細かく見る
- ^ : 行頭にマッチ
- [] : 文字クラス
- ? : 0回もしくは1回
- [0-9] : 10進数字
- : 1回以上
=~
は、マッチしたらマッチした位置の先頭のインデックス、マッチしなければnilを返す。
p /foo/ =~ "foo" #=> 0 p /foo/ =~ "afoo" #=> 1 p /foo/ =~ "bar" #=> nil
0はtrueになるのでマッチしたらブロック内の処理が行われることになる。
[+-]?
は+/-が0回か1回なので、2回あったらマッチしない。
> '-+1' =~ /^([+-]?[0-9]+)/ => nil
3行目
上の正規表現にマッチした部分を取得して、Integerクラスに変換し、変数に格納。
i = $1.to_i
$1
を調べると…
最後に成功したパターンマッチで n 番目の括弧にマッチした値が格納されます。
へーー知らんかった。 つまりこういうこと。
>'12345' =~ /^([+-]?[0-9]+)/ > $1 => "12345"
4行目
上で格納した値が正の値であればmax値と比較して小さい方、負の値であればmin値と比較して大きい方を返す。
return i >= 0 ? [2147483647, i].min : [-2147483648, i].max
細かく見る
max``min
便利。
3つ以上の要素にも対応。
> [1, 433, 254245, 642].max => 254245
文字列とは比較不可。
> [1, 433, 'foo', 'bar'].max ArgumentError: comparison of String with 433 failed from (irb):42 from /Users/okiaya/.rbenv/versions/2.4.6/bin/irb:11:in `<main>'
数値の文字列もダメ。
> [1, 433, '123'].max ArgumentError: comparison of String with 433 failed from (irb):43 from /Users/okiaya/.rbenv/versions/2.4.6/bin/irb:11:in `<main>'
6行目
配列の1つ目の値が「頭に+-が0回か1回ついており、その後1回以上数字が連なる」という条件に合致しなければ、0を返す。
# if arr[0] =~ /^([+-]?[0-9]+)/ # i = $1.to_i # return i >= 0 ? [2147483647, i].min : [-2147483648, i].max # end 0
おさらい
# @param {String} str # @return {Integer} def my_atoi(str) # 文字列から前後の空白を取り除く→空白で区切って配列に入れる arr = str.strip.split(/\s+/) # 配列の1つ目の値が頭に+-が0回か1回ついており、その後1回以上数字が連なるか? if arr[0] =~ /^([+-]?[0-9]+)/ # マッチした部分を取得して、Integerクラスに変換し、変数に格納。 i = $1.to_i # 格納した値が正の値であればmax値と比較して小さい方、負の値であればmin値と比較して大きい方を返す return i >= 0 ? [2147483647, i].min : [-2147483648, i].max end # 一致しなければ0を返す 0 end
うーん、素晴らしい。