ЕГЭ, ОГЭ, программирование и робототехника в Калининграде

Алгоритм решения задания 10. Часть 1

О задании

В прошлых статьях мы не только разобрались с работой модели OSI, но и научились пользоваться функциями модуля ipaddress в Python. Теперь можем смело переходить к разбору алгоритмов решения 10 задания ЕГЭ по информатике.

Данное задание направлено на проверку умений определять параметры по заданному IP-адресу и маске подсети, рассчитывать количество возможных адресов в сети, а также на применение побитовых операций для работы с IP-адресами и масками.

Работать в данном задании предстоит всего с тремя значениями: адрес сети, адрес узла и маска подсети. Два из них даются в условии, а одно требуется найти. Задания 13 можно разделить на 7 типов, в зависимости от данных значений:

  1. Даны адрес узла и маска. Чаще всего в таких заданиях требуется найти наибольший адрес сети, который может быть назначен компьютеру;
  2. Даны два адреса узлов, находящихся в одной сети. Требуется найти маску (наибольшее/наименьшее количество единиц/нулей в маске) или количество адресов в этой сети;
  3. Даны адрес узла и адрес сети. Обычно требуется найти маску подсети (наибольшее/наименьшее количество единиц/нулей);
  4. Даны адрес сети и маска. Требуется найти количество адресов в сети, удовлетворяющих условию, например, количество нулей/единиц в IP-адресе кратно какому-то числу и т.д.;
  5. Даны маска и адрес узла с неизвестным байтом. Требуется найти значение отсутствующего байта адреса узла, при котором будет выполняться условие (количество единиц/нулей в левых двух байтах меньше/больше количества в правых двух байтах);
  6. Даны маска и адрес сети с неизвестным байтом. Требуется найти значение отсутствующего байта адреса сети, при котором будет выполняться условие (количество единиц/нулей в левых двух байтах меньше/больше количества в правых двух байтах);
  7. Даны адрес сети и маска с неизвестным байтом. Требуется найти значение отсутствующего байта маски, при котором будет выполняться условие (количество единиц/нулей в левых двух байтах меньше/больше количества в правых двух байтах);

Типов слишком много для одной статьи, так что мы разобьём их разбор на три части. В данной статье рассмотрим первые 2 типа заданий, в следующей поговорим про ручные и программные методы решения 3-4 типов, а напоследок разберём все задания с неизвестным байтом.

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

Решать 10 задания мы будем по большей части программными методами с помощью встроенного в Python модуля ipaddress. Если вы еще не умеете работать с данным модулем, то настоятельно рекомендуем сначала ознакомиться с прошлой статьёй.

Во всех формулировках заданий далее мы будем опускать общую часть и приводить только основную информацию, нужную для решения.

Тип 1

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

Рассмотрим такую формулировку:

Сеть задана IP-адресом одного из входящих в неё узлов 98.81.154.195 и сетевой маской 255.252.0.0.
Найдите наибольший в данной сети IP-адрес, который может быть назначен компьютеру.

В ответе укажите найденный IP-адрес без разделителей.

Например, если бы найденный адрес был равен 111.22.3.44, то в ответе следовало бы записать 11122344

Открыть задание

Здесь буквально всё решение занимает лишь пару строчек. Сначала импортируем функцию ip_interface и создаём объект IP-интерфейса:

from ipaddress import ip_interface


net = ip_interface('98.81.154.195/255.252.0.0')

Вспоминаем, что последний (с индексом -1) адрес является широковещательным. Значит, нам нужно взять предпоследний (с индексом -2) и вывести на экран:

from ipaddress import ip_interface


net = ip_interface('98.81.154.195/255.252.0.0')

print(net.network[-2])

Мы получаем такой адрес: 98.83.255.254. Осталось лишь удалить из него точки и записать в ответ.

Пример 1

Рассмотрим задание немного посложнее:

Сеть задана IP-адресом одного из входящих неё узлов 190.202.83.62 и сетевой маской 255.255.252.0.
Найдите наибольший IP-адрес в данной сети, который может быть назначен компьютеру. В ответе укажите сумму числовых значений октетов найденного IP-адреса.

Например, если бы найденный адрес был равен 100.20.3.4, то в ответе следовало бы записать: 127.

Открыть задание

Уже интереснее, здесь нужно не только найти адрес, но и просуммировать значения его октетов. С первой частью мы справимся без проблем:

from ipaddress import ip_interface


net = ip_interface('190.202.83.62/255.255.252.0')
last_ip = net.network[-2]

Теперь нужно как-то из переменной last_ip достать значения, разделённые точками. Сделаем это через списочное включение:

from ipaddress import ip_interface


net = ip_interface('190.202.83.62/255.255.252.0')
last_ip = net.network[-2]

octets = [int(octet) for octet in str(last_ip).split(".")]

