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

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

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

12 ноември 2012

Днес

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

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

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

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

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

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

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

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

Произход

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

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

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

Понятия

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

РИ в 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 = '2012-11-12'
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: 2012-11-12 ...>
end

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 = '2012-11-12'

if date_string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
  Date.new $1.to_i, $2.to_i, $3.to_i # #<Date: 2012-11-12 ...>
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">

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

/(\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

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

Look-ahead и look-behind

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

Работа с 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"

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のお父さんはまつもとゆきひろさんです]。"

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

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

Въпроси