Как учиться

Логика и таблицы истинности

На двери секции гребли висит правило приёма. Берут тех, кому ещё нет девяти лет, и тех, кому уже исполнилось пятнадцать: для детей помладше и для подростков есть свои группы, а средняя набрана. Школьник-дежурный перенёс правило в программу двумя условиями и соединил их словом and. Программа отказала всем: и семилетнему, и шестнадцатилетнему, и тому, кому ровно восемь. Ошибка встречается в самых первых задачах на условия (сами «если» и «иначе» объясняет статья про условия и ветвления), и искать её лучше карандашом: отладчик скажет лишь, что условие ложно, а почему, не скажет.

Строчка дежурного выглядела так:

  • if vozrast < 9 and vozrast >= 15: print("принят")

Ей скормили четыре возраста, и результат оказался одинаковым:

  • 7 лет: отказ
  • 9 лет: отказ
  • 14 лет: отказ
  • 16 лет: отказ

Первое подозрение падает на границы: может быть, перепутаны «меньше» и «меньше или равно». Проверка быстро его снимает: знак «меньше» можно заменить на «меньше или равно» в любом из двух условий, и отказ всё равно получит любой возраст, потому что меньше девяти и не меньше пятнадцати одновременно не бывает. Значит, дело не в числах, а в том, как условия соединены.

Почему не срабатывает ни один случай

Назовём условия буквами: А — «меньше девяти», Б — «пятнадцать и больше». Таблица истинности перечисляет все сочетания этих двух утверждений и для каждого говорит, чему равны «А и Б» и «А или Б». Строк четыре, потому что у каждого утверждения два значения:

  • А истинно, Б истинно: «и» даёт истину, «или» даёт истину
  • А истинно, Б ложно: «и» даёт ложь, «или» даёт истину
  • А ложно, Б истинно: «и» даёт ложь, «или» даёт истину
  • А ложно, Б ложно: «и» даёт ложь, «или» даёт ложь

«И» истинно ровно в одной строке: там, где истинны оба утверждения. Но ни одно число не бывает одновременно меньше девяти и не меньше пятнадцати, и первая строка для возрастов недостижима. Значит, «А и Б» ложно всегда, и условие дежурного не пропустит никого, какое бы число ни пришло.

Теперь посмотрите на колонку «или». Она ложна только в последней строке: ребёнку не меньше девяти и меньше пятнадцати, то есть он попал в среднюю группу, куда приём закрыт. Исправление занимает одно слово:

  • if vozrast < 9 or vozrast >= 15: print("принят")

Семь, восемь, пятнадцать и шестнадцать лет теперь проходят, девять и четырнадцать получают отказ, и это именно то, что висит на двери.

Таблицу истинности можно выписать и программой:

  • from itertools import product
  • for a, b in product((0, 1), repeat=2): print(a, b, a and b, a or b)

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

Откуда берётся ошибка: русское «и»

Фраза на двери звучит естественно: «берём тех, кому меньше девяти, и тех, кому за пятнадцать». Это «и» соединяет группы людей и означает «и тех, и других». В программе «и» соединяет условия, которые относятся к одному человеку: значит, и меньше девяти, и пятнадцать с лишним, одновременно. Слово то же, смысл противоположный.

У русского «или» есть и вторая трудность. «Кофе или чай» означает одно из двух, и обоих сразу не предлагают. Логическое «или» истинно и тогда, когда истинно только одно утверждение, и тогда, когда оба: в нашей таблице оно давало истину в первой строке тоже. Для «ровно одно из двух» в Python пишут a != b (для логических значений), и это отдельная операция, которую школа называет исключающим «или».

Поэтому в таких случаях выручает приём, к логике как науке почти не относящийся: подставьте в условие одного живого человека. Ребёнку восемь лет. Подходит ли он под «меньше девяти»? Да. Подходит ли под «пятнадцать и больше»? Нет. Должна ли программа его принять? Да. Значит, условие, при котором он не принят, написано неверно, и вопрос остаётся в том, чем соединять.

Мне кажется, что учебники отводят этому месту слишком мало задач, хотя ошибок с «и» и «или» у начинающих хватает на каждом курсе. Если на уроке вам показали таблицу, но ни разу не просили записать условие по-русски и пересказать им же написанный код, этот пробел стоит закрыть отдельно.

Тот же отказ, записанный через отрицание

Бывает и обратная постановка: правило вывешено как запрет. «Не принимаем тех, кому девять лет и больше, но ещё нет пятнадцати». Здесь «и» стоит на своём месте, потому что оба условия относятся к одному ребёнку:

  • if not (vozrast >= 9 and vozrast < 15): print("принят")

Это то же самое условие, что и у дежурного после исправления: «меньше девяти или пятнадцать и больше». Равенство называется законом де Моргана: отрицание «и» превращается в «или» от отрицаний. Каждое условие внутри скобок меняется на противоположное («больше или равно девяти» становится «меньше девяти»), а связка меняет «и» на «или».

