Какой блок ветвления в языке python реализует разделение кода более чем на две ветви



Скачай курс
в приложении
Перейти в приложение
Открыть мобильную версию сайта
© 2013 — 2023. Stepik
Наши условия использования и конфиденциальности

Public user contributions licensed under cc-wiki license with attribution required
Ветвление. Условный оператор
Ход выполнения программы может быть линейным, то есть таким, когда выражения выполняются друг за другом, начиная с первого и заканчивая последним. Ни одна строка кода программы не пропускается.
Однако чаще в программах бывает не так. При выполнении кода, в зависимости от тех или иных условий, некоторые его участки могут быть опущены, в то время как другие – выполнены. Иными словами, в программе может присутствовать ветвление, которое реализуется условным оператором – особой конструкцией языка программирования.
Проведем аналогию с реальностью. Человек живет по расписанию. Можно сказать, расписание – это алгоритм для человека, его программный код, подлежащий выполнению. В расписании на 18.00 стоит поход в бассейн. Однако экземпляр биоробота класса Homo sapiens через свои рецепторы-сенсоры получает информацию, что воду из бассейна слили. Разумно было бы отменить занятие по плаванию, то есть изменить ход выполнения программы-расписания. Одним из условий посещения бассейна должно быть его функционирование, иначе должны выполняться другие действия.
Подобная нелинейность действий может быть реализована в компьютерной программе. Например, часть кода будет выполняться лишь при определенном значении конкретной переменной. В языках программирования используется приблизительно такая конструкция условного оператора:
if логическое_выражение
Перевести на человеческий язык можно так: если логическое выражение возвращает истину, то выполняются выражения внутри фигурных скобок; если логическое выражение возвращает ложь, то код внутри фигурных скобок не выполняется. С английского «if» переводится как «если».
Конструкция if логическое_выражение называется заголовком условного оператора. Выражения внутри фигурных скобок – телом условного оператора. Тело может содержать как множество выражений, так и всего одно.
Пример использования условного оператора в языке программирования Python:
if n 100: a = a + b
В Питоне вместо фигурных скобок используется двоеточие. Обособление вложенного кода, то есть тела оператора, достигается за счет отступов. В программировании принято делать отступ равным четырем пробелам. Можно использовать клавишу табуляции ( Tab ) на клавиатуре.
Большинство сред программирования автоматически создают отступ, как только вы поставите двоеточие и перейдете на новую строку. Однако при работе в интерактивном режиме отступы надо добавлять вручную.

Нахождение в теле условного оператора здесь обозначается тремя точками. При создании файла со скриптом таких точек быть не должно, как и приглашения >>> .
Python считается языком с ясным синтаксисом и легко читаемым кодом. Это достигается сведением к минимуму таких вспомогательных элементов как различные скобки и точка с запятой. Для разделения выражений используется переход на новую строку, а для обозначения вложенных выражений – отступы от начала строки. В других языках данный стиль программирования также используется, но лишь для удобочитаемости кода человеком. В Питоне же такой стиль возведен в ранг синтаксического правила.
Данный пример вырван из контекста и сам по-себе не является рабочим. Полная версия программы могла бы выглядеть так:
a = 50 b = 10 n = 98 if n 100: a = a + b print(a)
Последняя строчка кода print(a) уже не относится к условному оператору, что обозначено отсутствием перед ней отступа. Она не является вложенной в условный оператор, значит, не принадлежит ему.
Поскольку переменная n равна 98, а это меньше 100, то a станет равной 60. Это значение будет выведено на экран. Если переменная n изначально была бы связана, например, со значением 101, то на экран было бы выведено 50. Потому что при n , равной 101, логическое выражение в заголовке условного оператора вернуло бы ложь. Значит, тело не было бы выполнено, и переменная a не изменилась бы.
Структуру программы можно изобразить следующим образом:
Основная ветка программы выполняется всегда, а вложенный код лишь тогда, когда в темно-зеленой строчке, обозначающей заголовок условного оператора, случается истина.
Для небольших программ иногда чертят так называемые блок-схемы, отражающие алгоритм выполнения. В языке блок-схем различные части кода обозначаются своими фигурами. Так блоку последовательно выполняемых действий соответствует прямоугольник, ветвлению – ромб. Для кода выше блок-схема может выглядеть так:

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

