Регулярное выражение, совпадающее с читаемым человеком числом - PullRequest
0 голосов
/ 06 сентября 2018

У меня возникли проблемы с регулярным выражением, которое я пишу для одной из кодовых войн Katas (https://www.codewars.com/kata/parseint-reloaded/train/ruby)), и я надеялся, что меня здесь укажут в правильном направлении. Мне нужно регулярное выражение, которое может соответствовать английской, читаемой человеком числовой строке в любом месте от 1 до 999. Например: «один», «триста два», «пятьсот девяносто семь» и т. д.

Когда я сопоставляю с регулярным выражением, я бы хотел, чтобы совпадения появлялись в последовательных точках обратной ссылки. То, что я написал, более или менее работает в большинстве случаев, но обратные ссылки повсюду. Иногда, когда я сопоставляю «сотню». он составляет 3 доллара, а иногда - 6 долларов, и логика вытаскивания чисел запутывается. В других случаях одна и та же строка появляется дважды. Есть ли способ спасти это и сделать его лучше, или я должен укусить пулю и написать несколько регулярных выражений для разных случаев?

regex_test.rb

regex = "^((.+?)( hundred)? )?((.+)[ -])?(.+?)$"

test_cases = [
  'seven hundred ninety six',
  'six hundred twenty-two',
  'one hundred',
  'two hundred one',
  'sixty six',
  'one',
  'sixty'
]

test_cases.each do |test_case|
  puts test_case.match(regex).to_a.inspect
end

Выход:

["seven hundred ninety six", "seven hundred ", "seven", " hundred", "ninety ", "ninety", "six"]
["six hundred twenty-two", "six hundred ", "six", " hundred", "twenty-", "twenty", "two"]
["one hundred", "one ", "one", nil, nil, nil, "hundred"]
["two hundred one", "two hundred ", "two", " hundred", nil, nil, "one"]
["sixty six", "sixty ", "sixty", nil, nil, nil, "six"]
["one", nil, nil, nil, nil, nil, "one"]
["sixty", nil, nil, nil, nil, nil, "sixty"]

1 Ответ

0 голосов
/ 07 сентября 2018

Сначала создайте хеши, которые будут использоваться для преобразования строк в целые числа, и используйте ключи этих хешей для определения слов, которые могут появиться в строке для вставки в регулярное выражение.

units_to_digit = %w| one two three four five six seven eight nine |.
  zip((1..9).to_a).to_h
  #=> {"one"=>1, "two"=>2, "three"=>3, "four"=>4, "five"=>5, "six"=>6, "seven"=>7,
  #    "eight"=>8, "nine"=>9}
units = units_to_digit.keys.join('|')
  #=> "one|two|three|four|five|six|seven|eight|nine"

tens_to_digit = %w| twenty thirty forty fifty sixty seventy eighty ninety |.
  zip((2..9).to_a).to_h
  #=> {"twenty"=>2, "thirty"=>3, "forty"=>4, "fifty"=>5, "sixty"=>6, "seventy"=>7,
  #    "eighty"=>8, "ninety"=>9}
tens = tens_to_digit.keys.join('|')
  #=> "twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety"

teens_to_digit =
  %w| ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen |.
  zip((10..19).to_a).to_h
  #=> {"ten"=>10, "eleven"=>11, "twelve"=>12, "thirteen"=>13, "fourteen"=>14,
  #    "fifteen"=>15, "sixteen"=>16, "seventeen"=>17, "eighteen"=>18, "nineteen"=>19}
teens = teens_to_digit.keys.join('|')
  #=>  "ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen"

(В качестве альтернативы можно написать units = Regexp.union(units_to_digit.keys) и аналогичные для tens и teens. См. Regexp :: union .)

Затем создайте регулярное выражение, используя именованные группы захвата. (Я использовал режим свободного пробела для целей документирования. Классы символов, содержащие один пробел ([ ]), могут быть заменены пробелом, если режим свободного пробела не используется.)

regex = /
        \A                            # match beginning of string
        (?:                           # begin a non-capture group
          (?<nbr_hundreds>#{units})   # match nbr of hundreds, named 'nbr_hundreds'
          [ ]hundred                  # match ' hundred'        
        )?                            # close non-capture group and make optional 
        [ ]?                          # optionally match a space
        (?:                           # begin non-capture group
          (?:                         # begin a non-capture group 
            (?<tens>#{tens})          # match 'twenty' to 'ninety', named 'tens'
            (?:                       # begin non-capture group
              [ -]                    # match a space or hyphen
              (?<tens_units>#{units}) # match units, named 'tens_units'
            )?                        # close non-capture group and make optional
          )                           # close non-capture group
          |                           # or
          (?<units>#{units})          # match '1-9', named 'units'
          |                           # or
          (?<teens>#{teens})          # match 'ten', 'eleven',...'nineteen'
        )?                            # close non-capture group and make optional
        \z                            # match end of string
        /x                            # free-spacing regex definition mode   

  #=>   /
  #     \A
  #     (?:
  #       (?<nbr_hundreds>one|two|three|four|five|six|seven|eight|nine)
  #       [ ]hundred
  #     )?
  #     [ ]?
  #     (?:
  #       (?: 
  #         (?<tens>twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety)
  #         (?:
  #           [ -]
  #           (?<tens_units>one|two|three|four|five|six|seven|eight|nine)
  #         )?
  #       )
  #       |
  #       (?<units>one|two|three|four|five|six|seven|eight|nine)
  #       |
  #       (?<teens>ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen)
  #     )?
  #     \z
  #     /x

str.match(regex) вернет MatchData объект m. Значения групп захвата будут m[:nbr_hundreds], m[:tens], m[:tens_units], m[:units] и m[:teens]. Каждый будет равен nil, когда нет совпадений. (Например, m[:nbr_hundreds] будет равно nil, когда str = "one".) Удобно просто рассматривать эти нули как нули. Простой способ сделать это - добавить пару ключ-значение nil=>0 к каждому из хэшей units_to_digit, tens_to_digit и teens_to_digit:

units_to_digit[nil] = 0
tens_to_digit[nil] = 0
teens_to_digit[nil] = 0

Теперь создайте метод, который преобразует объект MatchData в целое число.

def match_data_to_integer(units_to_digit, tens_to_digit, teens_to_digit, m)
  100 * units_to_digit[m[:nbr_hundreds]] +
  10  * tens_to_digit[m[:tens]] +
  teens_to_digit[m[:teens]] +
  units_to_digit[m[:tens_units]] +
  units_to_digit[m[:units]]
end

Давайте теперь проверим это на некоторых строках.

test_cases = [
  'seven hundred ninety six',
  'six hundred twenty-two',
  'one hundred',
  'two hundred one',
  'sixty six',
  'one',
  'sixty'
]

test_cases.each do |test_case|
  m = test_case.match(regex)
  n = match_data_to_integer(units_to_digit, tens_to_digit, teens_to_digit, m)
  puts "#{test_case} -> #{n}"
end

печать

seven hundred ninety six -> 796
six hundred twenty-two -> 622
one hundred -> 100
two hundred one -> 201
sixty six -> 66
one -> 1
sixty -> 60
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...