К содержанию

Модуль 6 · Урок 6.5

Записи чисел, биты и текстовые шаблоны

int с основанием, bin, oct, hex и format, битовые операции, маски fnmatch и регулярные выражения re.

6 задач

В задачах ЕГЭ одно и то же число встречается в разных записях: 47 и двоичное 101111 — одно число. Нужно переводить запись в число и обратно, считать единицы двоичной записи и проверять, подходит ли запись под маску вроде 1?2*. Число и его запись — разные вещи: Python переводит одно в другое, работает с отдельными двоичными разрядами и проверяет запись по шаблону.

Двоичная запись устроена так же, как десятичная из урока о числах, только веса разрядов другие. Начнём с перевода числа 47.

Системы счисления

Запись числа зависит от основания, а int(text, base) переводит запись в число. «Двоичным» или «десятичным» бывает не число, а его запись. В записи с основанием base разряды справа налево имеют веса 1, base, base ** 2 и так далее. Например, 1011 в двоичной записи — это 1 · 8 + 0 · 4 + 1 · 2 + 1 · 1 = 11:

Та же схема разрядов, что для десятичного числа 472 в уроке о числах, только веса — степени двойки: 8, 4, 2, 1.
print(int("1011", 2))print(int("101111", 2))print(int("FF", 16))print(bin(47), oct(47), hex(255))print(bin(47)[2:])
11 47 255 0b101111 0o57 0xff 101111

int(text, base) разбирает запись в заданном основании. bin, oct и hex строят двоичную, восьмеричную и шестнадцатеричную записи с префиксами 0b, 0o, 0x; срез [2:] убирает префикс у неотрицательного числа.

Как получается сама запись? Последний разряд — остаток n % 2, остальные разряды хранит n // 2 — так же в уроке о while снимали десятичные цифры через % 10 и // 10. Пройди перевод 47 по шагам:

  1. n = 47: остаток 1 становится новой цифрой слева, дальше делим 47 // 2 = 23.
  2. n = 23: остаток 1 становится новой цифрой слева, дальше делим 23 // 2 = 11.
  3. n = 11: остаток 1 становится новой цифрой слева, дальше делим 11 // 2 = 5.
  4. n = 5: остаток 1 становится новой цифрой слева, дальше делим 5 // 2 = 2.
  5. n = 2: остаток 0 становится новой цифрой слева, дальше делим 2 // 2 = 1.
  6. n = 1: остаток 1 становится новой цифрой слева, дальше делим 1 // 2 = 0.
  7. 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"))
101111 00000101 ff
Тонкости:отрицательные числа

bin(-5) — это '-0b101', и срез [2:] оставит 'b101'. Знак обрабатывают отдельно: переводят abs(n) и добавляют минус.

Битовые операции

Битовые операции работают с каждым двоичным разрядом отдельно. Бит — двоичный разряд. Операция сравнивает биты, стоящие друг под другом, и соседние разряды на результат не влияют. Возьмём a = 6, то есть двоичное 110, и b = 3, то есть 011.

Каждый разряд результата зависит только от двух битов над ним, соседние разряды не влияют. Сдвиги переносят всю запись: влево — умножение на 2, вправо — целое деление на 2.
a = 6b = 3print(a & b, a | b, a ^ b, a << 1, a >> 1)print((47).bit_count(), (47).bit_length())
2 7 5 12 3 5 6

Сдвиг влево на 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*"))
True True False

В первом случае * пустая; в последнем на месте ? нет символа: после 1 сразу 2, и маске не хватает символа.

Тонкости:fnmatch и fnmatchcase

Сама маска не знает, что подставлять надо цифры: на месте ? подойдёт любой символ. Цифры получаются потому, что проверяется str(n) для неотрицательного целого. case в имени значит, что большие и маленькие буквы различаются на любом компьютере. В решениях ЕГЭ встречается и fnmatch — для строк из цифр результат тот же.

Проверь маску на своих записях: схема показывает, какие цифры заняли ? и *, а если запись не подходит — говорит об этом.

Маска 1?2*, число 15200

Подходит. Звёздочка заменяет «00».

Белые клетки — обязательные цифры маски, серые — ?, в оранжевой рамке — *. Тонкая оранжевая черта — пустая *.

Типичная задача: найти числа до 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])
[(112, 16), (182, 26), (1022, 146), (1029, 147), (1120, 160)]

Срез [: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)
True False

Шаблон пишут сырой строкой 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())
12 1 3 345 4 7 6 8 9

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.

Проверьте себя

Ответь сначала сам, потом открой ответ.

Вопрос 1

Что напечатает print(int("110", 2), bin(6)[2:])?

ОтветСкрыть ответ
Ответ

6 110: 1 · 4 + 1 · 2 + 0 · 1 = 6, и обратно.

Вопрос 2

Чему равны 5 & 3 и 5 | 3?

ОтветСкрыть ответ
Ответ

1 и 7: 101 и 011 — общий только младший бит, а вместе заняты все три.

Вопрос 3

Подходят ли записи "1234" и "13" под маску "1?3*"?

ОтветСкрыть ответ
Ответ

«1234» подходит, «13» — нет: на месте ? нет символа.

Вопрос 4

Что вернёт re.fullmatch(r"[0-9]+", "123a")? А re.search(r"[0-9]+", "123a") — найдёт ли что-нибудь?

ОтветСкрыть ответ
Ответ

None; search найдёт 123 в начале строки.

Вопрос 5

Какие 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))

Запишите напечатанные строки через пробел в порядке вывода.

Что напечатает программа?

print(bin(10))print(bin(10)[2:])print(format(10, "08b"))

Запишите напечатанные строки через пробел в порядке вывода.

Что напечатает программа?

print(12 & 10, 12 | 10, 12 ^ 10, 3 << 3, 100 >> 2)

Запишите напечатанные числа через пробел в порядке вывода.

Что напечатает программа?

print((13).bit_count(), (13).bit_length())

Запишите напечатанные числа через пробел в порядке вывода.

Напиши программу: кратные 9 под маской

Напиши программу: она перебирает числа меньше 1000, кратные 9, и печатает через пробел те, чья запись подходит под маску 1?8 (fnmatchcase). Запусти её и запиши, что она напечатала.

Запишите числа через пробел по возрастанию.

Напиши программу: три единицы в записи

Напиши программу: она считает, сколько чисел от 1 до 100 имеют ровно три единицы в двоичной записи. Запусти её и запиши, что она напечатала.

Введите число.