В случае возврата логическим выражением False поток выполнения программы не возвращается сразу в основную ветку. На случай False существует другой вложенный код, отличный от случая True . Другими словами, встретившись с расширенной версией условного оператора, поток выполнения программы не вернется в основную ветку, не выполнив хоть какой-нибудь вложенный код.
В языках программирования разделение на две ветви достигается с помощью добавления блока else, получается так называемое if–else (если-иначе). Синтаксис выглядит примерно так:
if логическое_выражение < выражение 1; выражение 2; … >else
Если условие при инструкции if оказывается ложным, то выполняется блок кода при инструкции else . Ситуация, при которой бы выполнились обе ветви, невозможна. Либо код, принадлежащий if , либо код, принадлежащий еlse . Никак иначе. В заголовке else никогда не бывает логического выражения.
Пример программы с веткой else на языке Python:
tovar1 = 50 tovar2 = 32 if tovar1 + tovar2 > 99: print("99 рублей недостаточно") else: print("Чек оплачен")
Следует иметь в виду, что логическое выражение при if может выглядеть нестандартно, то есть не так просто, как a > b и тому подобное. Там может стоять просто одна переменная, число, слово True или False , а также сложное логическое выражение, когда два простых соединяются через логически and или or .
a = ? if a: a = 1
Если вместо знака вопроса будет стоять 0, то с логической точки зрения это False , значит выражение в if не будет выполнено. Если a будет связано с любым другим числом, то оно будет расцениваться как True , и тело условного оператора выполнится. Другой пример:
a = 5 > 0 if a: print(a)
Здесь a уже связана с булевым значением. В данном случае это True . Отметим, что в выражении a = 5 > 0 присваивание выполняется после оператора сравнения, так что подвыражение 5 > 0 выполнится первым, после чего его результат будет присвоен переменной a . На будущее, если вы сомневаетесь в последовательности выполнения операторов, используйте скобки, например так: a = (5 > 0) .
if a > 0 and a b: print(b - a)
Тут, чтобы вложенный код выполнился, a должно быть больше нуля и одновременно меньше b . Также в Питоне, в отличие от других языков программирования, позволительна такая сокращенная запись сложного логического выражения:
if 0 a b: print(b - a)
Практическая работа
- Напишите программу, которая просит пользователя что-нибудь ввести с клавиатуры. Если он вводит какие-нибудь данные, то на экране должно выводиться сообщение «ОК». Если он не вводит данные, а просто нажимает Enter , то программа ничего не выводит на экран.
- Напишите программу, которая запрашивает у пользователя число. Если оно больше нуля, то в ответ на экран выводится число 1. Если введенное число не является положительным, то на экран должно выводиться -1.
Примеры решения и дополнительные уроки в pdf-версии курса
X Скрыть Наверх
Python. Введение в программирование
Генерация рандомных ветвлений на Питоне
Вспоминая Докинза, основную идею можно выразить так: если долго держать смерч над помойкой, то может собраться Боинг-747. Появление структуры из хаоса дуриком: перебирая и рекомбинируя всё подряд, из всех бессмысленных и беспорядочных процессов можно увидеть вполне осмысленные и упорядоченные. Если такие процессы каким-либо образом закрепляются и повторяются, то система, еще вчера представлявшая из себя броуновское движение, сегодня начинает выглядеть уже так, как будто ее поведение настроила невидимая рука, и что она совершает какие-то осмысленные с нашей точки зрения действия. При этом никакой руки и близко нет. Она настроила себя сама.
Чтобы в этом убедиться еще раз, я и стремлюсь написать некое подобие цифровой жизни, которая из хаоса и без лишних указаний от человека способна будет сама себе рандомно генерить логику и существовать по ней в своем естественном пространстве обитания — операционной системе. Да, в этом, вероятно, есть отличие от многих программ из направления «Искусственная жизнь», которые «живут» в загончиках, плодят «хищников» и «травоядных», и со-существуют на искусственных полях с «едой» и друг другом. Никакие из этих программ не взаимодействуют с объектами системы (процессами, файлами и т.д.), а значит код по-настоящему не живет. Кроме того, этот код так или иначе всё равно выполняет какую-то нужную человеку задачу и очень из-за этого ограничен рамками.
- Модуль рандомной генерации основного исполняемого кода
- Модуль, связанный с рандомным образованием опыта
- Модуль «компьютерного зрения» объектов ОС
Почему рандомно? Для того, чтобы оставить коду максимальную возможность действовать самостоятельно и отодвинуть от этого процесса человека(программиста) как можно дальше (в идеале исключить совсем). Последнее — наиболее сложное для программиста, т.к. стандартное программирование, к которому все привыкли, напоминает жесткую дрессировку животных, которые должны выполнять ровно то, что укажет программист, ровно так, как он укажет и тогда, когда он укажет. Тут ситуация обратная: конечный генерируемый код должен действовать так, чтобы это было для создателя его генератора максимально непредсказуемым.
Прежде, чем мы перейдем к схемам и коду генератора, необходимо остановиться на функции принятия решений, которая используется как дирижер, давая исполняться то одной, то другой части кода. Ранее я уже писал о ней здесь. Мне тогда подсказали, что я описал идею Reinforcement Learning и игру Джона Конвеея под названием «Жизнь». Вполне может быть, я не имею ничего против того, чтобы использовать то, что уже наработано или открыто. В конце концов всё новое — это синтез уже известного, да я и сам признавал, что перенял идею приоритезации потоков, которая используется в винде. Здесь она очень подходит.
В настоящее время упомянутая функция немного трансформировалась:
def make_solution(p_random, p_deter): deter_flag = 0 random_flag = 0 if p_random >= random.random(): p_random-=0.01 # баланс приоритета p_deter+=0.01 random_flag = 1 if p_deter >= random.random(): p_deter-=0.01 # баланс приоритета p_random+=0.01 deter_flag = 1 if random_flag == 1 and deter_flag == 0: return(p_random, p_deter, 1) elif deter_flag == 1 and random_flag == 0: return(p_random, p_deter, -1) else: return (p_random, p_deter,0)
На вход она принимает 2 вероятности (по умолчанию на старте они обе равны 0,5), после чего поочередно проверяет их срабатывание. Сработавшая вероятность уменьшает сама себя на 1% и одновременно увеличивает на 1% другую. Следовательно, с каждым разом, как вероятность срабатывает, она уменьшается, а другая — увеличивается. В результате ни одна вероятность не получает слишком большого преимущества перед другой, и они самобалансируются, образуя нормальное распределение с центром в 0,5 и с рабочей окрестностью не более +-10%, что отличает эту функцию от стандартной random, где вероятность в нашем случае всегда была бы равна 0,5 и не зависела бы от предыдущих вычислений.
Образно говоря, это маятник вероятности с небольшой амплитудой. Если сработала первая вероятность и не сработала вторая — возвращается 1, в обратном случае возвращается -1, а если сработали или не сработали обе — 0. Таким образом, функция make_solution на 2 входящих вероятности возвращает одно из 3 возможных действий, давая, соответственно принимать сбалансированное решение на развилках с 3 возможными вариантами продолжения. В дальнейшем эта функция, скорее всего будет универсальной, и сможет принимать неопределенное количество вероятностей, т.к. вариативность на развилках может быть больше 3, но в случае с генератором if-elif-else трёх вариантов продолжения вполне достаточно.
Здесь надо еще отметить, что в коде встречаются разные, так сказать, типовые развилки. Например, как будет видно ниже, в основной функции генератора находится развилка, в которой идет выбор схемы построения ветвления, коих всего 3, но в коде также присутствуют другие случаи: вставлять блок действия или запускать рекурсию, сколько строк действия генерировать, насколько сложной должна быть строка с условием, ставить or или and, elif или else.
Я считаю, что вероятностный маятник, о котором мы говорили выше, должен выставляться для каждого типа действия свой: тогда развилка балансируется только исходя из того, что происходило ранее именно на этой развилке, а не в каких то других частях кода. Т.е. при выборе общей структуры ветвления у нас своя пара вероятностей, а внутри, когда строятся его элементы — другая.
Можно конечно балансировать все действия одной парой, но тогда вероятность на каждой развилке будет очень сложной и будет зависеть от всех предыдущих действий на других развязках. Рандомность такой конструкции будет еще выше, но я лично пока что склоняюсь к первой схеме, ибо мне нравится конструкция, где в рамках одного большого качающегося маятника качаются другие маленькие, т.е. в одном большом балансе рождаются «балансы» поменьше. Плюс к этому во второй схеме рандомность тоже более чем достаточная.
При написании генератора ветвлений нужно было сделать не только работоспособный код, который выдает безошибочные генерации, но и такой код, который может генерировать максимально возможные конструкции if-elif-else, а таких возможных вариантов не 2 и не 3. Рассмотрим, например, следующие возможные схемы.

