Регулярные выражения
Разбор домашних заданий
Создайте модуль utils, в который положите функции tokenize, token_types (и token_type, который для неё нужен). Напишите программу, которая пользуется модулем utils, читает и токенизирует файл words.txt, и выводит на экран построчно: токен и его тип.
В файл utils.py копируем соответствующие функции из конспектов:
1 def tokenize(text):
2 chunks = text.split()
3 tokens = []
4 for chunk in chunks:
5 token = chunk.strip(u".,?!\"'-+")
6 if token != u"":
7 tokens.append(token)
8 return tokens
9
10 def token_type(token):
11 if token.isalpha():
12 if token.isupper():
13 return "upper"
14 if token.islower():
15 return "lower"
16 if token.istitle():
17 return "title"
18 return "mixed"
19 else:
20 if token.isalnum():
21 return "alnum"
22 else:
23 return "other"
24
25 def token_types(tokens):
26 types = []
27 for token in tokens:
28 types.append(token_type(token))
29 return types
После этого создаём нашу программу, например, tokenize.py, и пишем:
Напишите скрипт, который открывает файл words.txt, токенизирует, считает частоты слов, и выводит ответ в виде CSV-таблицы с двумя колонками: токен, частота.
Дописываем в конец модуля utils.py функцию counts_v4 из конспекта:
И создаём новый скрипт, который пользуется этим модулем:
1 import codecs
2 import utils
3
4 with codecs.open("words.txt", "r", "utf-8") as infile:
5 tokens = utils.tokenize(infile.read())
6
7 counts = utils.counts(tokens)
8
9 with codecs.open("word-counts.csv", "w", "utf-8") as outfile:
10 for token in counts:
11 frequency = counts[token]
12 line = "{0};{1}".format(token, frequency)
13 outfile.write(line)
Напишите функцию parse_exceptions, которая получает на вход CSV-файл с ровно двумя колонками (не нужно проверять, что это так), в первой колонке токен, во второй – тип токена, и возвращает словарь, который по токену возвращает его тип.
Пишется по обазу и подобию функций для разбора CSV-файла, и функций для преобразования списка.
1 def parse_exceptions(file):
2 result = {}
3 for line in file:
4 line = line.rstrip()
5 cells = line.split(";")
6 key = cells[0]
7 value = cells[1]
8 # всё тело цикла выше можно сократить до одной строки:
9 # key, value = line.rstrip().split(";")
10 # хотя на самом деле её поведение будет чуть-чуть отличаться -- вопрос на засыпку: в чём?
11 result[key] = value
12 return result
В некоторых решениях в функцию передавался не объект файла, а только имя файла.
Приятная особенность такого показанного подхода в том, что на самом деле в теле функции мы нигде не пользуемся тем, что это именно файл. Мы можем вместо него подсунуть питону список строк, и питон будет абсолютно доволен:
1 print parse_exceptions(["a;b\n", "c;d"])
4
Задача номер 4 имела самую расплывчатую и неудачную формулировку задания, и под условия подходило очень много разнообразных решений. Не буду здесь их разбирать.
Напишите функцию parse_dictionary, которая получает на вход CSV-файл с неограниченным числом колонок в каждой строке, и для каждой непустой строки возвращает словарь, в котором ключ – слово из первой колонки, значение – список слов из остальных колонок.
Решается почти дословно так же, как и третья:
Лучше всего дописать этот код туда же в utils.py.
Напишите функцию definition_pairs, которая принимает на вход словарь (ключ – определяемое слово, значение – список слов-определений) и возвращает список пар определяемое слово – определение.
Всё по тому же образу и подобию. Единственная сложность здесь в том, что нам нужно собирать данные не просто с самого словаря, а ещё и с каждого определения в статье – отсюда два вложенных цикла:
На языке питона она звучит почти дословно так же, как на русском, даже проще: для каждого определяемого из словаря, и для каждого его определения нужно дописать в выходной список пару из определяемого и определения.
И снова лучше бы дописать этот код туда же в utils.py.
Напишите функцию filter_counts, которая получает на вход словарь (ключ – текст находки, значение – количество находок) и число – минимальное количество находок, и возвращает список тех находок, которые встретились не меньше заданного количества раз.
Когда-то мы такое уже писали, с той маленькой разницей, что фильтровали не словарь, а список:
Как и прошлые задачи, пишем это в utils.py.
Соберите из решений задач 2, 5, 6, 7, решение задания, которое я давал в начале семестра на автомат.
Итого, у нас в utils.py есть полезного: parse_dictionary, definition_pairs, counts, filter_counts.
1 import codecs
2 import utils
3
4 # Нам нужно работать с многими файлами, положим их имена в список:
5 filenames = [...]
6 min_dictionaries = 3 # количество словарей, которые должны поддержать одно определение
7 max_definitions = 3 # количество определений из одной словарной статьи, которые мы используем
8
9 # Не разделённого на маленькие кусочки осталось ещё два шага.
10
11 # Шаг - где-то в середине: в прочитанном словаре нужно оставить только первые N слов определения:
12 def truncate_definitions(dictionary, max_definitions):
13 result = {}
14 for term in dictionary:
15 definition = dictionary[term]
16 result[term] = definition[:max_definitions]
17 return result
18
19 # Шаг 1: нужно прочитать все файлы и получить из них все пары определений:
20 def read_pairs(filenames):
21 pairs = []
22 for filename in filenames:
23 with codecs.open(filename, "r", "utf-8") as infile:
24 dictionary = utils.parse_dictionary(infile)
25 pairs = pairs + utils.definition_pairs(dictionary)
26 return pairs
27
28 # Шаг 2: найти только поддержанные пары:
29 def supported_pairs(pairs):
30 counts = utils.counts(pairs) # магия: наша функция counts умеет считать не только слова!
31 return filter_counts(counts, min_dictionaries)
32
33 # Осталось только вызвать нужные функции. Делаем это прилично:
34 if __name__ == "__main__":
35 pairs = supported_pairs(read_pairs(filenames))
36 for term, definition in pairs:
37 print term, definition
Мораль из этой истории такова: чем на более простые кусочки мы нарезаем задачу, тем проще в конечном итоге окажется её решить!
Регулярные выражения
Регулярные выражения – это способ обозначать множество строк. Он отличается от простого перечня строк:
во-первых, гораздо более компактной записью; например, список строк world, wOrld, w0rld обозначается регулярным выражением w[oO0]rld
во-вторых, возможностью задавать бесконечные множества; например, множество строк a, aa, aaa, aaaa, ... задаётся выражением a+
Выражение |
Синонимы |
Комментарий |
Примеры |
Vanya |
|
Выражение обозначает одну строку: Vanya |
+ Vanya |
Van[yj]a |
|
В квадратных скобочках указываются символы, которые могут стоять в данной позиции. При этом можно использовать тире для того, чтобы указывать последовательность символов, например: [0-9] (все цифры), [a-z] все маленькие буквы, [a-zA-Z] (все буквы) |
+ Vanya |
Van[^ji] |
|
Если первым символом после открывающей квадратной скобочки является ^, то в квадратных скобочках перечисляются символы, которые НЕ могут стоять в данной позиции, т.е. остальные все символы, кроме этих, могут. |
+ Vanya |
Van{3}ya |
Vannnya |
В фигурных скобочках указывается количество раз, которое повторяется непосредственно предшествующий символ |
+ Vannnya |
Van{1,3}ya |
|
В фигурных скобочках через запятую указывается минимальное и максимальное количество раз, которое повторяется непосредственно предшествующий символ |
+ Vannnya |
Van{2,}ya |
|
Если максимальное количество раз пропущено, то это значит что количество повторений сверху не ограничено и может быть бесконечным |
+ Vannya |
Van?ya |
Van{0,1}ya |
Знак вопроса означает, что непосредственно предшествующий символ необязательный |
+ Vaya |
Van*ya |
Van{0,}ya |
Звездочка означает 0 или большее количество повторений непосредственно предшествующего символа |
+ Vaya |
Van+ya |
Van{1,}ya |
Плюсик означает 1 или большее количество повторений непосредственно предшествующего символа |
- Vaya |
Va.ya |
|
Точка означает любой символ, кроме символа конец строки (\n) |
+ Va.ya |
Va\.ya |
|
Backslash «защищает» символ, который следует за ним, т.е. делает символ обычным. |
+ Va.ya |
(Va)+nya |
|
Скобочки используются для группировки |
+ Vanya |
Vanya|Kolya |
|
Выбор одного из нескольких вариантов |
+Vanya |
Van(ya|e) |
|
Выбор одного из нескольких вариантов и круглые скобки |
+Vanya |
Пояснение про конкатенацию
Когда мы пишем два регулярных выражения вплотную, мы совершаем между ними операцию конкатенации (та же самая конкатенация, которую делает операция плюс со строками в питоне – "a" + "b"). Конкатенация двух регулярных выражений обозначает следующее: мы берём каждый возможный вариант, подходящий под левое выражение и присоединяем к нему каждый возможный вариант, подходящий под правое выражение. Например: выражение 1|2 описывает строки 1 и 2; выражение a|b описывает строки a и b; выражение (a|b)(1|2) описывает строки a1, a2, b1, b2.
Несколько примеров
Чтобы обозначить множество всех дат, записанных через чёрточку, мы можем написать: [0-9]{2}-[0-9]{2}-{0-9]{4} – эта запись довольно простая (и примерно такими простыми записями я рекомендую вам пользоваться в жизни), но при этом в нашем множестве окажутся наряду с вполне допустимыми (например 24-10-2013), ещё и такие немыслимые даты, как 99-42-0000.
На примере номеров месяцев: мы можем заменить [0-9]{2} на множество чисел от 1 до 12. 0?[1-9]|1[0-2]. В этом множестве есть строки 01, 12, 9, но нет строк 001, 012, 13, 0, 99. То есть некоторым усложнением выражения мы можем добиться того, чтобы более точно соответствовать нашим пожеланиям. Во многих случаях не стоит увлекаться тем, чтобы слишком детализировать критерии искомого множества.
Ещё один пример. В самом грубом приближении задачу описания множества всех мнимых английских имён собственных мы можем решить так: [A-Z][a-z]+ – то есть просто как любое слово, начинающееся с большой буквы и содержащее не меньше одной маленькой буквы дальше.
Литература
http://regexone.com/ интерактивные уроки по регулярным выражениям
http://docs.python.org/2.7/library/re.html справочник по диалекту регулярных выражений, который использует python