Что у нас здесь происходит? Для начала мы приводим объект IP-адреса к строчному типу данных: str(last_ip). Затем разделяем строку по символу точки: str(last_ip).split(".").

Проходимся по полученному списку и каждую строку в нём приводим к целочисленному типу данных: int(octet) for octet in str(last_ip).split("."). В конце собираем все целые числа в один список, чтобы проще было просуммировать их.

Для любителей функции map — те же действия можно записать так:

octets = map(int, str(last_ip).split("."))

Останется лишь вывести на экран сумму чисел в списке octets:

from ipaddress import ip_interface


net = ip_interface('190.202.83.62/255.255.252.0')
last_ip = net.network[-2]

octets = [int(octet) for octet in str(last_ip).split(".")]

print(sum(octets))

Запускаем программу и получаем число 729, которое и запишем в ответ.

Пример 2

И для закрепления рассмотрим еще одно задание:

Сеть задана IP-адресом одного из входящих в неё узлов 191.128.66.83 и сетевой маской 255.192.0.0.
Найдите в данной сети наибольший IP-адрес, который может быть назначен компьютеру.

В ответе укажите найденный IP-адрес без разделителей.

Например, если бы найденный адрес был равен 111.22.3.44, то в ответе следовало бы записать: 11122344.

Открыть задание

Что же, не будем просто дублировать одинаковый код. Давайте в этот раз покажем максимально «питоническое» решение в одну строку:

from ipaddress import ip_network

print(ip_network('191.128.66.83/255.192.0.0', False)[-2])

А если чувствуете, что можете ошибиться в переписывании адреса с этими точками-разделителями, то лучше вовсе избавиться от них вот таким образом:

from ipaddress import ip_network

net = ip_network('191.128.66.83/255.192.0.0', strict=False)

print(str(net[-2]).replace('.',''))

В результате получим строку 191191255254, которую можно сразу записать в ответ.

Тип 2

Задания второго типа чаще всего имеют только авторские формулировки. Но в любом случае будет полезно научиться их решать. Начнём с такого:

Два узла, находящиеся в одной сети, имеют IP-адреса 127.0.1.65 и 127.0.1.101.

Укажите наименьшее возможное количество адресов в этой сети.

Открыть задание

Определим алгоритм действий:

  1. Сначала создадим по объекту на каждый IP-адрес;
  2. Затем в цикле будем перебирать возможные префиксы маски. Так как нам нужно наименьшее количество адресов, то следует начать со значения 30 и идти на убывание с шагом 1;
  3. Строим сеть по первому IP-адресу (любому из двух);
  4. Проверяем, попадает ли второй в эту же сеть;
  5. Дополнительно проверяем, что оба адреса не служебные — не адреса сети и не широковещательные.

Переходим к решению. Импорт и создание объектов:

from ipaddress import ip_network, ip_address

ip1 = ip_address('127.0.1.65')
ip2 = ip_address('127.0.1.101')

Перебираем префиксы от большего к меньшему. Чем больше префикс, тем меньше сеть, поэтому первое подходящее значение сразу даст минимальное количество адресов:

from ipaddress import ip_network, ip_address

ip1 = ip_address('127.0.1.65')
ip2 = ip_address('127.0.1.101')

for mask in range(30, -1, -1):

Строим сеть по первому адресу:

from ipaddress import ip_network, ip_address

ip1 = ip_address('127.0.1.65')
ip2 = ip_address('127.0.1.101')

for mask in range(30, -1, -1):
    net = ip_network(f'{ip1}/{mask}', strict=False)

Теперь проверим условия:

  1. ip2 входит в сеть net;
  2. ip1 и ip2 — не служебные. Здесь удобно будет проверить принадлежность к net.hosts.
from ipaddress import ip_network, ip_address

ip1 = ip_address('127.0.1.65')
ip2 = ip_address('127.0.1.101')

for mask in range(30, -1, -1):
    net = ip_network(f'{ip1}/{mask}', strict=False)

    if (ip2 in net and ip1 not in (net.network_address, 
                                   net.broadcast_address)
            and ip2 not in (net.network_address,
                            net.broadcast_address)):

Как только выполнится условие — выводим количество адресов в этой сети и завершаем цикл:

from ipaddress import ip_network, ip_address

ip1 = ip_address('127.0.1.65')
ip2 = ip_address('127.0.1.101')

for mask in range(30, -1, -1):
    net = ip_network(f'{ip1}/{mask}', strict=False)

    if (ip2 in net and ip1 not in (net.network_address, 
                                   net.broadcast_address)
            and ip2 not in (net.network_address,
                            net.broadcast_address)):
        print(net.num_addresses)
        break

Здесь требуется количество всех адресов, так что никаких «-2» мы не делаем. Запускаем программу и видим число 64. Его и запишем в ответ.

Пример 1

Теперь рассмотрим такое задание:

Два узла, находящиеся в одной сети, имеют IP-адреса 192.168.190.12 и 192.168.184.0.