Под значком [..] в схемах я подразумеваю набор выражений для условия либо блок рандомных действий. Самая элементарная схема — 1, где просто идет условие, а за ним блок действия. 2а и 2b — это вариации if с одним elif или одним else. В варианте 2с if идет уже в комбинации с несколькими elif без еlse. И, наконец, в варианте 2d представлена наиболее общая схема, где if содержит в себе несколько elif и 1 else.
Всё было бы просто, если бы не необходимость построения неограниченных ветвлений. После каждого if, elif или else может вызываться рекурсия, которая в свою очередь также может рекурсировать дальше и плодить «вправо» новые блоки elif-else. Посмотрим на схему возможных вариантов.

В вариантах 2е и 2f показаны простые частные случаи такого рекурсивного ветвления, когда рекурсия вызывается либо после одиночного elif, либо после одиночного else. Вариант 2g описывает наиболее сложный и общий случай такой рекурсии, когда после каждого elif может идти блок действия+рекурсия (либо сразу рекурсия), и тоже самое может происходить после else.
Есть еще вариации, когда рекурсия наступает сразу же после if или после if и блока действия.

Это видно в вариантах 3а и 3b. Вариант 3с показывает такую схему в наиболее общем виде.
Нельзя сказать, что приведенные выше схемы охватывают все возможные варианты построения ветвлений, однако даже в таком виде конечный код легко рождает ветвления в 150 строк, уходя «вправо» на 10-15 шагов. В любом случае усложнить схемы при необходимости не составляет большого труда.
Можно посмотреть на пример одной такой генерации, чтобы убедиться в том, что ветвления могут быть весьма разнообразными.

На состав условных выражений и блоков действий обращать внимания не нужно — для визуальной простоты они сгенерированы всего из комбинаций двух переменных, 3 выражений и небольшого ряда арифметических и логических знаков. Обсуждение реального «мяса» для рекомбинаций выходит за рамки данной статьи (об этом речь будет при рассмотрении 3 модуля).
Перед тем, как перейти к непосредственному рассмотрению кода генератора, необходимо вспомнить, что сгенерированные блоки должны смещаться вправо по горизонтали, если это elif, else, рекурсия if или блоки действия, а также «возвращаться» назад влево, после того как ветка завершится. Причем, учитывая, что Питон весьма придирчив к горизонтальным отступам, желательно сделать шаг одинаковым (в нашем случае шаг равен 3).
На следующей схеме проиллюстрировано, каким образом сдвигаются смещения.

