連続した文字を抽出する正規表現とガラケー文字入力問題(応用)の解答

前回の記事の問題を解いている過程で、"連続した数字を取り出さなければ"と思い、凝った正規表現を作ってしまったので、それを使える問題を作成しました。

★前回の記事

yucatio.hatenablog.com

こちらの記事のガラケー文字入力問題の応用編を作成しました。

blog.jnito.com

問題

英語のガラケーでは「2」キーを2回押すと「b」になり、「3」キーを3回押すと「f」になります。

文字を確定するには、'0'または別の数字を入れることで確定されます。

たとえば、このプログラムに対して"443355505556660"を入力すると、"hello"が返ってきます。


解答

class FeaturePhone
  BUTTONS = {
    '1' => ['.', ',', '!', '?', ' '],
    '2' => %w[a b c],
    '3' => %w[d e f],
    '4' => %w[g h i],
    '5' => %w[j k l],
    '6' => %w[m n o],
    '7' => %w[p q r s],
    '8' => %w[t u v],
    '9' => %w[w x y z]
  }.freeze

  def digits_to_alphabet(digits_line)
    digits_line.scan(/(([1-9])\2*)/).map {|digits, |
      button_values = BUTTONS[digits[0]]
      button_values[(digits.length - 1) % button_values.length]
    }.join
  end
end

テストコードです。

require 'rspec'
require File.expand_path(File.dirname(__FILE__) + '/../hello/feature_phone')

describe FeaturePhone do
  describe '#digits_to_alphabet_non_zero' do
    subject { phone.digits_to_alphabet(digits) }
    let(:phone){ FeaturePhone.new }

    context '2のボタンが1回押された場合' do
      let(:digits) { '20' }
      it { is_expected.to eq 'a'}
    end

    context '4, 3, 5, 6のボタンが2回か3回押され、毎回0で確定された場合' do
      let(:digits) { '440330555055506660' }
      it { is_expected.to eq 'hello'}
    end

    context '1, 4, 3, 5, 6, 7, 9のボタンが押され、途中に0がない場合' do
      let(:digits) { '4433555055566611011111090666077755531110' }
      it { is_expected.to eq 'hello, world!'}
    end

    context '0が複数回登場し、5のボタンが8回押されたケースが含まれる場合' do
      let(:digits) { '000555555550000330000444000080000200004440000' }
      it { is_expected.to eq 'keitai'}
    end

    context '0が複数回登場し、5のボタンが8回押されたケースが含まれる場合' do
      let(:digits) { '0005555555533444824440000' }
      it { is_expected.to eq 'keitai'}
    end
  end
end

解説

1から9の数字で同じ数字が連続している部分を抜き出します。[1-9]+では、443355などにマッチしてしまい、同じ数字の連続を取り出すことができません。

同じ数字の連続を取り出すのには、後方参照を使用します。後方参照は、カッコでキャプチャした部分を、\1の形式で正規表現中で参照できる機能です。同じ数字が2回連続で出現する正規表現([0-9])\1で表すことができます。今回は、1から9の数字の1回以上の連続を取り出したいので、正規表現([1-9])\1*となります。

String.scan( scan (String) - Rubyリファレンス )で正規表現にマッチする部部分を繰り返し取得します。

ここまでをコードにしていきます。

class FeaturePhone
  def digits_to_alphabet(digits_line)
    digits_line.scan(/([1-9])\1*/)
  end
end

入力が0005555555533444824440000の場合の実行結果です。連続した部分の先頭の文字列が取得できました。これはカッコでキャプチャした部分です。

[["5"], ["3"], ["4"], ["8"], ["2"], ["4"]]

マッチする部分全体を取得するため、キャプチャのカッコを追加します。後方参照の\1\2に変更します。

class FeaturePhone
  def digits_to_alphabet(digits_line)
    digits_line.scan(/(([1-9])\2*)/)
  end
end

入力が0005555555533444824440000の場合の実行結果です。連続した数字が取得できました。

[["55555555", "5"], ["33", "3"], ["444", "4"], ["8", "8"], ["2", "2"], ["444", "4"]]

連続した数字列と最初のの数字の2つをmapに渡します。 多重代入を使用して、配列の1番目と2番目をそれぞれ別の変数に代入します。

class FeaturePhone
  BUTTONS = {
    '1' => ['.', ',', '!', '?', ' '],
    '2' => %w[a b c],
    '3' => %w[d e f],
    '4' => %w[g h i],
    '5' => %w[j k l],
    '6' => %w[m n o],
    '7' => %w[p q r s],
    '8' => %w[t u v],
    '9' => %w[w x y z]
  }.freeze

  def digits_to_alphabet(digits_line)
    digits_line.scan(/(([1-9])\2*)/).map {|digits, digit|
      button_values = BUTTONS[digit]
      button_values[(digits.length - 1) % button_values.length]
    }.join
  end
end

そのほかの部分は前回の記事と一緒ですので、参考にどうぞ。

yucatio.hatenablog.com

出題者の伊藤淳一さんの書籍はこちら↓