НОЧУ ДПО "Национальный открытый университет "ИНТУИТ"
Опубликован: 24.01.2021 | Доступ: свободный | Студентов: 1229 / 21 | Длительность: 03:57:00
Лекция 16:

Программирование в процедурах и функциях

< Лекция 1 || Лекция 16: 1234

Смотреть на youtube

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

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

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

Мы знаем, что можно, используя лямбда определение, задать анонимные функции - константные функции, не имеющие имени.

Главное, что нужно помнить, что методы (функции и процедуры) - это простейший и наиболее часто используемый механизм модульного построения программы, позволяющий:

  • справиться со сложностью задачи;
  • повторно использовать программный код;
  • облегчить построение корректного кода.

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

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

Сформулирую задачу. Дано вещественное число, имеющее целую и дробную часть. Число записано как строка в системе счисления с основанием p. Необходимо перевести число в систему счисления с основанием q. В ЕГЭ по информатике такая задача встречается достаточно часто. Кроме того, любой программист должен свободно работать с числами в любой системе счисления. Так что построенное решение задачи определенно может повторно использоваться.

Иногда задать заголовок метода, понять, что метод делает, какие у него аргументы, - это не простое дело. Но в нашем случае постановка задачи прозрачна, так что написание заголовка и заголовочного комментария не представляет трудностей:

def TranslateFromPtoQ(x:str, p:int, q:int)->str:
    """
    Перевод числа x, представленного строкой в системе счисления с основанием p,
    в строку в системе счисления с основанием q
    """

Заголовок написать просто, а вот реализовать в теле метода алгоритм, преобразующий строку, представляющую число в системе счисления p, в строку, представляющее это же число в системе с основанием q, не кажется легкой задачей. Как справиться со сложностью? Единственный путь - декомпозиция. Нужно представить решение задачи, как комбинацию решений нескольких более простых задач. Естественное предложение - выделить в числе x целую и дробную часть, перевести каждую независимо, затем сцепить два решения в одну строку, что и даст решение исходной задачи. Такой подход позволяет написать тело нашей функции в четыре строчки:

    #Декомпозиция к более простым задачам
    lx = Split(x)
    ix = TranslateIntFromPtoQ(lx[0], p, q)
    fx = TranslateFractFromPtoQ(lx[1], p, q)
    return ix + fx    

Метод Split расщепляет x. Методы Translate выполняют перевод, return - возвращает конкатенацию строк.

Заметьте, мы написали корректный код, точнее условно корректный код. Если вызываемые методы корректно работают, то и наш метод корректно работает. Доказательство этого достаточно очевидно.

Займемся методами, которые предстоит реализовать. Метод Split кажется достаточно простым. На прошлой лекции мы реализовали подобную функцию, когда x - задавалось числом типа float, теперь же речь идет о строке, представляющей число этого типа. Пожалуй, самое главное при разработке метода Split - это сформулировать предусловие к методу - условие, которому должен удовлетворять аргумент x при вызове метода. Фактически, предусловие к методу Split является предусловием к создаваемому нами методу Translate. Содержательно, предусловие можно сформулировать следующим образом:

"Строка, задающая число x, должна состоять из цифр системы счисления p. Цифрами этой системы являются символы от 0 до 9, от A до Т, где цифра А имеет значение 10, а Т - 30. Цифры, используемые в записи числа в системе p, должны быть по значению меньше p. Так что при p = 2 возможны в записи только две цифры - 0 и 1, при p = 16 возможны цифры от 0 до 9, от А до F. Помимо цифр в строке, задающей число x, возможна точка, отделяющая целую часть от дробной."

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

Я не буду в данном случае заниматься проверкой предусловия, оставляю эту проверку в качестве упражнения. Приведу текст метода Split в предположении, что предусловие метода выполнено:

def Split(x:str)->list:
    """
    Расщепление строки x, представляющей число типа float,
    на целую и дробную части
    Результат - список из двух элементов. Первый задает целую часть,
    второй - дробную часть числа x
    """
    res = []
    if '.' in x:
        ind = x.index('.')
        res.append(x[0 : ind])
        res.append(x[ind:])
    else:
        res.append(x)
        res.append('')
    return res

Заголовочного комментария и аннотаций достаточно для понимания метода. Нетрудно обосновать и корректность работы метода, если выполняется предусловие. Заметьте только, что если точки в записи числа нет, то целая часть числа совпадает с числом, а дробная часть - пустая строка. Если запись начинается с точки, то пустой строкой является целая часть числа. В общем случае - целая часть состоит из цифр, а дробная часть начинается с точки, за которой следуют цифры.

< Лекция 1 || Лекция 16: 1234
Елена Лаптева
Елена Лаптева

Думаю. что не смогу его закончить. Хотелось предупредить других - не тратьте зря время, ищите другой курс.

Михаил Сидоров
Михаил Сидоров

Если S - последовательность, то срез задается как S(i : j) и содержит j - i элементов,

а в примере используютс другие скобки - 

NL[1:3] = ["решили", "не", "искать"]

или это не срез, тогда, что это?