Модуль 6 · Урок 6.5
Записи чисел, биты и текстовые шаблоны
int с основанием, bin, oct, hex и format, битовые операции, маски fnmatch и регулярные выражения re.
В задачах ЕГЭ одно и то же число встречается в разных записях: 47 и двоичное 101111 — одно число. Нужно переводить запись в число и обратно, считать единицы двоичной записи и проверять, подходит ли запись под маску вроде 1?2*. Число и его запись — разные вещи: Python переводит одно в другое, работает с отдельными двоичными разрядами и проверяет запись по шаблону.
Двоичная запись устроена так же, как десятичная из урока о числах, только веса разрядов другие. Начнём с перевода числа 47.
Системы счисления
Запись числа зависит от основания, а int(text, base) переводит запись в число. «Двоичным» или «десятичным» бывает не число, а его запись. В записи с основанием base разряды справа налево имеют веса 1, base, base ** 2 и так далее. Например, 1011 в двоичной записи — это 1 · 8 + 0 · 4 + 1 · 2 + 1 · 1 = 11:
print(int("1011", 2))print(int("101111", 2))print(int("FF", 16))print(bin(47), oct(47), hex(255))print(bin(47)[2:])int(text, base) разбирает запись в заданном основании. bin, oct и hex строят двоичную, восьмеричную и шестнадцатеричную записи с префиксами 0b, 0o, 0x; срез [2:] убирает префикс у неотрицательного числа.
Как получается сама запись? Последний разряд — остаток n % 2, остальные разряды хранит n // 2 — так же в уроке о while снимали десятичные цифры через % 10 и // 10. Пройди перевод 47 по шагам:
- n = 47: остаток 1 становится новой цифрой слева, дальше делим 47 // 2 = 23.
- n = 23: остаток 1 становится новой цифрой слева, дальше делим 23 // 2 = 11.
- n = 11: остаток 1 становится новой цифрой слева, дальше делим 11 // 2 = 5.
- n = 5: остаток 1 становится новой цифрой слева, дальше делим 5 // 2 = 2.
- n = 2: остаток 0 становится новой цифрой слева, дальше делим 2 // 2 = 1.
- n = 1: остаток 1 становится новой цифрой слева, дальше делим 1 // 2 = 0.
- n = 0 — цикл закончен. Остатки, записанные справа налево, дают 101111.
Для любого основания то же: остаток n % base — новая цифра слева, дальше n // base. Подробно перевод разобран в теме 14.
format(n, "b") даёт двоичную запись без префикса, а format(n, "08b") — дополненную нулями слева до восьми знаков:
print(format(47, "b"))print(format(5, "08b"))print(format(255, "x"))Тонкости:отрицательные числа
bin(-5) — это '-0b101', и срез [2:] оставит 'b101'. Знак обрабатывают отдельно: переводят abs(n) и добавляют минус.
Битовые операции
Битовые операции работают с каждым двоичным разрядом отдельно. Бит — двоичный разряд. Операция сравнивает биты, стоящие друг под другом, и соседние разряды на результат не влияют. Возьмём a = 6, то есть двоичное 110, и b = 3, то есть 011.
a = 6b = 3print(a & b, a | b, a ^ b, a << 1, a >> 1)print((47).bit_count(), (47).bit_length())Сдвиг влево на k разрядов — умножение на 2 ** k, вправо — целочисленное деление на 2 ** k.
n.bit_count() считает единицы в двоичной записи модуля числа. n.bit_length() даёт число разрядов без ведущих нулей. У 47 = 101111 пять единиц и шесть разрядов. Скобки вокруг 47 нужны, чтобы точку не приняли за дробную часть числа.
Тонкости:~n и ноль
Целые Python не ограничены 8 или 32 битами, поэтому ~n («инверсия всех битов») равно -n - 1. У нуля bit_length() равен 0, хотя запись "0" имеет длину 1.
Маски fnmatch
Маска fnmatch проверяет запись целиком: ? — один символ, * — любая последовательность. В записи числа ? — ровно одна цифра, а * — сколько угодно цифр, в том числе ни одной. Функция fnmatchcase из модуля fnmatch проверяет, подходит ли строка под маску целиком:
from fnmatch import fnmatchcase print(fnmatchcase("102", "1?2*"))print(fnmatchcase("15200", "1?2*"))print(fnmatchcase("12", "1?2*"))В первом случае * пустая; в последнем на месте ? нет символа: после 1 сразу 2, и маске не хватает символа.
Тонкости:fnmatch и fnmatchcase
Сама маска не знает, что подставлять надо цифры: на месте ? подойдёт любой символ. Цифры получаются потому, что проверяется str(n) для неотрицательного целого. case в имени значит, что большие и маленькие буквы различаются на любом компьютере. В решениях ЕГЭ встречается и fnmatch — для строк из цифр результат тот же.
Проверь маску на своих записях: схема показывает, какие цифры заняли ? и *, а если запись не подходит — говорит об этом.
Маска 1?2*, число 15200
Белые клетки — обязательные цифры маски, серые — ?, в оранжевой рамке — *. Тонкая оранжевая черта — пустая *.
Типичная задача: найти числа до 10 000, кратные 7, чья запись подходит под маску, и для каждого — частное от деления на 7. Перебираем кандидатов и проверяем запись:
from fnmatch import fnmatchcase answers = []for n in range(7, 10_000, 7): if fnmatchcase(str(n), "1?2*"): answers.append((n, n // 7))print(answers[:5])Срез [:5] показывает первые пять ответов. Граница исключает 10 000, а перебор по возрастанию даёт ответы уже по порядку. ? нельзя заменить пустотой; * может означать ноль символов — этот случай легко забыть при ручном построении кандидатов.
Регулярные выражения
Регулярное выражение — другой язык шаблонов, со своими правилами. Модуль re проверяет и ищет по нему текст. В нём * не значит «любой текст»: это повторение предыдущего элемента ноль или более раз. Основные записи — выбери запись, чтобы увидеть, какие строки она описывает целиком:
- [0-9]
- подходят
"7"; не подходят"a"одна ASCII-цифра - [ABC]
- подходят
"B"; не подходят"D"один символ из набора - .
- подходят
"?"; не подходят""любой символ, обычно кроме перевода строки - X*
- подходят
"","XXX"; не подходят"XY"ноль или более повторений X - X+
- подходят
"XXX"; не подходят""одно или более повторений - X?
- подходят
"","X"; не подходят"XX"ноль или одно повторение - X{3}
- подходят
"XXX"; не подходят"XX"ровно три повторения - X{2,4}
- подходят
"XX","XXXX"; не подходят"X"от двух до четырёх повторений - A|B
- подходят
"A"; не подходят"AB"одно из двух - (AB)+
- подходят
"ABAB"; не подходят"ABA"группа: повторяется целиком
Эквивалент маски 1?2* для десятичных записей — 1[0-9]2[0-9]*:
import re pattern = r"1[0-9]2[0-9]*"print(re.fullmatch(pattern, "15200") is not None)print(re.fullmatch(pattern, "12") is not None)Шаблон пишут сырой строкой r"…": в ней обратная косая черта остаётся сама собой. fullmatch требует совпадения со всей строкой; search ищет где угодно; match — только с начала, но не обязательно до конца. Успех возвращается объектом совпадения, неуспех — None.
Объект совпадения знает найденный текст и его место. re.finditer перебирает все совпадения по очереди:
import re for m in re.finditer(r"[0-9]+", "a12b345c6"): print(m.group(), m.start(), m.end())group() — найденный текст, start() — индекс его начала, end() — индекс сразу после конца, как граница среза: "a12b345c6"[4:7] — это "345". re.findall возвращает список найденных строк, но, как и count, без перекрытий: пересекающиеся фрагменты им не посчитать.
Тонкости:медленные регулярные выражения
Сложное регулярное выражение может много раз возвращаться к прежним выборам и работать очень долго на длинной строке. Простой цикл в таких задачах бывает яснее и надёжнее.
Итог
Запись числа — строка, а число одно: Python переводит одно в другое, работает с отдельными битами и проверяет записи по шаблону. Вот что понадобится дальше.
- Перевод
int(text, base)— запись в число;bin,oct,hex,format(n, "08b")— число в запись; цифры — остаткиn % baseсправа налево. - Биты
&,|,^— по разрядам,<<и>>— умножение и деление на 2;bit_count— число единиц. - Маска
fnmatchcase(str(n), mask):?— ровно один символ,*— любые, в том числе ни одного. - Регулярное выражение
*— повторение предыдущего;fullmatchпроверяет всю строку,searchищет где угодно. finditerСовпадения по очереди:group,startиend.
Проверьте себя
Ответь сначала сам, потом открой ответ.
Что напечатает print(int("110", 2), bin(6)[2:])?
ОтветСкрыть ответ
6 110: 1 · 4 + 1 · 2 + 0 · 1 = 6, и обратно.
Чему равны 5 & 3 и 5 | 3?
ОтветСкрыть ответ
1 и 7: 101 и 011 — общий только младший бит, а вместе заняты все три.
Подходят ли записи "1234" и "13" под маску "1?3*"?
ОтветСкрыть ответ
«1234» подходит, «13» — нет: на месте ? нет символа.
Что вернёт re.fullmatch(r"[0-9]+", "123a")? А re.search(r"[0-9]+", "123a") — найдёт ли что-нибудь?
ОтветСкрыть ответ
None; search найдёт 123 в начале строки.
Какие group, start и end у совпадений r"[A-Z]+" в строке "abCDeFGHi"?
ОтветСкрыть ответ
CD 2 4 и FGH 5 8.
Практика
Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.
Что напечатает программа?
print(int("1011", 2))print(int("777", 8))print(int("1A", 16))Запишите напечатанные строки через пробел в порядке вывода.
Подсказка
Веса разрядов — степени основания: 8, 4, 2, 1; 64, 8, 1; 16, 1. Буква A означает 10.
Разбор
8 + 2 + 1 = 11; 7 · 64 + 7 · 8 + 7 = 511; 16 + 10 = 26.
Ответ: 11 511 26
Что напечатает программа?
print(bin(10))print(bin(10)[2:])print(format(10, "08b"))Запишите напечатанные строки через пробел в порядке вывода.
Подсказка
bin пишет префикс 0b, срез [2:] его убирает, "08b" дополняет нулями до восьми знаков.
Разбор
10 = 8 + 2, двоичная запись 1010.
Ответ: 0b1010 1010 00001010
Что напечатает программа?
print(12 & 10, 12 | 10, 12 ^ 10, 3 << 3, 100 >> 2)Запишите напечатанные числа через пробел в порядке вывода.
Подсказка
12 = 1100, 10 = 1010; сдвиг влево на 3 — умножение на 8, вправо на 2 — деление на 4.
Разбор
1100 & 1010 = 1000 = 8; | = 1110 = 14; ^ = 0110 = 6; 3 · 8 = 24; 100 // 4 = 25.
Ответ: 8 14 6 24 25
Что напечатает программа?
print((13).bit_count(), (13).bit_length())Запишите напечатанные числа через пробел в порядке вывода.
Подсказка
Переведи 13 в двоичную запись: 8 + 4 + 1.
Разбор
13 = 1101: три единицы, четыре разряда.
Ответ: 3 4
Напиши программу: кратные 9 под маской
Напиши программу: она перебирает числа меньше 1000, кратные 9, и печатает через пробел те, чья запись подходит под маску 1?8 (fnmatchcase). Запусти её и запиши, что она напечатала.
Запишите числа через пробел по возрастанию.
Подсказка
Маска 1?8 — ровно три цифры: 1, любая, 8.
Разбор
Например:
from fnmatch import fnmatchcase answers = []for n in range(9, 1000, 9): if fnmatchcase(str(n), "1?8"): answers.append(n)print(*answers)Сумма цифр числа, кратного 9, делится на 9: 1 + x + 8 делится на 9 при x = 0 и x = 9.
Ответ: 108 198
Напиши программу: три единицы в записи
Напиши программу: она считает, сколько чисел от 1 до 100 имеют ровно три единицы в двоичной записи. Запусти её и запиши, что она напечатала.
Введите число.
Подсказка
n.bit_count() считает единицы двоичной записи.
Разбор
Например:
print(sum(1 for n in range(1, 101) if n.bit_count() == 3))Ответ: 33