Самое главное тут, что смещения при углублении ветвления сдвигаются всегда вправо. Однако, если у нас есть, например, блок elif-else, в котором есть несколько elif или одинарная пара elif-else, то существует необходимость «возврата» каретки, уплывшей вправо, для того, чтобы следующий elif (или else) начинался с того же смещения, что и предыдущий в блоке. Для этого необходимо сохранять изначальное смещение (wall_offset) и после окончания генерации ветки (например, полного ветвления одного elif), восстанавливать его. Таким образом достигается ровное нахождение «друг над другом» элементов elif, else в блоке. Причем у каждого нового блока это смещение своё. Тот же самый приём обеспечивает стройность общей конструкции if-elif-else (в том числе при рекурсиях).
Теперь перейдем к коду. Код общим объемом около 200 строк состоит из 8 функций, одну из которых мы рассмотрели выше. Из-за рекурсивности и большого количества параметров, передаваемых в функции, он местами может быть плохо читаемым. Для начала приведу то самое «мясо», которое используется для генерации условных выражений и блоков действий.
var_list = ['a','b'] exp_list = ['a+b','b-a', 'b//a'] sign = ['+','-','/','*','//'] sign2 = ['>','=','
Как видно, используются две переменные: a и b (var_list), которые инициализированы, 3 арифметических выражения (exp_list), а также два листа со знаками (sign, sign2). Как и говорилось ранее, состав получаемых выражений сейчас значения не имеет и в данной статье не рассматривается — они нужны в основном для иллюстрации кода. Надо отметить еще такую особенность: в генерации блока elif-else нужно отслеживать появление else и прекращать генерацию, — в противном случае else может появиться перед elif, что естественно вызовет ошибку. Для этой цели используется флаг fin_else_flag.
Начнем рассмотрение с основной функции генерации.
def if_gen(exp_list, var_list, if_str, offset_koeff, fin_else_flag, prob_list): choice_list = [exp_list, var_list] base_offset = ' ' # основная структурная развилка prob_list[0],prob_list[1],sol = make_solution(prob_list[0],prob_list[1]) # if + блок действия (1 вариант в схеме) if sol == 0: # генерим блок действия со смещением+3 action_str = action_str_gen(choice_list, offset_koeff+3, prob_list) return(base_offset*offset_koeff+'if '+ if_sub(exp_list,var_list, sign, prob_list) +':\n' + action_str, offset_koeff, fin_else_flag, prob_list) # if + elif/else (2 вариант в схеме) elif sol == -1: if_str= base_offset*offset_koeff+'if '+ if_sub(exp_list,var_list, sign, prob_list) +':\n' + action_str_gen(choice_list, offset_koeff+3, prob_list) # if [..]: # развилка elif/else prob_list[2],prob_list[3],sol2=make_solution(prob_list[2],prob_list[3]) if sol2!=0: ee_string='elif' else: ee_string='else' # генерация блока elif/else if_str, offset_koeff, fin_else_flag, prob_list = elif_else_block(ee_string, offset_koeff, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list) return(if_str, offset_koeff, fin_else_flag, prob_list) # if + if(рекурсия) (3 вариант в схеме) else: if_str= base_offset*offset_koeff+'if '+ if_sub(exp_list,var_list, sign, prob_list) +':\n' # if [..]: # развилка if/if+блок действия prob_list[4],prob_list[5],sol = make_solution(prob_list[4],prob_list[5]) if sol==0: # генерим блок действия со смещением+3 if_str+=action_str_gen(choice_list, offset_koeff+3, prob_list) # сохраняем смещение wall_offset = offset_koeff if_rek, offset_koeff, fin_else_flag, prob_list = if_gen(exp_list, var_list, if_str, offset_koeff+3, fin_else_flag, prob_list) # рекурсия if+if # прицепляем сгенерированный рекурсивный кусок if_str+=if_rek # развилка блок elif-else/блок действия prob_list[4],prob_list[5],sol2=make_solution(prob_list[4],prob_list[5]) if sol2!=0: prob_list[2],prob_list[3],sol3=make_solution(prob_list[2],prob_list[3]) if sol3!=0: ee_string='elif' else: ee_string='else' if_str, offset_koeff, fin_else_flag, prob_list = elif_else_block(ee_string, wall_offset, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list) else: # генерим блок действия со смещением+3 if_str+=action_str_gen(choice_list, offset_koeff+3, prob_list) return(if_str, offset_koeff,fin_else_flag, prob_list)
Функция принимает помимо списков с «мясом» для генерации (exp_list, var_list) также if_str — это строка, куда поочередно собирается генерируемый код. Принимается она здесь по причине того, что сама функция if_gen может рекурсивно вызываться, и кусок кода, сгенерированный ранее, желательно бы не терять.
Параметр offset_koeff — это коэффициент смещения, который является множителем для строки с одним пробелом (base_offset) и, соответственно, именно он и отвечает за горизонтальные смещения блоков кода.
Про fin_else_flag мы говорили выше, здесь он просто передается в функцию, которая отвечает за генерацию if + elif/else (см.ниже).
Ну и есть еще параметр — prob_list, который представляет из себя лист с 10-ю вероятностями (5 парами вероятностей)
prob_list = [0.5 for y in range(0,10)]
и используется функцией make_solution так, как мы обсуждали выше: в нее передается та или иная пара вероятностей из него, соответствующая типу развилки (например, основная структурная развилка использует первые 2 вероятности в листе: prob_list[0] и prob_list[1]). Результаты изменений вероятностей в этом листе, как пример, можно видеть на следующем рисунке.

Вероятности в этом списке меняются от генерации к генерации, если при очередной генерации соответствующий кусок кода попадал на исполнение.
В самой функции в начале инициализирован вложенный список choice_list — он нужен для удобной рандомной генерации выражений из «мяса», и базовое смещение base_offset = ' ' в один пробел.
После этого идет основная развилка, которая через функцию make_solution получает решение в переменную sol. Sol принимает одно из трёх значений (0,-1,1) и обуславливает, следовательно, по какой схеме будет строиться конструкция.
В первом варианте реализуется самая простой вариант if+[..]. Ответ формируется в виде строки со текущим смещением (оно не обязательно равно 0!), строка «if», рандомное условие, которое генерируется функцией if_sub (будет рассмотрена дальше), перевод каретки и генерация блока действия с помощью функции action_str(см.ниже). В результате мы получаем нечто вроде:
if ((a+b)==(b)): b=b a=b-a a=a
Второй вариант отвечает за генерацию такого типа: if [..]+elif/else-блок (вариант 2 в схемах). Сначала там формируется аналогично строка if+[..], затем идет развилка elif/else, которая решает будет ли генерироваться блок elif-else, просто if-elif или if-else (функция elif_else_block — см. ниже). Результаты могут быть разными. Например:
if ((a+b)==(a)): b=a+b elif ((b//a)==(a)): None elif ((a+b)<=(a)): a=b//a else: if ((b)<=(a)): a=b-a b=a
if ((a)==(b-a)): b=b-a b=b a=b a=b-a elif ((b)>(b-a))and((a)<(b-a)): if ((b//a)<(a)): b=b-a elif ((a+b)<(b-a))and((b)<(a+b))or((a+b)==(a+b)): b=b a=b-a elif ((a)>(b-a)): None
if ((b)<=(b-a))or((a+b)>=(b)): a=a b=b elif ((b)<=(b)): if ((a)>=(b)): a=a+b a=b elif ((b)>=(a)): a=b-a a=a if ((a)>=(b))and((b//a)==(a))and((b//a)!=(b)): b=b-a else: a=b//a if ((b//a)<(b-a)): a=b a=b-a else: if ((a)==(b)): a=a a=b//a b=b b=a+b b=a else: None
Третий вариант реализует рекурсию с самого начала (вариант 3 в схемах), т.е. рождает ветвление вида:
if ((a)==(a)): if ((a+b)<(b)):
if ((b-a)<=(a)): a=a if ((b-a)==(b)): a=a a=a
Сначала идет формирование строки if (аналогично), потом появляется развилка, которая решает, вставлять ли дальше блок действия или нет, после чего сохраняется смещение и вызывается рекурсия. Смещение нужно сохранить, чтобы после того, как отработает рекурсия и вернет кусок кода, можно было добавить еще блок elif-else по тому же смещению, как и первоначальная строчка с if. Здесь видно, как elif и else в ветвлении стоят по одному смещению со своим «родным» if.
if ((b-a)==(b)): if ((a)>(a+b)): if ((b)==(b-a)): b=b a=a elif ((b)>(b)): None else: None b=a b=b
Дальше идет развилка elif-else-блок/блок действия, которая решает добавлять ли после рекурсии блок действия или elif-else блок. Если решено добавлять блок elif-else, то там аналогично вышеописанному случаю в схеме 2 выбирается elif или else.
Здесь же надо обратить внимание на то, что рекурсия вызывается со смещением offset+3, чтобы сдвинуть вправо на шаг генерируемый код, а блок elif-else вызывается со смещением wall_offset, чтобы этот блок после рекурсии не поехал вправо, а остался с «родным» смещением изначального if.
Результаты могут быть довольно разными: от простых до сложных, но появление рекурсии сразу же выдает наиболее витиеватые ветвления.
if ((b-a)>(a+b))and((b)<(a+b)): if ((b-a)<=(a+b)): b=b//a elif ((b)!=(a)): a=b-a else: if ((a+b)!=(b-a)): a=a
if ((b)<(b-a)): if ((a+b)==(b-a))and((b-a)<(a+b))and((b-a)==(a))and((a)>(b//a))or((a+b)>(b//a)): if ((b)>=(b-a)): a=b b=b if ((b)>(b)): a=a+b b=a+b a=a b=a+b b=b//a b=a else: b=a+b a=b a=b elif ((a)<(b-a)): a=b//a a=b-a
if ((a)>=(b-a))or((a)>=(a))or((b)<=(b)): a=a a=a elif ((a)==(a))and((b)>(b-a)): a=b//a if ((a)<(b)): if ((a+b)==(b-a)): a=a if ((a)!=(b//a)): if ((b//a)!=(a))and((b-a)>=(b)): a=b else: None a=b//a else: b=b b=a+b if ((b-a)<=(b//a)): a=b a=b a=a+b else: a=a+b if ((b-a)>=(a)): a=b if ((b-a)==(a))or((b)!=(b//a)): a=b-a a=a a=a a=b//a a=a+b b=a
Теперь посмотрим на функцию elif_else_block, которая отвечает за формирование блока elif-else и вызывается из основной функции if_gen.
def elif_else_block(ee_string, offset_koeff, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list): if ee_string=='elif': sol3 = 9 # сохраняем смещение wall_offset = offset_koeff # генерация elif в цикле while sol3!=0 and fin_else_flag!=1: temp_str, offset_koeff, fin_else_flag, prob_list=elif_else('elif', wall_offset, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list) if_str+=temp_str prob_list[6],prob_list[7],sol3 = make_solution(prob_list[6],prob_list[7]) # развилка - добавлять ли else к группе elif? prob_list[2],prob_list[3],sol = make_solution(prob_list[2],prob_list[3]) if sol!=0: # добавляем else, значит ставим флаг fin_else_flag=1 temp_str,offset_koeff, fin_else_flag, prob_list=elif_else('else', wall_offset, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list) if_str+=temp_str return(if_str,offset_koeff, fin_else_flag, prob_list) # генерация else else: temp_str,offset_koeff, fin_else_flag, prob_list=elif_else('else', offset_koeff, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list) if_str+=temp_str return(if_str, offset_koeff, fin_else_flag, prob_list)
Данная функция решает, добавлять ли в код блок elif или elif/else. Ставить ли просто else она сама не решает, а зависит от входящего значения ee_string, которое она получает из основной функции if_gen. Сначала идет генерация блока elif в цикле while, где проверяются 2 условия: вероятностное — от него зависит количество самих elif в блоке и флаг fin_else_flag, который, если вдруг включается, то это означает, что до этого присоединялся else, и следовательно из цикла надо выходить.
Решение присоединять ли к блоку elif еще и else решается развилкой с помощью всё той же функции make_solution, причем, если присоединяется else, то сразу же включается флаг fin_else_flag, который прекращает генерацию блока.
Непосредственное же присоединение elif и else осуществляется функцией elif_else (см.ниже). Здесь надо обратить внимание, что при генерации блока elif (а также при присоединении к нему else) используется смещение wall_offset, чтобы ровно выстраивать блок в целом.
Теперь рассмотрим функцию elif_else .
def elif_else(ee_string, offset_koeff, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list): ee_str = '' # формирование строки else: или elif [..]: if ee_string=='else': ee_str += ' '*offset_koeff+ee_string + ':\n' elif ee_string=='elif': ee_str += ' '*offset_koeff+ee_string+' '+if_sub(exp_list, var_list, sign, prob_list) + ':\n' # развилка блок действия-None / блок действия+рекурсия prob_list[2],prob_list[3],sol = make_solution(prob_list[2],prob_list[3]) if sol!=0: prob_list[6],prob_list[7],sol2 = make_solution(prob_list[6],prob_list[7]) if sol2!=0: # блок действия ee_str+=action_str_gen(choice_list,offset_koeff+3, prob_list) else: # None ee_str+=' '*(offset_koeff+3)+'None\n' return(ee_str, offset_koeff, fin_else_flag, prob_list) else: # подразвилка блок действия prob_list[6],prob_list[7],sol2 = make_solution(prob_list[6],prob_list[7]) if sol2==0: # блок действия ee_str+=action_str_gen(choice_list,offset_koeff+3, prob_list) # рекурсия if_gen if_str, offset_koeff, fin_else_flag, prob_list = if_gen(exp_list, var_list, if_str, offset_koeff+3, fin_else_flag, prob_list) ee_str+=if_str return(ee_str, offset_koeff, fin_else_flag, prob_list)
Функция отвечает за формирование самой строки elif или else, а также за последующую генерацию после этих строк блоков действий или рекурсии. Она также принимает переменную ee_string, которая содержит или elif, или else, и формирует соответствующую строку. Затем идет развилка, где определяется, что будет идти дальше: (блок действия или None), или (блок действия или блок действия+рекурсия). Внутри этой развилки есть разделение, соответственно на две подразвилки, и в каждом случае вызывается функция make_solution с соответствующими параметрами для принятия решения.
Надо отметить, что когда в коде встречается if sol!=0 , это значит, что мы намеренно даем преимущество одной части кода над другой, ибо если sol!=0, значит она равняется или -1, или 1, и следовательно другой кусок кода будет исполняться реже (только когда sol ==0). Это используется, в частности в функции elif_else_block, где нам выгоднее давать формироваться большему количеству elif в блоке, а не давать равную вероятность elif и else. Или, например, в функции elif_else мы даём преимущество варианту, когда формируется блок действия или None нежели, чем идет рекурсия — в противном случае ветвления могут разрастаться до совсем уже неприличных размеров.
Нам осталось рассмотреть только функции, отвечающие за рандомную генерацию выражений в условиях и блоках действий. Как я и говорил выше, на данном этапе они не играют решающей роли и введены здесь, чтобы в целом показать, как будет выглядеть конечный сгенерированный код. Но так как в генераторе они используются, мы их кратко посмотрим.
Функция, отвечающая за генерацию блока действия action_str.
def action_str_gen(choice_list, offset_koeff, prob_list): sol = 9 curr_offset = ' '*offset_koeff act_str = '' while sol!=0: act_str+= curr_offset+rand(rand(choice_list[1]))+'='+rand(rand(choice_list))+'\n' prob_list[6],prob_list[7],sol = make_solution(prob_list[6],prob_list[7]) return(act_str)
Здесь всё достаточно просто: из вложенного списка choise_list, который, как мы помним, состоит из var_list (список переменных) и exp_list (список выражений), эта функция составляет одну или несколько строк такого вида: a = a + b или b = b. Т.е. либо переменной присваивается выражение, либо другая переменная (включая себя саму). Функция rand осуществляют случайную выборку элемента из списка и нужна тут исключительно для того, чтобы не плодить монстроузные строки.
def rand(t_list): return(t_list[random.randint(0,len(t_list)-1)])
Функция генерации выражений if_sub для условий выглядит побольше.
def if_sub(exp_list, var_list, sign, prob_list): sub_str = '' sol = 9 choice_list = [exp_list, var_list] flag = 0 while sol!=0: prob_list[6],prob_list[7],sol = make_solution(prob_list[6],prob_list[7]) sub_str+='(('+rand(rand(choice_list))+')'+rand(sign2)+'('+rand(rand(choice_list))+'))' if flag == 1 and sol==1: sub_str+=')' flag=0 or_and_exp = or_and(prob_list) if len(or_and_exp): sub_str+=or_and_exp else: break prob_list[6],prob_list[7],sol2 = make_solution(prob_list[6],prob_list[7]) if sol2 == 1 and (sub_str[-1]=='D' or sub_str[-1]=='R') and flag == 0: sub_str+='(' flag = 1 if sub_str[-1] == '(': if sub_str[-2]=='d': sub_str=sub_str[0:-4] elif sub_str[-2]=='r': sub_str=sub_str[0:-3] else: sub_str=sub_str[0:-1] elif sub_str[-1]=='d': sub_str=sub_str[0:-3] elif sub_str[-1]=='r': sub_str=sub_str[0:-2] else: None if flag == 1: sub_str+=')' return(sub_str) else: return(sub_str)
def or_and(prob_list): prob_list[8],prob_list[9],sol = make_solution(prob_list[8],prob_list[9]) if sol==-1: return('and') elif sol==1: return('or') else: return('')
Остальная часть функции if_sub отрезает от выражений лишние хвосты и добавляет, когда нужно, закрывающие скобки, — рассматривать здесь эти танцы с бубнами, я думаю, нецелесообразно.
Ну вот собственно и всё. Запустить генератор можно, например, так:
var_list = ['a','b'] exp_list = ['a+b','b-a', 'b//a'] sign = ['+','-','/','*','//'] sign2 = ['>','=','
Сначала входные данные, включая лист с вероятностями prob_list, затем в бесконечном цикле вызов основной функции if_gen и запуск полученной сгенерированной строки на исполнение. Стоит обрабатывать отдельно ZeroDivisionError, т.к. деление на ноль при таком рандомном построении выражений встречается весьма часто. После запуска достаточно жать Enter для появления очередной генерации. Чаще всего они будут достаточно простыми, но нередко встречаются и разветвленные и даже очень разветвленные. Ну и import random в начало тоже было бы неплохо вставить;) Для тех, кто не хочет посмотреть собирать всё руками, можно скачать файл с Github (файл if_gen.py).
В заключение я хочу сказать, что представленный код тестировался мною на сотнях тысячах генераций без ошибок, при этом он демонстрировал всю ту палитру схем if-elif-else, которую я и хотел в конце концов увидеть. Один раз я по ошибке дал в одной части кода слишком большую вероятность рекурсии и у меня вышла генерация в 52 000 (!) строк и при этом она была рабочей (хотя комп подвис секунд на 30). Это также свидетельствует о надежности алгоритма.
Наверное, можно было где-то написать лаконичнее, где-то пооптимизировать, по-другому скомпоновать основную функцию, но главное, что этот код работает и выдает примерно 250 генераций в секунду, что, на мой взгляд, вполне приемлемо.
Я никогда не рассматривал этот код как самодостаточный — он всего лишь модуль будущего цифрового организма и написан в исследовательских целях, поэтому каких-либо практических применений вряд ли имеет. Вместе с тем, я не несу ответственности за какие-либо последствия при любом использовании кем-либо вышеприведенного кода и призываю всех ножом для резки хлеба резать всё-таки хлеб, а не что-то другое.
В следующей статье мы будем рассматривать второй модуль, который будет отвечать за рандомное формирование опыта. Эта тема обещает быть куда интересней, чем генератор if, и я обязательно выложу результаты, как только они у меня будут.
- автопрограммирование
- искусственная жизнь
- искусственный интеллект
- генерирование кода
- Ненормальное программирование
- Python
- Программирование
- Алгоритмы
- Искусственный интеллект
Разветвляющиеся алгоритмы. Сложные условия. Каскадные ветвления

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

В данный момент вы не можете посмотреть или раздать видеоурок ученикам
Чтобы получить доступ к этому и другим видеоурокам комплекта, вам нужно добавить его в личный кабинет.
Получите невероятные возможности

1. Откройте доступ ко всем видеоурокам комплекта.

2. Раздавайте видеоуроки в личные кабинеты ученикам.

3. Смотрите статистику просмотра видеоуроков учениками.
Получить доступ
Конспект урока "Разветвляющиеся алгоритмы. Сложные условия. Каскадные ветвления"
Во многих случаях алгоритм некоторых действий зависит от условий. В записях таких алгоритмов присутствуют конструкции ветвления. Однако в ветвлении рассматривается всего одно условие, которого может быть недостаточно. Сегодня мы изучим способы, которые позволяют обойти эти ограничения – сложные условия и каскадные ветвления.
Рассмотрим задачу. Прямоугольник на координатной плоскости со сторонами, параллельными координатным осям, задан координатами левой верхней и правой нижней своих вершин. Также даны координаты точки на плоскости. Все координаты – целые числа. Определить, принадлежит ли точка заданному прямоугольнику. Рассмотрим, как должна располагаться точка, чтобы принадлежать прямоугольнику. Относительно левой верхней вершины точка должна находиться не выше и не левее её. То есть координата x точки должна быть больше либо равна координате x вершины, а координата y точки должна быть меньше либо равна соответствующей координате вершины. Рассмотрим положение точки относительно правой нижней вершины. Точка должна находиться не ниже и не правее её. То есть координата x точки должна быть меньше или равна соответствующей координате вершины, а координата y точки должна быть больше либо равна соответствующей координате вершины. Таким образом, мы получили четыре неравенства, в случае выполнения которых точка будет принадлежать прямоугольнику.

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

Как видим, мы получили блок-схему с использованием нескольких ветвлений, которые записаны в составе других ветвлений; они называются вложенными. Код программы, составленной по такой блок-схеме, будет достаточно сложен.
Но на самом деле эту задачу можно решить куда проще. Как мы помним, условиями ветвлений являются логические высказывания. Логические высказывания делятся на простые и сложные. До этого мы рассматривали простые логические высказывания. В них ни одна из частей сама по себе не является логическим высказыванием. Сложные же логические высказывания состоят из простых, соединённых с помощью логических операций.
Есть 3 основные логические операции, 2 из которых – бинарные, то есть соединяют 2 выражения логического типа. Первая логическая операция – конъюнкция или логическое умножение. В русском языке для её обозначения используют союз «И». В языке Python она обозначается словом and. Конъюнкция возвращает значение «истина» тогда и только тогда, когда истинны оба соединяемых высказывания.
Вторая логическая операция – дизъюнкция или логическое сложение. В русском языке она обозначается союзом «или», а в языке Python – служебным словом or. Дизъюнкция возвращает значение «истина» тогда, когда истинно хотя бы одно или оба соединяемых высказывания.
И последняя из основных логических операций – инверсия или логическое отрицание. Эта операция является унарной, то есть применяется к одному логическому выражению. Инверсия изменяет значение выражения, к которому применяется, на противоположное. В русском языке она обозначается частицей «не», а в языке Python – служебным словом not.
Логические операции выполняются в самую последнюю очередь, даже после операций сравнения. Таким образом, логические операции имеют самый низкий приоритет выполнения.
Вернёмся к решению задачи о принадлежности точки прямоугольнику. Некоторые из вас уже наверняка догадались, что решение этой задачи можно сильно упростить, если записать сложное условие принадлежности точки прямоугольнику. Обратим внимание на то, что первое и третье неравенства можно объединить так же, как второе и четвёртое. Объединённые неравенства будут работать и в языке Python так же, как и одиночные, соединённые операцией конъюнкции. То есть сложную конструкцию, состоящую из четырёх ветвлений, можно заменить всего одним ветвлением, условием которого будут два неравенства. То есть точка принадлежит прямоугольнику, если её координата x больше либо равна координате x левой верхней вершины треугольника, а также меньше либо равна координате x правой нижней вершины прямоугольника И координата точки y меньше или равна координате y левой верхней вершины прямоугольника, а также больше либо равна координате y правой нижней вершины прямоугольника. Если это условие выполняется, тогда точка принадлежит прямоугольнику, в противном случае – точка не принадлежит прямоугольнику. Таким образом, для решения задачи нам достаточно всего одного ветвления.
Напишем программу для решения задачи. Вначале выведем на экран сообщение о том, что это программа, определяющая принадлежность точки прямоугольнику. С помощью второй инструкции print выведем на экран запрос на ввод координат верхней левой вершины прямоугольника. Дальше запишем инструкцию для считывания этих координат в переменные x1 и y1. Так как по условию задачи координаты – целые числа, то при считывании мы будем преобразовывать их значения в целочисленный тип int. Теперь дважды скопируем последние две инструкции и изменим их соответственно для считывания координат нижней правой вершины в переменные x2 и y2, а также для считывания координат точки в переменные x и y. Теперь запишем инструкцию ветвления с условием, состоящим из двух полученных ранее неравенств, соединённых служебным словом and. Если это условие выполняется, то выведем на экран сообщение о том, что точка принадлежит прямоугольнику. В противном случае, выведем сообщение о том, что точка не принадлежит прямоугольнику.
print ('Программа, определяющая принадлежность точки \nпрямоугольнику.')
print ('Введите координаты левой верхней вершины \nпрямоугольника.')
x1, y1 = int (input ()), int (input ())
print ('Введите координаты правой нижней вершины \nпрямоугольника.')
x2, y2 = int (input ()), int (input ())
print ('Введите координаты точки.')
x, y = int (input ()), int (input ())
if x2 >= x >= x1 and y2
print ('Точка принадлежит прямоугольнику.')
print ('Точка не принадлежит прямоугольнику.')
Сохраним написанный модуль и запустим его на выполнение. Зададим верхнюю левую вершину прямоугольника в точке (1; 7), а правую нижнюю – в точке (10; 2). Зададим точку (3; 3). Эта точка принадлежит заданному прямоугольнику. Программа вывела сообщение об этом. Снова запустим модуль. Зададим вершины прямоугольника в тех же точках, что и в прошлый раз, а также точку с координатами (7; 10). Заданная точка не принадлежит прямоугольнику. Программа вывела сообщение об этом, значит она работает правильно. Задача решена.
Рассмотрим ещё одну задачу. Написать программу для решения квадратных уравнений. Как мы знаем, квадратное уравнение имеет вид: ax 2 + bx + c = 0. При этом a ≠ 0. Таким образом, квадратное уравнение можно задать значениями его коэффициентов: a, b и c.
Составим блок-схему алгоритма решения задачи. Вначале программа будет принимать на ввод коэффициенты уравнения: a, b и c. Далее мы должны вычислить значение дискриминанта уравнения. Для этого переменной D мы присвоим значение b 2 – 4ac. Далее, как мы помним, нам нужно проверить значение дискриминанта. Если оно отрицательное, то уравнение не имеет решений. Если дискриминант равен нулю, то уравнение имеет единственное решение, которое вычисляется, как b – 2a. Если же дискриминант положительный, то уравнение имеет два решения, которые вычисляются по формуле:

Чтобы это реализовать, сначала мы запишем ветвление с условием, что дискриминант отрицательный. Если это условие выполняется – уравнение не имеет решений, и мы выведем сообщение об этом. Если же условие не выполняется, то мы запишем второе ветвление с условием, что дискриминант равен нулю. Если это условие выполняется, то уравнение имеет одно решение. Мы вичислим его значение в переменной x, после чего выведем результат на экран. Если же и это условие не выполняется, значит дискриминант положительный и уравнение имеет два решения. Мы вычислим эти решения в переменных x1 и x2, после чего выведем их на экран. На этом работа программы будет завершена.

Как видим, наличие вложенного ветвления в одной из ветвей внешнего реализует разделение кода на три ветви. В языке Python разделение кода более чем на две ветви реализуется с помощью каскадного ветвления. Рассмотрим его запись. Как мы помним, обычное ветвление записывается так… Однако между блоками if и else можно добавить ещё один блок – elif с некоторым условием и инструкциями. Это ветвление работает таким образом, что сначала будет проверено условие в блоке if. Если это условие выполняется, то будут выполнены инструкции, записанные в этом блоке. Если же условие в блоке if не выполняется, то будет проверено условие в блоке elif. Если оно выполняется, то будут выполнены инструкции, записанные в этом блоке, если же и это условие не будет выполняться, то будут выполнены инструкции из блока else. Блоков elif может быть несколько, их условия будут проверяться сверху вниз, при этом будут выполнены инструкции, записанные в каком-то одном из блоков.
Используя каскадное ветвление, напишем программу для решения задачи. Вначале с помощью инструкции print выведем сообщение о том, что это программа для решения квадратных уравнений и запрос на ввод его коэффициентов. Дальше запишем инструкцию для считывания коэффициентов квадратного уравнения. В условии не сказано, что это целые числа, поэтому при считывании их значения мы будем преобразовывать в вещественный тип float. Далее вычислим значение дискриминанта в переменной D. Так как в дальнейшем нам понадобится функция извлечения квадратного корня, загрузим её в наш модуль из модуля math. Далее запишем инструкцию ветвления с условием, что дискриминант меньше нуля. Если это условие выполняется, то выведем на экран сообщение о том, что уравнение не имеет решений. Дальше запишем блок elif с условием, что D = 0. В случае выполнения этого условия мы вычислим решение уравнения в переменной x. Далее выведем на экран с соответствующим поясняющим сообщением значение переменной x. В блоке else мы вычислим два решения уравнения в переменных x1 и x2, после чего с соответствующим поясняющим сообщением выведем на экран их значения.
print ('Программа для решения квадратных уравнений.\nВведите коэффициенты уравнения.')
a, b, c = float (input ()), float (input ()), float (input ())
D = b ** 2 - 4 * a * c
from math import sqrt
print ('Заданное уравнение не имеет решений.')
x1 = (-b + sqrt (D)) / (2 * a)
x2 = (-b - sqrt (D)) / (2 * a)
print ('x1 =', x1, 'x2 =', x2)
Сохраним модуль и запустим его на выполнение. Введём коэффициенты уравнения равными 1, 8 и 7. Таким образом, мы получим уравнение x 2 + 8x + 7 = 0. Оно действительно имеет два решения: -1 и -7. Снова запустим модуль на выполнение и зададим коэффициенты уравнения равными 2, 4 и 2. Мы получим уравнение 2x 2 + 4x + 2 = 0. Оно действительно имеет одно решение: –1. И снова запустим модуль на выполнение и зададим коэффициенты, равные 7, -1 и 3. Мы получим уравнение 7x 2 – x + 3 = 0. Оно действительно не имеет решений. Программа работает правильно. Задача решена.
· Вложенным называется ветвление, которое находится в одной из ветвей другого ветвления.
· Сложные условия состоят из простых логических высказываний, соединённых с помощью логических операций.
· Основные логические операции: конъюнкцию, дизъюнкцию и инверсию.
· Каскадные ветвления используются для разделения кода более чем на две ветви.