Неделя 1

Лекция. Введение. Алгоритм и язык Python

Лекция 1. Введение. Алгоритм и язык Python

Курс «Алгоритмы и структуры данных» (факультет искусственного интеллекта МГУ). Два рабочих критерия качества алгоритма:

  • корректность — на всех допустимых входах ответ верный;
  • эффективность — время и память растут приемлемо с размером входа.

Python 3 здесь — язык записи и реализации. Где задание требует свою реализацию, встроенными аналогами пользоваться нельзя.

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

План

  1. Алгоритм. Псевдокод, блок-схема, Python.
  2. Основы языка Python.
  3. Функции: капсула, чистая функция, контракт.
  4. Потоки чисел и память $O(1)$.

Алгоритм

Алгоритм — это

  1. последовательность команд
  2. на формальном языке исполнителя,
  3. приводящая к цели
  4. за конечное время.

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

Языки описания

Один и тот же алгоритм можно записать псевдокодом, блок-схемой и на Python. Задача: по натуральному $n$ вычислить сумму $1 + 2 + \ldots + n$.

Псевдокод.

ввести n
s ← 0
для x от 1 до n:
    s ← s + x
вывести s

Блок-схема.

началоввести ns ← 0x ← 1x ≤ n?нетдаs ← s + xx ← x + 1вывести sконец

Python. Исполнитель курса — интерпретатор Python: те же шаги, формальный синтаксис.

In [1]:
n = 5
s = 0
x = 1
while x <= n:
    s = s + x
    x = x + 1
print(s)
15

Основы языка Python

Напоминание конструкций, без которых алгоритм не записать. Списков на этой лекции нет.

Операции с целыми

ОперацияДействие
A + B, A - B, A * Bсумма, разность, произведение
A / Bчастное (вещественное)
A // Bчастное нацело
A % Bостаток
A ** Bстепень

// и % разбирают число по разрядам: последняя цифра — n % 10, без последней — n // 10.

Имя можно заставить обозначать новый объект: s = s + x или короче s += x.

