本記事は以下の記事の続編です。
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_count
とhigh_count
からposition_one
とposition_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_count
とhigh_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)
上のコードは、上手く別の表現を使っている(訳注: Array
のone?
メソッドを使って処理している)以外は、排他的論理和(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日目はこれで終わりですが、今後数日ないしは数週間かけてそれぞれの問題に取り組み、解決策や方法論を探っていきたいと思います。
私のオリジナル解答をすべてご覧になりたい方は、完全なコメント付きの以下のリポジトリをどうぞ。
概要
原著者の許諾を得て翻訳・公開いたします。
今回の出題: Day 2: Password Philosophy - Advent of Code 2020