В Python есть и короткая запись для границ: 9 <= vozrast < 15 понимается как две проверки сразу. Для условия «в средней группе» она читается лучше, чем длинная, и именно с неё удобно начинать, а отрицание ставить сверху.

Второе требование: справка

При приёме секция потребовала и второе: справку от врача. Здесь «и» нужно по-настоящему, потому что справка нужна и младшим, и старшим. Строчка, которую пишут первой:

  • if vozrast < 9 or vozrast >= 15 and spravka: print("принят")

Она выглядит убедительно и пропускает младшего без справки. Причина в приоритете: в Python сначала вычисляется not, потом and, потом or. Значит, строка читается так: «младше девяти, или (пятнадцать и больше и есть справка)». Скобки ставят так, как говорится правило:

  • if (vozrast < 9 or vozrast >= 15) and spravka: print("принят")

Ошибка тихая: восьмилетний со справкой проходит в обоих вариантах, и только тест «восемь лет, справки нет» показывает разницу. Пока теста «восемь лет, справки нет» нет, ошибка не проявится.

Проверка границ и остальные задачи

Для правила с возрастом и справкой минимум — шесть проверок: по одному значению из каждой зоны (младше девяти, между, старше) и для каждого оба ответа про справку. Если брать возраст на границах (восемь, девять, четырнадцать, пятнадцать), проверок будет восемь. Почему границы выбирают именно так, объяснено в статье про условия и ветвления; те же сдвиги на единицу ждут и в поиске делением пополам (про сортировки и поиск).

Проверки удобно закрепить в файле, если оформить правило функцией (как это делается, описано в статье про функции и аргументы) и записать по одной строке на случай:

  • def prinyat(vozrast, spravka): return (vozrast < 9 or vozrast >= 15) and spravka
  • assert prinyat(8, False) == False
  • assert prinyat(8, True) == True
  • assert prinyat(14, True) == False

Пока всё верно, программа молчит, а при первом расхождении останавливается и показывает строку. Такой файл репетитору прислать проще всего: он сразу видит, какие случаи вы считали важными, и подсказывает, каких не хватает.

Логика условий нужна не только в «если». В задачах на обход графа условие «ребро существует и вершина ещё не посещена» устроено так же, как наша справка: оба требования обязательны, и пропущенное «не» даёт бесконечный обход (граф и обход описаны отдельно).

На экзамене и на занятиях

На ЕГЭ по информатике есть задание на таблицы истинности: в нём нужно восстановить, какому столбцу какая переменная соответствует. Его разбору посвящена статья про задание 2 ЕГЭ по информатике, а общую карту первой части найдёте в обзоре заданий. Навык у него тот же, что в нашем разборе: перебрать сочетания и не пропустить строку. Там же встречается операция, которой нет среди слов Python: импликация, «если А, то Б». Она ложна в единственной строке, где А истинно, а Б ложно. В программе её записывают как (not a) or b, и по-русски это звучит естественно: либо условие не выполнено и обещание не действует, либо обещание выполнено.

Экран репетитору из видеокомнаты не виден, поэтому вместе с условием в чат стоит отправить четыре-пять значений, на которых программа отвечает неверно: по ним ошибку видно почти сразу. Общий порядок экзамена описан в статье о его структуре, а о том, как строится курс в целом, в статье о порядке тем школьного курса.

Начать поиск:

Вопросы и ответы

Можно ли писать & и | вместо and и or? Для условий нужны and и or. Знаки & и | работают побитово, и у них выше приоритет, чем у сравнений, так что условие без скобок разбирается совсем не так, как вы рассчитывали.

Сколько строк в таблице истинности для трёх переменных? Восемь. Каждая новая переменная удваивает число строк: две дают четыре, три дают восемь, четыре дают шестнадцать.

Что такое закон де Моргана и зачем он школьнику? Это правило переписывания отрицания. Отрицание «А и Б» равно «не А или не Б»; отрицание «А или Б» равно «не А и не Б». Пригождается в двух случаях: когда правило сформулировано как запрет, а программа пишется на разрешение, и когда условие из задачи нужно упростить, чтобы увидеть, какие значения оно вообще пропускает. Один раз проверьте закон по таблице из четырёх строк, и в дальнейшем он перестанет казаться формулой, которую надо заучивать.

Почему в таблице «или» истинно и в строке, где истинны оба утверждения? Логическое «или» не исключающее: оно говорит «хотя бы одно». Для «ровно одно из двух» берут другую операцию, в Python для логических значений это неравенство a != b.

Нужна ли таблица истинности вне экзамена? Нужна, когда условие длиннее двух частей и вы не уверены, что оно пропускает именно те случаи, которые вы имели в виду. Для коротких проверок хватает границ.

Оставить заявку

Ещё в рубрике «Как учиться»