12. Регулярни изрази, част 1

12. Регулярни изрази, част 1

12. Регулярни изрази, част 1

27 ноември 2013

Идната седмица

Първи тест

Днес

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
  • И регулярен израз, разбира се

Disclaimer

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

Една забавна история

...за ножове с много остриета

Симптомите и първи стъпки на дебъгване

Причината

Причината (2)

Текстуално илюстриране на света и змията:

..................................................
..................................................
..................##@@............................
..................##..............................
..................######..........................
..................................................
..................................................

Виновникът

Изводи

Да пишеш софтуер не е лесна работа...

Произход

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

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

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

Понятия

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

РИ в 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">

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

Просто число с регулярен израз

Следва продължение

Следващата седмица ще довършим темата за регулярните изрази.

Въпроси