Укажите наибольшее возможное количество единиц в маске этой сети.

В ответе укажите только число.

Открыть задание

Сразу вспоминаем, что количество единиц в маске — это как раз длина префикса, которую мы и перебираем в цикле. Значит, теперь нужно будет вывести на экран значение переменной mask. В остальном код практически без изменений:

from ipaddress import ip_network, ip_address

ip1 = ip_address('192.168.190.12')
ip2 = ip_address('192.168.184.0')

for mask in range(30, -1, -1):
    net = ip_network(f'{ip1}/{mask}', strict=False)

    if (ip2 in net and ip1 not in (net.network_address, 
                                   net.broadcast_address) 
            and ip2 not in (net.network_address, 
                            net.broadcast_address)):
        print(mask)
        break

Запускаем его и получаем число 20, которое и будет ответом на это задание.

Пример 2

Напоследок решим задание посложнее:

Два узла, находящиеся в разных подсетях, имеют IP-адреса 192.168.34.17 и 192.168.45.91. Известно, что в масках обеих подсетей содержится одинаковое количество единиц.

Определите наименьшее возможное количество единиц в масках этих подсетей.

В ответе укажите только число.

Открыть задание

Снова ищем количество единиц, но теперь узлы находятся уже в разных сетях! И теперь нам не обойтись без построения обеих сетей.

Начинаем как обычно, импортируем функции и создаём два объекта IP-адреса:

from ipaddress import ip_network, ip_address

ip1 = ip_address('192.168.34.17')
ip2 = ip_address('192.168.45.91')

Первые два байта совпадают, поэтому можно начать с /16. Идём к большим префиксам: первое значение, при котором сети станут разными, будет минимальным ответом.

from ipaddress import ip_network, ip_address

ip1 = ip_address('192.168.34.17')
ip2 = ip_address('192.168.45.91')

for mask in range(16, 31):

Теперь строим сети с разными узлами, но одинаковой длиной префикса (или маской):

from ipaddress import ip_network, ip_address

ip1 = ip_address('192.168.34.17')
ip2 = ip_address('192.168.45.91')

for mask in range(16, 31):
    net_1 = ip_network(f'{ip1}/{mask}', strict=False)
    net_2 = ip_network(f'{ip2}/{mask}', strict=False)

Что нам нужно проверить здесь? Во-первых, что сети разные, то есть не равны друг другу. Во-вторых, что первый IP-адрес не является служебным и входит в первую сеть. Аналогично и со вторым:

from ipaddress import ip_network, ip_address

ip1 = ip_address('192.168.34.17')
ip2 = ip_address('192.168.45.91')

for mask in range(16, 31):
    net_1 = ip_network(f'{ip1}/{mask}', strict=False)
    net_2 = ip_network(f'{ip2}/{mask}', strict=False)

    if (net_1 != net_2 and
            ip1 not in (net_1.network_address, net_1.broadcast_address) and
            ip2 not in (net_2.network_address, net_2.broadcast_address)):

Нам нужно наименьшее количество единиц в маске, следовательно и наименьшая длина префикса. Тогда, как только выполнятся все условия, выводим текущее значение переменной mask на экран и завершаем цикл:

from ipaddress import ip_network, ip_address

ip1 = ip_address('192.168.34.17')
ip2 = ip_address('192.168.45.91')

for mask in range(16, 31):
    net_1 = ip_network(f'{ip1}/{mask}', strict=False)
    net_2 = ip_network(f'{ip2}/{mask}', strict=False)

    if (net_1 != net_2 and
            ip1 not in (net_1.network_address, net_1.broadcast_address) and
            ip2 not in (net_2.network_address, net_2.broadcast_address)):

        print(mask)
        break

Готово, теперь запускаем код и радуемся ответу на это задание — числу 21.

Итоги

Первые два типа задания 10 сводятся к перебору длины префикса. На каждом шаге строятся сети для адресов из условия, после чего проверяется нужное отношение между ними.

Шаблон решения

from ipaddress import ip_address, ip_network

ip1 = ip_address('*1')
ip2 = ip_address('*2')

for prefix in *3:
    net1 = ip_network(f'{ip1}/{prefix}', strict=False)
    net2 = ip_network(f'{ip2}/{prefix}', strict=False)

    if *4:
        print(*5)
        break

Здесь:

  1. *1 и *2 — IP-адреса,
  2. *3 — диапазон префиксов и направление перебора,
  3. *4 — условие одной или разных сетей,
  4. *5 — требуемая величина: префикс, адрес сети или значение маски.

В следующей статье разберём типы 3 и 4: восстановим характеристики маски по адресу сети и научимся считать адреса с заданным свойством.

Разберём эту тему на бесплатном пробном занятии

Преподаватель покажет, как решать такие задачи, и подскажет, с чего начать подготовку. Группы до 8 человек, очно в Калининграде и онлайн.

Записаться на пробное занятие