14. Регулярни изрази

14. Регулярни изрази

14. Регулярни изрази

30 ноември 2015

Контролно

Днес

/find me/.match 'Can you find me here?' # => #<MatchData "find me">

Кой...?

Stand back...

Wait, forgot to escape a space. Wheeeeee[taptaptap]eeeeee.

Знам регулярни изрази!

За просветените в тайнството...

Традиционната задача

за тези от вас, които се чувстват комфортно с РИ

Имаме следната задача:

Да се напише кратък Ruby expression, който проверява дали дадено число е просто или не, посредством употреба на регулярен израз. Резултатът от изпълнението му трябва да е true за прости числа и false за всички останали. Неща, които можете да ползвате:
  • Самото число, разбира се.
  • Произволни методи от класа Regexp
  • Подходящ регулярен израз (шаблон)
  • Текстовия низ '1'.
  • String#*.
  • Някакъв условен оператор (например if-else или ? … : …)
  • true, false, ...

И още една задача

за тези от вас, които вече ни знаят номерата

Имаме следната задача:

Да валидирате изрази от следния тип за правилно отворени/затворени скоби:
  • (car (car (car ...)))
  • Например: (car (car (car (car list))))
  • Целта е израз, чийто резултат да може да се ползва в условен оператор (true/false-еквивалент)
  • Можете да ползвате произволни методи от класа Regexp
  • И регулярен израз, разбира се

И още

за тези от вас, които вече ни знаят номерата

Сменете * на % ако тя не е екранирана (escape-ната)
  • foo* => foo%
  • foo\* => foo\*
  • foo\\* => foo\\%
  • *\** => %\*%
  • Може и да стане без look-ahead/behind/... :)

MOAR, MOAR!!!!111!

за тези от вас, които вече ни знаят номерата

Проверете дали нещо е валиден математически израз
  • Произволно число цяло 1337
  • Променлива (латинска малка буква) x
  • Операция между валидни изрази (+, -, *, /) x + y - 21 / 3
  • Скоби, ограждащи валидни изрази -x * (y + -5 * (7 - 13)) / 44 - 9000

Disclaimer

Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems.

...summons tainted souls into the realm of the living...

will liquify the n​erves of the sentient whilst you observe, your psyche withering in the onslaught of horror

Произход

малко обща култура

Проблемна област

най-общо: работа с текстови низове

Понятия

и терминология

РИ в Ruby

синтаксис, накратко

Regexp#match

ще го ползваме в примерите

Шаблони

(регулярни изрази, patterns и т.н.)

Най-прост пример

/find me/.match 'Can you find me here?' # => #<MatchData "find me">
/find me/.match 'You will not find ME!' # => nil

Специални символи

meta characters

/day|nice/.match  'A nice dance-day.'  # => #<MatchData "nice">
/da(y|n)ce/.match 'A nice dance-day.'  # => #<MatchData "dance" 1:"n">

Внимавайте с приоритета на |

Екраниране

на специалните символи (escape-ване)

Класове от символи

(character classes)

Примери с класове от символи

/W[aeiou]rd/.match "Word" # => #<MatchData "Word">
/[0-9a-f]/.match '9f'     # => #<MatchData "9">
/[9f]/.match     '9f'     # => #<MatchData "9">
/[^a-z]/.match   '9f'     # => #<MatchData "9">

Предефинирани класове от символи

POSIX-класове от символи

Полезни не-POSIX класове

Символни свойства

character properties

/\s\p{Cyrillic}\p{Cyrillic}\p{Cyrillic}/.match 'Ние сме на всеки километър!' # #<MatchData " сме">

Котви

Примери с котви

/real/.match "surrealist"    # => #<MatchData "real">
/\Areal/.match "surrealist"  # => nil
/\band/.match "Demand"       # => nil

/\Band.+/.match "Supply and demand curve" # => #<MatchData "and curve">

Повторители

(quantifiers)

Примери с повторители

/e+/.match     'Keeewl'       # => #<MatchData "eee">
/[Kke]+/.match 'Keeewl'       # => #<MatchData "Keee">
/\w+/.match '2038 - the year' # => #<MatchData "2038">
/".*"/.match '"Quoted text!"' # => #<MatchData "\"Quoted text!\"">

/[[:upper:]]+[[:lower:]]+l{2}o/.match 'Hello' # => #<MatchData "Hello">

Алчност

и лакомия...

/<.+>/.match("<a><b>")  # => #<MatchData "<a><b>">
/<.+?>/.match("<a><b>") # => #<MatchData "<a>">

Групи

и прихващане

Символите ( и ) се използват за логическо групиране на части от шаблона с цел:

Референции към групи

Текстът, който match-ва частта на шаблона, оградена в скоби, може да се достъпва:

Референции към групи

извън шаблона, за номерирани групи, през MatchData

date_string = '2015-11-30'
date_parts  = /\A(\d{4})-(\d\d)-(\d\d)\z/.match(date_string)