In [2]:
n = 179
print(n // 100, n // 10 % 10, n % 10)
print(n // 100 + n // 10 % 10 + n % 10)
1 7 9
17
In [4]:
1 - 3 - 7
Out[4]:
-9
In [5]:
1 / 2 / 2
Out[5]:
0.25
In [11]:
(3 ** 3) ** 3 == 3 ** (3*3)
Out[11]:
True
In [15]:
-1 // 10
Out[15]:
-1
In [16]:
-1 % 10
Out[16]:
9

Строки: + и *

Сложение строк — конкатенация, умножение на целое — повтор.

In [18]:
a = "алго"
b = "ритм"
print(a + b)
алгоритм
In [19]:
a + b == b + a
Out[19]:
False
In [20]:
print("-" * 8)
--------
In [21]:
a * b
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[21], line 1
----> 1 a * b

TypeError: can't multiply sequence by non-int of type 'str'
In [25]:
2 ** 10000
Out[25]:
19950631168807583848837421626835850838234968318861924548520089498529438830221946631919961684036194597899331129423209124271556491349413781117593785932096323957855730046793794526765246551266059895520550086918193311542508608460618104685509074866089624888090489894838009253941633257850621568309473902556912388065225096643874441046759871626985453222868538161694315775629640762836880760732228535091641476183956381458969463899410840960536267821064621427333394036525565649530603142680234969400335934316651459297773279665775606172582031407994198179607378245683762280037302885487251900834464581454650557929601414833921615734588139257095379769119277800826957735674444123062018757836325502728323789270710373802866393031428133241401624195671690574061419654342324638801248856147305207431992259611796250130992860241708340807605932320161268492288496255841312844061536738951487114256315111089745514203313820202931640957596464756010405845841566072044962867016515061920631004186422275908670900574606417856951911456055068251250406007519842261898059237118054444788072906395242548339221982707404473162376760846613033778706039803413197133493654622700563169937455508241780972810983291314403571877524768509857276937926433221599399876886660808368837838027643282775172273657572744784112294389733810861607423253291974813120197604178281965697475898164531258434135959862784130128185406283476649088690521047580882615823961985770122407044330583075869039319604603404973156583208672105913300903752823415539745394397715257455290510212310947321610753474825740775273986348298498340756937955646638621874569499279016572103701364433135817214311791398222983845847334440270964182851005072927748364550578634501100852987812389473928699540834346158807043959118985815145779177143619698728131459483783202081474982171858011389071228250905826817436220577475921417653715687725614904582904992461028630081535583308130101987675856234343538955409175623400844887526162643568648833519463720377293240094456246923254350400678027273837755376406726898636241037491410966718557050759098100246789880178271925953381282421954028302759408448955014676668389697996886241636313376393903373455801407636741877711055384225739499110186468219696581651485130494222369947714763069155468217682876200362777257723781365331611196811280792669481887201298643660768551639860534602297871557517947385246369446923087894265948217008051120322365496288169035739121368338393591756418733850510970271613915439590991598154654417336311656936031122249937969999226781732358023111862644575299135758175008199839236284615249881088960232244362173771618086357015468484058622329792853875623486556440536962622018963571028812361567512543338303270029097668650568557157505516727518899194129711337690149916181315171544007728650573189557450920330185304847113818315407324053319038462084036421763703911550639789000742853672196280903477974533320468368795868580237952218629120080742819551317948157624448298518461509704888027274721574688131594750409732115080498190455803416826949787141316063210686391511681774304792596709376
In [26]:
x = 2 ** 10000
In [27]:
print(x)
19950631168807583848837421626835850838234968318861924548520089498529438830221946631919961684036194597899331129423209124271556491349413781117593785932096323957855730046793794526765246551266059895520550086918193311542508608460618104685509074866089624888090489894838009253941633257850621568309473902556912388065225096643874441046759871626985453222868538161694315775629640762836880760732228535091641476183956381458969463899410840960536267821064621427333394036525565649530603142680234969400335934316651459297773279665775606172582031407994198179607378245683762280037302885487251900834464581454650557929601414833921615734588139257095379769119277800826957735674444123062018757836325502728323789270710373802866393031428133241401624195671690574061419654342324638801248856147305207431992259611796250130992860241708340807605932320161268492288496255841312844061536738951487114256315111089745514203313820202931640957596464756010405845841566072044962867016515061920631004186422275908670900574606417856951911456055068251250406007519842261898059237118054444788072906395242548339221982707404473162376760846613033778706039803413197133493654622700563169937455508241780972810983291314403571877524768509857276937926433221599399876886660808368837838027643282775172273657572744784112294389733810861607423253291974813120197604178281965697475898164531258434135959862784130128185406283476649088690521047580882615823961985770122407044330583075869039319604603404973156583208672105913300903752823415539745394397715257455290510212310947321610753474825740775273986348298498340756937955646638621874569499279016572103701364433135817214311791398222983845847334440270964182851005072927748364550578634501100852987812389473928699540834346158807043959118985815145779177143619698728131459483783202081474982171858011389071228250905826817436220577475921417653715687725614904582904992461028630081535583308130101987675856234343538955409175623400844887526162643568648833519463720377293240094456246923254350400678027273837755376406726898636241037491410966718557050759098100246789880178271925953381282421954028302759408448955014676668389697996886241636313376393903373455801407636741877711055384225739499110186468219696581651485130494222369947714763069155468217682876200362777257723781365331611196811280792669481887201298643660768551639860534602297871557517947385246369446923087894265948217008051120322365496288169035739121368338393591756418733850510970271613915439590991598154654417336311656936031122249937969999226781732358023111862644575299135758175008199839236284615249881088960232244362173771618086357015468484058622329792853875623486556440536962622018963571028812361567512543338303270029097668650568557157505516727518899194129711337690149916181315171544007728650573189557450920330185304847113818315407324053319038462084036421763703911550639789000742853672196280903477974533320468368795868580237952218629120080742819551317948157624448298518461509704888027274721574688131594750409732115080498190455803416826949787141316063210686391511681774304792596709376
In [29]:
s = str(x)
In [32]:
s.count()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[32], line 1
----> 1 s.count()

TypeError: count expected at least 1 argument, got 0
In [33]:
help(s.count)
Help on built-in function count:

count(sub[, start[, end]], /) method of builtins.str instance
    Return the number of non-overlapping occurrences of substring sub in string S[start:end].

    Optional arguments start and end are interpreted as in slice notation.

In [34]:
s.count("2")
Out[34]:
287

Индекс и срез

Символы нумеруются с нуля: s[i], последний — s[-1]. Срез s[a:b] берёт индексы $a, a+1, \ldots, b-1$ (правая граница не входит). У str нет отдельного типа «символ»: и s[i], и срез — снова строка. Присваивать в s[i] нельзя: строки неизменяемы.

In [35]:
s = "алгоритм"
print(s[0], s[2], s[-1])
print(s[0:4], s[4:])
а г м
алго ритм

Ветвления

Сравнения ==, !=, <, >, <=, >= дают True или False. Команды, сдвинутые на 4 пробела под if, выполняются только при истинном условии. Первая несдвинутая строка после блока идёт уже безусловно.

Вложенный if — проверка внутри другой проверки.

In [38]:
x = 2 ** 100
if isinstance(x, int) and x > 0:
    s = str(x)
    if s.count('2') > 0:
        print("Двоек в числе", x, s.count('2'))
Двоек в числе 1267650600228229401496703205376 6

Каскад if / elif / else выбирает один из взаимоисключающих исходов. Год високосный, если кратен 4, но не кратен 100, либо кратен 400.

In [39]:
year = 2000
if year % 400 == 0:
    print("YES")
elif year % 100 == 0:
    print("NO")
elif year % 4 == 0:
    print("YES")
else:
    print("NO")
YES

Циклы for и while

range(n) даёт $0, 1, \ldots, n-1$. Цикл for — когда число шагов известно. Цикл while повторяет тело, пока условие истинно; переменную условия нужно менять в теле, иначе цикл не остановится.

In [40]:
s = 0
for x in range(1, 6):
    s += x
print(s)
15
In [43]:
x = 1
while x < 100:
    print(x)
    x *= 2
1
2
4
8
16
32
64

Функции

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

Заголовок — строка def имя(параметры):. Тело сдвинуто на 4 пробела. Имена в скобках — формальные параметры; при вызове им подставляют фактические значения. return возвращает значение и заканчивает выполнение тела.

In [44]:
def maximum(a, b):
    if a >= b:
        return a
    return b

print(maximum(3, 8), maximum(10, 2))
8 10

Чистая функция

Если кроме вычисления значения функция ещё читает клавиатуру, печатает, меняет объекты снаружи — у неё есть побочные эффекты.

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

In [45]:
def is_leap(year):
    """Високосный ли год year (натуральное число)."""
    if year % 400 == 0:
        return True
    if year % 100 == 0:
        return False
    return year % 4 == 0

print(is_leap(2000), is_leap(1900), is_leap(2024))
True False True
In [46]:
help(is_leap)
Help on function is_leap in module __main__:

is_leap(year)
    Високосный ли год year (натуральное число).

Контракт и документ-строка

Вызов функции — контакт заказчика и поставщика. Заказчик обещает допустимый вход (здесь — натуральный номер года). Поставщик обещает результат, соответствующий условию (истинность «год високосный»). Нарушение договорённости на входе снимает ответственность за выход.

Минимум документации контракта — документ-строка: первая строка в теле, в тройных кавычках. Она описывает, что делает функция, что принимает и что возвращает. В примере выше это строка сразу под def.

Ввод и вывод на семинаре

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

year = int(input())
if is_leap(year):
    print("YES")
else:
    print("NO")

input() даёт строку до конца строки, int(...) делает из неё целое, print(...) пишет ответ.

Потоки чисел и память $O(1)$

Последовательность можно обработать, не запоминая её целиком: прочитали (или породили) очередное число, обновили несколько переменных, само число забыли. Число дополнительных ячеек не растёт с длиной входа — это и есть память $O(1)$ на этой лекции (без формального определения нотации).

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

Свёртка последовательности в одно число — редукция. Аккумулятор суммы инициализируют нулём, произведение — единицей (иначе всё занулится).

In [ ]:
s = 0
p = 1
for x in range(1, 6):
    s += x
    p *= x
print(s, p)

Максимум: помним текущий лучший, сравниваем с очередным. Ниже поток — остатки (k * 7) % 10 при $k = 0, 1, \ldots, 7$; в памяти только k, x и m.

In [ ]:
m = None
for k in range(8):
    x = (k * 7) % 10
    if m is None or x > m:
        m = x
print(m)

Генерация прогрессий

Член печатают сразу, последовательность не собирают. Арифметическая: постоянная разность. Геометрическая: постоянный множитель.

In [ ]:
x = 10
while x < 50:
    print(x)
    x += 5
In [ ]:
x = 1
while x < 100:
    print(x)
    x *= 2

Фильтрация

Редукция только по членам, прошедшим условие: учёт очередного числа ставят под if. Список отобранных не нужен, если ответ — одно число (здесь — сколько чётных).

In [ ]:
count = 0
for x in range(1, 11):
    if x % 2 == 0:
        count += 1
print(count)

Поэлементная обработка

Для каждого члена сразу печатают преобразование. Пример: квадраты, не превосходящие $n$.

In [ ]:
n = 20
k = 1
while k * k <= n:
    print(k * k)
    k += 1

На семинаре те же приёмы: разряды через // и %, ветвления, циклы, поток без списка.

Модель вычислений и нотации роста времени и памяти — на следующей лекции.