スコーンの開発日記

開発中の学びをまとめていく。

LeetCode復習 (8. String to Integer (atoi))

最近LeetCodeをコツコツやっている。

こちらの問題で、自分では数十行書いていた処理が他の人の解答ではたったの6行で感動した。 後学のために、処理内容を1行ずつ見ていく。

leetcode.com

問題

StringをIntegerに変換する。

  • 数値は正負の符号を含む
  • 文字列には数値以外に単語が含まれることもある
  • 文字列は空白を含む
  • 空白を除く最初の文字が数値ではない場合、0を返す
  • 数値は符号付きで32ビットで表現するため、[−2 ** 31, 2 ** 31 − 1]の範囲を超える場合は、INT_MAX (2 ** 31 − 1) or INT_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.

leetcode.com

解答

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

leetcode.com

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 番目の括弧にマッチした値が格納されます。

variable $1 (Ruby 2.6.0)

へーー知らんかった。 つまりこういうこと。

>'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

うーん、素晴らしい。

参考

docs.ruby-lang.org

docs.ruby-lang.org

docs.ruby-lang.org