if date_parts
  Date.new date_parts[1].to_i, date_parts[2].to_i, date_parts[3].to_i
  # #<Date: 2015-11-30 ...>
end

Пропускане на уловени групи

/(\d+)(st|nd|rd|th)? (\w+)/.match '1st June' # => #<MatchData "1st June" 1:"1" 2:"st" 3:"June">
/(\d+)(?:st|nd|rd|th)? (\w+)/.match '1st June' # => #<MatchData "1st June" 1:"1" 2:"June">
/(\d+)(st|nd|rd|th)? (\w+)/.match '1 June' # => #<MatchData "1 June" 1:"1" 2:nil 3:"June">
/(\d+)(?:st|nd|rd|th)? (\w+)/.match '1 June' # => #<MatchData "1 June" 1:"1" 2:"June">

if с регулярни изрази

if с регулярни изрази

пример

log_entry = "[2011-07-22 15:42:12] - GET / HTTP/1.1 200 OK"

if log_entry =~ /\bHTTP\/1\.1 (\d+)/
  request_status = $1.to_i # => 200
else
  raise "Malformed log entry!"
end

Референции към групи

извън шаблона, за номерирани групи, през $1, $2...

date_string = '2015-11-30'

if date_string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
  Date.new $1.to_i, $2.to_i, $3.to_i # #<Date: 2015-11-30 ...>
end

Именовани групи

/(?<date>\d{4}-\d{2}-\d{2})/.match 'Today is 2011-11-08, Tuesday.' # => #<MatchData "2011-11-08" date:"2011-11-08">

Референции към групи

в рамките на шаблона

Примери за референции към групи

/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/.match 'Today is 2011-11-08, Tuesday.'
# #<MatchData "2011-11-08" year:"2011" month:"11" day:"08">

