Techioz Blog

Ruby と Python の正規表現の違い

概要

私は Advent of Code 2023 を開始したばかりで、これを使用していくつかの新しいプログラミング言語を学習しようとしています。私は Python に(ある程度)精通していて、文字通り今日 Ruby をインストールしたばかりです。

1 日目、パート 2 では、正規表現を使用して数字とそのスペル表記されたバージョンを検索します。 Python の正規表現 (正しい結果が得られます): (?=(0|1|2|3|4|5|6|7|8|9|ゼロ|1|2|3|4|5|6|7|8|9))

Ruby でこの正確な正規表現を使用すると、結果は nil になります。興味深いことに、この正規表現を使用すると、Python と Ruby の両方でまったく同じ結果が得られますが、これは間違った答えです。 r”0|1|2|3|4|5|6|7|8|9|ゼロ|1|2|3|4|5|6|7|8|9”

したがって、答えは前向き先読みアサーションに関係していると思いますが、なぜ、何が違うのかはわかりません。

以下に両方のファイルを示します。

パイソン:

import re

input = open("../resources/input.txt","r")
lines = input.readlines()

targets = [
    '0','1','2','3','4','5','6','7','8','9',
    'zero','one','two','three','four','five','six','seven','eight','nine'
]
values = {
    '0': 0,
    '1': 1,
    '2': 2,
    '3': 3,
    '4': 4,
    '5': 5,
    '6': 6,
    '7': 7,
    '8': 8,
    '9': 9,
    'zero': 0,
    'one': 1,
    'two': 2,
    'three': 3,
    'four': 4,
    'five': 5,
    'six': 6,
    'seven': 7,
    'eight': 8,
    'nine': 9
}

sum = 0

for line in lines:
    numbers = re.findall(r"(?=("+'|'.join(targets)+r"))", line)

    firstDigitValue = values[numbers[0]] * 10
    lastDigitValue = values[numbers[-1]]

    sum += (firstDigitValue+lastDigitValue)



print(sum)

ルビィ:

# Init vars
sum = 0

reg = /\d|zero|one|two|three|four|five|six|seven|eight|nine/
reg2 = /(?=(0|1|2|3|4|5|6|7|8|9|zero|one|two|three|four|five|six|seven|eight|nine))/
reg3 = /0|1|2|3|4|5|6|7|8|9|zero|one|two|three|four|five|six|seven|eight|nine/

values = {
    '0' => 0,
    '1' => 1,
    '2' => 2,
    '3' => 3,
    '4' => 4,
    '5' => 5,
    '6' => 6,
    '7' => 7,
    '8' => 8,
    '9' => 9,
    'zero' => 0,
    'one' => 1,
    'two' => 2,
    'three' => 3,
    'four' => 4,
    'five' => 5,
    'six' => 6,
    'seven' => 7,
    'eight' => 8,
    'nine' => 9
}


# Pipe the file line by line and do per line
File.foreach("../resources/input.txt", chomp: true) do |line|
    # Get the first and last digits as their values
    numbers = line.scan(reg3)

    firstDigitValue = values[numbers[0]] * 10
    lastDigitValue = values[numbers[-1]]

    # accumulate
    sum += (firstDigitValue+lastDigitValue)

end

puts sum

解決策

0|1|2|3|4|5|6|7|8|9|zero|one|two|three|four|five|six|seven|eight|nine

この正規表現の問題は、Python と Ruby の両方で、重複する一致を考慮していないことです。私も今月初めにこの問題をやって全く同じ間違いを犯しました。たとえば、パズルの入力に「eighttwo」という語句が現れた場合、Python と Ruby は両方とも「eight」の部分に一致し、「w」の部分でさらに一致するものを探し始めるため、「two」という単語は表示されません。 。

(?=(0|1|2|3|4|5|6|7|8|9|zero|one|two|three|four|five|six|seven|eight|nine))

これにより、一致全体を先読みすることで問題が解決されます (おそらく効率的ではありませんが、コーディングの課題を行っているので十分です)。重複を考慮する場合、先読みはパターンの一部とはみなされないため、基本的には中断したところから検索を開始します。

ただし、Ruby では、正規表現にキャプチャ グループがある場合、String#scan の動作が異なります。

したがって、出力は実際には次のようになります

[["4"], ["one"], ["eight"], ["nine"]]

この追加の入れ子層に対処する必要があるだけです。

first_digit_value = values[numbers[0][0]] * 10
last_digit_value = values[numbers[-1][0]]