Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Ruby 3.0でアドベント問題集を解く(2日目-2)パスワードの別の理念(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

今回の出題: Day 2: Password Philosophy - Advent of Code 2020

本記事は以下の記事の続編です。

Ruby 3.0でアドベント問題集を解く(2日目-1)パスワードの理念(翻訳)

Ruby 3.0でアドベント問題集を解く(2日目-2)パスワードの別の理念(翻訳)

あらゆるプログラミングと同様、仕様には変更がつきものです。ここでは、パスワードが有効であることを解析する方法が変更されます。

(訳注: 2つの)数値は、実際にはパスワードの位置を表します。ここでは、パスワードの位置を表す数値の片方だけに目的の文字が存在し、かつ両方には存在していないことをチェックしたいと思います。

# 以下は有効(位置1にaがあり、位置3にはaがない)
1-3 a: abcde

# 以下は無効(位置1にも位置3にもbがない)
1-3 b: cdefg

# # 以下は無効(位置2にも位置9にもcがある)
2-9 c: ccccccccc

これに対応するには、前回の関数を若干変更することになります。

正規表現を変更する

意図が変更されたので、キャプチャグループ名をlow_counthigh_countからposition_oneposition_twoにそれぞれ変更する必要があります。

PASSWORD_INFO = /
  # 行の冒頭
  ^

  # 入力全体を「input」でキャプチャする
  (?<input>

    # 1番目の位置を取得する
    (?<position_one>\d+)

    # ダッシュは無視する
    -

    # 続いて2番目の位置を取得する
    (?<position_two>\d+)

    # リテラルスペース
    \s

    # 対象となる文字を検索する
    (?<target_letter>[a-z]):

    \s

    # 行の残りの部分がパスワードになる
    (?<password>[a-z]+)

  # 入力終了
  )

  # 行終了
  $
/x

変更はわずかですが、命名は重要なので名前もしっかり変更しておきましょう。

有効なパスワード

それでは、メインの関数でどこを変更する必要があるかを見てみましょう。

def valid_passwords(input)
  input.filter_map do
    extracted_password = extract_password(_1) or next

    extracted_password => {
      input:, position_one:, position_two:, target_letter:, password:
    }

    position_one = position_one.to_i - 1
    position_two = position_two.to_i - 1

    char_one, char_two = password[position_one], password[position_two]

    input if [char_one, char_two].one?(target_letter)
  end
end

パターンマッチング用の名前

ここでは、low_counthigh_countを、位置を表す新しい名前に変更する必要があります。

extracted_password => {
  input:, position_one:, position_two:, target_letter:, password:
}

添字にLuaを使っている人がいるのかな?

今回の仕様では、添字(index)に0ではなく1を使うよう指示されているので、位置にオフセットを与えて補正する必要があります。

position_one = position_one.to_i - 1
position_two = position_two.to_i - 1

訳注

参考: 配列とテーブル - Dolphin TAS制作@wiki - atwiki(アットウィキ) — Lua言語ではテーブルの添字は1から始まります。

文字を取り出す

続いて、それらの位置にある文字をそれぞれ取得したいと思います。

char_one, char_two = password[position_one], password[position_two]

「ワンライナーで多重代入を使うと顰蹙を買うでしょうか?」おそらく。「この多重代入は動くのでしょうか?」はい。

バリデーション

ここでは、数値をチェックするのではなく、2つの文字の片方「だけ」が該当していることを確認したいと思います。

input if [char_one, char_two].one?(target_letter)

上のコードは、上手く別の表現を使っている(訳注: Arrayone?メソッドを使って処理している)以外は、排他的論理和(XOR)として知られた概念です。しかしRubyでは以下のように^記号でXORを表現できます。

true ^ true
# => false

true ^ false
# => true

false ^ true
# => true

false ^ false
# => false

ただし^を使う場合は演算子の優先順位に注意しましょう。以下のように書くとうまくいきません。

input if char_one == target_letter ^ char_two == target_letter

(訳注: ^は)||(OR)と&&(AND)と挙動が異なるので、これはもしかするとバグではないかと個人的に感じていますが、理由についてはわかりません。ともあれ、上の^は以下のように()で囲むことで修正できます。

input if (char_one == target_letter) ^ (char_two == target_letter)

関数のその他の部分はそのままでよいので、これで要件を満たせるようになりました。

訳注

参考: 演算子式 (Ruby 3.0.0 リファレンスマニュアル) — 演算子の優先順位

2日目のまとめ

2日目はこれで終わりですが、今後数日ないしは数週間かけてそれぞれの問題に取り組み、解決策や方法論を探っていきたいと思います。

私のオリジナル解答をすべてご覧になりたい方は、完全なコメント付きの以下のリポジトリをどうぞ。

baweaver/advent_of_code_2020 - GitHub

関連記事

Ruby 3.0でアドベント問題集を解く(2日目-1)パスワードの理念(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。