/(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\11/.match 'Regular expressions'
# #<MatchData "ular express" 1:"u" 2:"l" 3:"a" 4:"r" 5:" " 6:"e" 7:"x" 8:"p" 9:"r" 10:"e" 11:"s">
/(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\k<11>1/.match 'Regular express1ions'
# #<MatchData "ular express1" 1:"u" 2:"l" 3:"a" 4:"r" 5:" " 6:"e" 7:"x" 8:"p" 9:"r" 10:"e" 11:"s">

Уточнение относно референциите

в рамките на шаблона

/(\w+), \1/.match 'testing, testing' # => #<MatchData "testing, testing" 1:"testing">
/(\w+), \1/.match 'testing, twice'   # => nil

/(?<word>\w+), \k<word>/.match 'testing, testing' # => #<MatchData "testing, testing" word:"testing">

Backtracking

/".*"/.match '"Quoted"' # => #<MatchData "\"Quoted\"">

Частта от шаблона .* хваща Quoted", тъй като е алчна. Това води до невъзможност да се намери съвпадение и алгоритъмът backtrack-ва -- връща се една стъпка/символ назад.

Атомарни (неделими) групи

/"(?>.*)"/.match('"Quote"') # => nil

Рекурсивни групи

/(\w+), \1/.match    'testing, twice'   # => nil
/(\w+), \g<1>/.match 'testing, twice'   # => #<MatchData "testing, twice" 1:"twice">

Рекурсивни групи

втора част

Да валидирате изрази от следния тип за правилно отворени/затворени скоби:
  • (car (car (car ...)))
  • Например: (car (car (car (car list))))
  • Целта е израз, чийто резултат да може да се ползва в условен оператор (true/false-еквивалент)
  • Можете да ползвате произволни методи от класа Regexp
  • И регулярен израз, разбира се

Примерно решение

с рекурсивни групи

validator = /^(\(car (\g<1>*|\w*)\))$/

valid   = '(car (car (car (car list))))'
invalid = '(car (car (car list))'

validator.match(valid)   ? true : false # => true
validator.match(invalid) ? true : false # => false

MOAR, MOAR!!!!111!

Проверете дали нещо е валиден математически израз
  • Произволно цяло число 1337
  • Променлива (малка латинска буква) x
  • Знак пред валиден израз (+, -) -33 + 22 * -y
  • Операция между валидни изрази (+, -, *, /) x + y - 21 / 3
  • Скоби, ограждащи валидни изрази -x * (y + -5 * (7 - 13)) / 44 - 9000

Примерно решение

so simple, right?

validator = /^([-+]?(\d+|[a-z]|\(\g<1>\)|\g<1> [-+*\/] \g<1>))$/

valid   = '-(3 + (x * (7 / y))) * (44 * y - z / 22)'
invalid = '((33 - 7) * x'

validator.match(valid)   ? true : false
validator.match(invalid) ? true : false

Примерно решение

nope... fail!

/^([-+]?(\d+|[a-z]|\(\g<1>\)|\g<1> [-+*\/] \g<1>))$/# ~> -:1: never ending recursion: /^([-+]?(\d+|[a-z]|\(\g<1>\)|\g<1> [-+*\/] \g<1>))$/

Примерно решение

с рекурсивни групи

validator = /^([-+]?(\d+|[a-z]|\(\g<1>\))( [-+*\/] \g<1>)?)$/

valid   = '-(3 + (x * (7 / y))) * (44 * y - z / 22)'
invalid = '((33 - 7) * x'

validator.match(valid)   ? true : false # => true
validator.match(invalid) ? true : false # => false

Look-ahead и look-behind

/(?<=<b>)\w+(?=<\/b>)/.match("Fortune favours the <b>bold</b>") # => #<MatchData "bold">

Пример

Сменете * на % ако тя не е екранирана (escape-ната)
  • foo* => foo%
  • foo\* => foo\*
  • foo\\* => foo\\%
  • *\\** => %\\*%

Първи начин

"*\\**".gsub(/((?<!\\)(?:\\\\)*)\*/, '\1%') # => "%\\*%"

Втори начин

"*\\**".gsub(/\G([^*\\]*(?:\\.[^*\\]*)*)\*/, '\1%') # => "%\\*%"

Интерполация в регулярни изрази

Между другото, регулярните изрази поддържат интерполация:

name      = /[^@]+/
host      = /\w+\.(com|net|org)/

email     = /#{name}@#{host}/ # => /(?-mix:[^@]+)@(?-mix:\w+\.(com|net|org))/

Работа с MatchData-обекти

Най-полезни методи на MatchData-обектите

/(\w+)/.match('Some words')[1]              # => "Some"
/(\w+)/.match('Some words').begin(1)        # => 0
/(?<id>\d+)/.match('ID: 12345')[:id]        # => "12345"
/(?<id>\d+)/.match('ID: 12345').begin(:id)  # => 4

#pre_match и #post_match методи

на MatchData-обектите

match = /(?<number>\d+)/.match 'ID: 12345 (new)'

match[:number]    # => "12345"
match.pre_match   # => "ID: "
match.post_match  # => " (new)"

Специалните променливи

case с регулярни изрази

работи благодарение на Regexp#===

html = '<h1>Header</h1>' # или:
html = '<img src="http://my/image.src" alt="Kartman Makes Burgers" />'

case html
  when /(<h(\d)>)(.+)<\/h\2>/
    {header: $3, size: $2}
  when /<a\s+href="([^"]+)">([^<]+)<\/a>/
    {url: $1, text: $2}
  when /<img\s+src="([^"]+)"\s+alt="([^"]+)"\s*\/>/
    {image: $1, alt: $2}
  else
    'unrecognized tag'
end

# {:image=>"http://my/image.src", :alt=>"Kartman Makes Burgers"}

Методи в String

свързани с регулярни изрази

Пример със String#gsub

плюс групи и блок

'SomeTitleCase'.gsub /(^|[[:lower:]])([[:upper:]])/ do
  [$1, $2.downcase].reject(&:empty?).join('_')
end

# "some_title_case"

Encoding

Цитат от документацията:

A regexp can be matched against a string when they either share an encoding, or the regexp’s encoding is US-ASCII and the string’s encoding is ASCII-compatible.

Encoding & Unicode

Rubyのお父さんはまつもとゆきひろさんです。
unicode_test = 'Rubyのお父さんはまつもとゆきひろさんです。'

/は[[:alpha:]]+さん/.match unicode_test # #<MatchData "はまつもとゆきひろさん">

Граници на думи в Unicode-текст

Граници на думи в Unicode-текст

пример

Например:

'Ruby no otousan ha Matsumoto Yukihiro san desu.'.gsub(/(\b[[:alpha:]]+\b)/) { "[#{$1}]" }
# "[Ruby] [no] [otousan] [ha] [Matsumoto] [Yukihiro] [san] [desu]."

Но:

'Rubyのお父さんはまつもとゆきひろさんです。'.gsub(/(\b[[:alpha:]]+\b)/) { "[#{$1}]" }
# "[Rubyのお父さんはまつもとゆきひろさんです]。"

Флагове на шаблоните

Условия в шаблоните (if)

Ново в Ruby 2.0

regexp = /^(number)?\s*(?(1)\d+|[a-zA-Z]+)$/

regexp =~ "number 123"   # => 0
regexp =~ "foo"          # => 0
regexp =~ "number baz"   # => nil

Документация

Валидация на просто число с регулярен израз

Да се напише кратък Ruby expression, който проверява дали дадено число е просто или не, посредством употреба на регулярен израз. Резултатът от изпълнението му трябва да е true за прости числа и false за всички останали. Неща, които можете да ползвате:
  • Самото число, разбира се.
  • Произволни методи от класа Regexp
  • Подходящ регулярен израз (шаблон)
  • Текстовия низ '1'.
  • String#*.
  • Някакъв условен оператор (например if-else или ? … : …)
  • true, false, ...

Решение на проверката за просто число с РИ

Въпроси?

Like any...

Въпроси