Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Программирование на языке Python. Учебное пособие.pdf
X
- •ПРЕДИСЛОВИЕ
- •I. ВВЕДЕНИЕ В ПРОГРАММИРОВАНИЕ
- •1. Начало работы
- •2. Основные понятия
- •Ввод и вывод данных
- •Логические операторы
- •Условные инструкции
- •Инструкции
- •Типы данных
- •Константы и переменные
- •Арифметические операторы
- •Операторы сравнения
- •3. Функции
- •Определение функций
- •Встроенные функции
- •Многократное использование функций
- •Типы параметров функции
- •Область видимости
- •Обработка исключений
- •Строки документации
- •4. Контейнеры
- •Методы
- •Списки
- •Кортежи
- •Словари
- •Контейнеры внутри контейнеров
- •5. Операции со строками
- •6. Циклы
- •Цикл for
- •Цикл while
- •Операторы break и continue
- •Функция Enumerate
- •Вложенные циклы
- •Настройка функции print()
- •7. Модули
- •8. Файлы
- •Запись в файлы
- •Использование конструкции with
- •Чтение из файлов
- •CSV-файлы
- •II. ВВЕДЕНИЕ В ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ (ООП)
- •9. Парадигмы программирования
- •Состояние
- •Процедурное программирование
- •Функциональное программирование
- •Объектно-ориентированное программирование
- •10. Базовые понятия ООП
- •Наследование
- •Полиморфизм
- •Абстракция
- •Инкапсуляция
- •Композиция
- •11. Дополнительные понятия ООП
- •Как работают переменные
- •Ключевое слово is
- •Константа None
- •Классы – это объекты
- •Переменные класса и переменные экземпляра класса
- •Приватные переменные
- •Методы переопределения
- •Функция super
- •Переопределение встроенных методов
- •12. Создание игр
- •Карты
- •Колода
- •Игрок
- •Игра
- •Игра «War»
- •13. Bash
- •Запуск Bash
- •Использование Bash
- •Относительные и абсолютные пути
- •Навигация
- •Флаги
- •Редактор vim
- •Команда touch
- •Просмотр файлов командой less
- •Пользователи
- •Разрешения
- •Программы Bash
- •Скрытые файлы
- •Переменные окружения
- •Переменная окружения $PATH
- •Вертикальная черта (пайп)
- •Команда cat
- •Последние команды
- •Быстрые перемещения
- •Экранирование (escaping)
- •Регулярные выражения в Python
- •15. Менеджеры пакетов
- •Вкладка «Завершение»
- •Функция wildcard (подстановочный знак)
- •Другие инструменты
- •14. Регулярные выражения
- •Настройка
- •Простое совпадение
- •Игнорирование регистра
- •Вывод только конкретных совпадений слов
- •Совпадение в начале и в конце
- •Поиск совпадений с несколькими символами
- •Повторение
- •Диапазон
- •16. Управление версиями
- •Репозитории
- •Помещение и извлечение данных
- •Пример помещения данных
- •Пример извлечения данных
- •Откат версий
- •Команда git diff
- •Другой запрос на включение
- •IV. ВВЕДЕНИЕ В ИНФОРМАТИКУ
- •17. Структуры данных и алгоритмы
- •Что такое алгоритмы и структуры данных?
- •Нотация Big-O
- •Оператор деления по модулю
- •Сортировка методом пузырька
- •Поисковый алгоритм
- •Рекурсия
- •Абстрактные типы данных
- •Узлы
- •Стеки
- •Связанные списки
- •Массивы
- •Бинарные деревья
- •Поиск в ширину и в глубину
- •Хэш-таблицы
- •ЗАКЛЮЧЕНИЕ
- •БИБЛИОГРАФИЧЕСКИЙ СПИСОК

Оператор деления по модулю
Если бы сначала проверяли, делится ли число только на 3 или только на 5,
Оператор деления по модулю обозначается символом «%» и возвращает
остаток от деления двух натуральных чисел. Например, «2 % 2» возвращает 0,
тогда как «3 % 2» возвращает 1. Иначе говоря, это алгоритм нахождения остатка от деления первого натурального числа на второе. Он полезен при решении
множества проблем, например таких, как FizzBuzz. Это известный тест, используемый на собеседованиях для выбора или отсеивания кандидатов. Вопрос был
введен Имраном Гори в его сообщении в блоге «Использование FizzBuzz для
поиска разработчиков, которые занимаются кодированием на Grok». Если вы
знаете правильный подход к проблеме, ее легко решить, а если нет, то она может показаться сложной. Задача обычно ставится в следующем виде:
Напишите программу, которая выводит числа от 1 до 100. Но вместо чи-
сел, кратных трем, выводите «Fizz», вместо кратных пяти выводите «Buzz»,
вместо кратных и трем, и пяти – «FizzBuzz».
Ключом к решению этой задачи является использование деления по моду-
лю. Чтобы решить задачу, просто нужно выполнить итерацию от 1 до 100
и проверить, делится ли каждое число на 3, 5 или на то и другое. Вот решение:
def fizz_buzz():
for i in range(0, 101):
if i % 3 == 0 and i % 5 == 0:
print 'FizzBuzz'
elif i % 3 == 0:
print 'Fizz'
elif i % 5 == 0:
print 'Buzz'
else:
print(i)
fizz_buzz()
Сначала перебираете числа от 1 до 100, затем проверяете, делится ли число
и на 3, и на 5. Это важно сделать в первую очередь, так как если число делится
и на 3, и на 5, нужно вывести FizzBuzz и перейти к следующей итерации цикла.
151

и обнаружили бы такое число, вы бы не могли вывести Fizz или Buzz и перейти
к следующей итерации цикла, поскольку это число все еще могло бы делиться
и на 3, и на 5. А в этом случае вывод Fizz или Buzz был бы неверен, ведь нужно
было бы выводить FizzBuzz.
После проверки порядок этих двух тестов больше не важен, так как известно, что число не делится на оба этих числа. Если число делится либо на 3,
либо на 5, вы останавливаете алгоритм и выводите Fizz или Buzz. Если число
проходит мимо первых трех условий, значит, оно не делится ни на 3, ни на 5
(ни на оба этих числа одновременно), и его можно вывести.
Вот еще одна задача, где использование деления по модулю является ключом к получению ответа:
Выполните циклический сдвиг массива из n элементов вправо на k шагов.
Например, при n = 7 и k = 3 массив [1,2,3,4,5,6,7] вращается до [5,6,7,1,2,3,4],
т.е. вы перемещаете каждый элемент списка на k позиций. Сдвиг также должен
быть кольцевым, то есть элемент, вышедший за пределы списка, должен появляться с другого его конца.
При решении этой задачи первым побуждением может быть простое добавление n к индексу каждого числа и перемещение числа на новую позицию.
Проблема в том, что это не работает. Ключ к решению задачи заключается
в использовании остатка от деления. Скажем, был список чисел от 1 до 12.
Можно думать об этом списке как о часах. Если начать с 12 часов и пройти по
часам двенадцать часов, можно вернуться к тому, с чего начали.
Можно добиться этого, взяв текущий индекс, добавив новый индекс и получить остаток. Если возьмем список из 12 чисел и рассчитаем, где будет число 12 (начиная с индекса 11), добавим 12 к индексу, можно вычислить это с помощью 11 +.
Сортировка методом пузырька
Алгоритм сортировки – это алгоритм, который берет группу чисел и упо-
рядочивает их в определенном порядке. Существует множество различных алгоритмов, таких как сортировка выбором, сортировка вставками, сортировка
оболочкой, сортировка слиянием и быстрая сортировка. Рассмотрим реализа-
152

цию пузырьковой сортировки, алгоритм которой не очень эффективен, но прост
для понимания и полезен для понимания алгоритмов сортировки:
def bubble_sort (num_list):
”””
:param num_list: List of numbers
:return : Sorted list of numbers
”””
for i in range(len (num_list)- 1 , 0 , - 1):
for j in range(i):
if num_list[j] > num_list[j + 1]:
temp = num_list[j]
num_list[j] = num_list[j + 1]
num_list[j + 1] = temp
my_list = [4, 266, 9, 24, 44, 54, 41, 89, 20]
bubble_sort(my_list)
print(my_list)
>> [4, 9, 20, 24, 41, 44, 54, 89, 266]
Поисковый алгоритм
Поисковый алгоритм используется для поиска числа в списке чисел: по-
следовательного поска и бинарного поиска.
Последовательный поиск – это простой поисковый алгоритм, который
проверяет каждое число в списке одно за другим, чтобы увидеть, соответствует
ли оно искомому числу. Последовательный поиск – это способ поиска определенной карты в колоде карт: вы проходите одну за другой через каждую карту,
если это та карта, которую ищете, вы останавливаетесь; если прошли через всю
колоду и не нашли карту, знаете, что карты там нет. Вот пример последовательного поиска:
def sequential_search(number_list, number):
”””
:param number_list: List of integers.
:param number: Integer to look for.
:return: True if the number is found otherwise false.
”””
153

found = False
for i in number_list:
if i == number:
found = True
break
return found
print sequential_search(range(0, 100), 2)
>> True
print sequential_search(range(0, 100), 202)
>> False
Первому “найденному” элементу присваивается значение False. Затем перебирается каждое число в списке и проверяется, равно ли оно искомому числу.
Если оно равно искомому числу, устанавливаете значение found равным True,
выходите из цикла и возвращаете True. В противном случае переходите к следующему номеру в списке. Если при прохождении по всему списку и для
найденного элемента никогда не будет установлено значение True, возвращате
found, для которого по-прежнему будет установлено значение False.
Последовательный поиск – это алгоритм, который принадлежит классу
вычислительной сложности O(n). В лучшем случае, число, которое ищете, может быть первым числом в списке, и в этом случае ваш алгоритм будет выполнять только один шаг. Однако в худшем случае этого числа нет в списке, тогда
нужно проверить каждое число в списке или n шагов. Если у вас есть миллион
элементов в списке, в худшем случае придется искать миллион элементов. По
мере роста списка наихудший сценарий для алгоритма увеличивается на размер
списка, делая этот алгоритм класса сложности O(n).
Бинарный (двоичный) поиск – тип поискового алгоритма, который по-
следовательно делит пополам заранее отсортированный массив данных, чтобы
обнаружить нужный элемент. Другие его названия – двоичный поиск, метод
половинного деления, дихотомия.
Заметим, что двоичный поиск – это алгоритм, работающий за логарифмическое время, используемый для поиска чисел в списке, но числа должны быть
упорядочены. Бинарный поиск работает за счет постоянного сокращения списка пополам. Алгоритм выбирает число в середине списка и проверяет, правильное ли это число. Если это правильный номер, поиск завершен. Если это непра-
154

вильное число, алгоритм отбрасывает половину списка. Если число было слишком большим, оно отбрасывает все, что находится выше выбранного им числа.
Если число было слишком маленьким, оно отбрасывает все, что находится ниже выбранного им числа.
Представьте, что имеется упорядоченный список от 1 до 10, в котором
нужно найти число 3. Этот список выглядит следующим образом:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Алгоритм сначала выберет число 5, потому что оно находится в середине
списка. Поскольку 5 – это не то число, которое ищите, а 3 меньше пяти, алгоритм отбрасывает все, что выше 5. Теперь список выглядит так:
[1, 2, 3, 4, 5]
Затем в списке будет выбрано число 3, потому что оно находится в середине списка. Поскольку 3 – это число, которое ищите, алгоритм остановится
и вернет его так, как оно было найдено в списке. Обратите внимание, что вашему алгоритму потребовалось всего два шага, чтобы определить, что в списке
было три. Если просматривать список линейно, один за другим, в поисках числа 3, это заняло бы три шага. Вот пример алгоритма двоичного поиска, который
ищет число 3:
def binary_search (number_list, number):
“““Logarithmic binary search algorithm.
:param number_list: List of ordered integers.
:param number: Integer to search for in the passed in list.
:return: True if the number is found, otherwise False.
”””
first = 0
last = len(number_list)-1
number_found = False
while first <= last and not number_found:
middle = (first + last)/2
if number_list[middle] == number:
number_found = True
else:
if number < number_list[middle]:
last = middle - 1
155

else:
first = middle + 1
return number_found
binary_search([1,2,3,4,5,6,7,8,9,10], 3)
Цикл while продолжается до тех пор, пока первая переменная не будет
больше или равна последней переменной, а переменная not_true имеет значение
False.
Вычисление среднего индекса списка заключается в сложении первого индекса списка с последним индексом и делении их на два. Причина, по которой
вычитается единица из последнего, заключается в том, что длина списка вычисляется, начиная с единицы, тогда как индексы начинаются с нуля.
Проверьте, является ли среднее число тем числом, которое вы ищете, просматривая среднее число в списке с помощью number_list[middle]. Если среднее
число – это число, которое ищете, для not_true устанавливаете значение True
и выходите из цикла. В противном случае проверяете, больше или меньше искомое число среднего числа. Если среднее число меньше, чем число, которое
ищете, то меняете последний индекс на средний индекс –1, который в следующем цикле разделит список пополам со всем, что меньше текущего среднего
числа; если среднее число больше, чем число, которое ищете, то меняете сначала на среднее +1, разделив список пополам, чтобы он содержал все, что больше
вашего среднего числа.
Рекурсия
Рекурсия известна как одна из самых сложных концепций для начинающих программистов. Рекурсия – это метод решения проблемы путем разбиения
ее на более мелкие части до тех пор, пока она не будет легко решена. Это достигается с помощью функции, которая вызывает сама себя. Любая проблема,
которая может быть решена рекурсивно, также может быть решена итеративно,
однако в некоторых случаях рекурсия предлагает более рациональное решение.
Рекурсивный алгоритм должен следовать трем правилам:
1. Рекурсивный алгоритм должен содержать базовый случай.
2. Рекурсивный алгоритм должен изменять свое состояние и переходить
к базовому случаю.
3. Рекурсивный алгоритм должен вызывать сам себя рекурсивно.
156

Рассмотрим пример рекурсивной функции, в которой есть все три правила.
Функция для распечатки текста популярной детской песенки «99 Bottles of
Beer on the Wall»:
def bottles_of_beer (bob):
””” Use recursion to print the bottles of beer song.
:param bob: Integer number of beers that arestart
on
the wall.
”””
if bob < 1:
print “No more bottles of beer on the wall. No more
bottles of beer.”
return
tmp = bob
bob -= 1
print“{} bottles of beer on the wall. {} bottles of
beer.
Take one down, pass it around, {} bottles of beer
on the wall.”.format(tmp, tmp, bob)
bottles_of_beer(bob)
bottles_of_beer(99)
>> 99 bottles of beer on the wall. 99 bottles of beer.
Take one down, pass it around, 98 bottles of beer on the
wall.
>> 98 bottles of beer on the wall. 98 bottles of beer.
Take one down, pass it around, 97 bottles of beer on the
wall.
…
>> No more bottles of beer on the wall. No more bottles
of beer.
В этом примере базовый случай:
if bob < 1:
print“No more bottles of beer on the wall. No more
bottles of beer”
return
157

Базовый случай рекурсивного алгоритма – это то, что в конечном итоге
останавливает алгоритм. Рекурсивный алгоритм без базового случая будет продолжать вызывать себя вечно, и можно получить ошибку времени выполнения,
говорящую о том, что максимальная глубина рекурсии превышена. Строка
bob -= 1 удовлетворяет второму правилу, согласно которому нужно двигать-
ся к базовому варианту. В рассматриваемом примере передали число «99»
в функцию в качестве параметра bob. Поскольку в базовом случае bob меньше 1
и поскольку bob начинается с 99, без этой строки мы никогда не достигнем
нашего базового случая, что также даст нам ошибку времени выполнения
с максимальной глубиной рекурсии.
Заключительное правило рекурсии реализуется с помощью конструкции
bottles_of_beer(bob)
С помощью этой строки осуществляется вызов функции. Функция будет
вызвана снова, но с одним важным отличием: вместо передачи 99, как при первом вызове функции, на этот раз будет передано 98 из-за правила номер два.
Далее вызвана третья функция с именем 97. Это будет продолжаться до тех
пор, пока bob не станет равным 0 и не будет достигнута базового случая.
В этот момент вы печатаете «No more bottles of beer on the wall. No more bottles
of beer» и возвращаетесь, сигнализируя алгоритму об остановке.
Рассмотрим еще один рекурсивный алгоритм.
Дано неотрицательное целое число, многократно складывайте все его цифры, пока в результате не останется только одна цифра.
Например:
Дано число num = 38, процесс выглядит следующим образом: 3 + 8 = 11,
1+1=2. Поскольку 2 содержит только одну цифру, верните эту цифру.
Одним из способов решения этой проблемы является использование рекурсии:
def add_digits (number):
”””
:param number: Int
:return : Single digit int
”””
158

number = str (number)
if len (number) == 1 :
return int (number)
the_sum = 0
for c in number:
the_sum += int (c)
return add_digits(the_sum)
print add_digits( 99 )
>> 9
В этом примере функция принимает число в качестве параметра, вычис-
ляет его сумму и вызывает себя до тех пор, пока не столкнется с базовым
случаем, то есть с числом, состоящим только из одной цифры. Если пропустили число 99, оно будет состоять из двух цифр, поэтому оно не соответствует
базовому регистру. Затем вычисляем, что the_sum равно 18, для этого снова
вызывается add_digits и передается 18. И снова 18 не проходит базовый
случай, поэтому вычисляется сумма цифр, которая на этот раз равна 9, и вызывается add_digits с 9 в качестве параметра. В третий раз число равно 9,
и поскольку это всего одна цифра, оно удовлетворяет базовому случаю, и выдаем ответ.
Абстрактные типы данных
При изучении структур данных встречается термин «абстрактный тип
данных». Понять, что это такое, можно, проводя аналогию связи между аб-
страктным типом данных и структурой данных, как о похожей (хотя и не совсем такой же), как о связи между классом и объектом.
Если нужно смоделировать апельсин в объектно-ориентированном про-
граммировании, класс представляет собой идею апельсина, тогда как объект
представляет собой фактический, реальный апельсин. Аналогично, абстрактный тип данных представляет собой идею определенного типа структуры данных. Примером абстрактного типа данных является список. Список – это абстрактный тип данных, который может быть реализован несколькими способами с помощью структур данных, таких как массив или связанный список.
159

Узлы
Узел – это термин, часто используемый в компьютерных науках. Можно
думать об узле как о точке на графике. Интернет состоит из маршрутизаторов,
которые взаимодействуют друг с другом. Каждый маршрутизатор является узлом в сети. Узлы используются в нескольких структурах данных, включая связанные списки, деревья и графы.
Стеки
Стек – это структура данных по принципу «последним вошел – первым
вышел» (LIFO, last-in-first-out). Лучше всего представить это как стопку посу-
ды. Скажем, вы ставите пять тарелок друг на друга. Чтобы добраться до последней тарелки в стопке, нужно убрать все остальные тарелки. Вот как работает структура данных стека. Вы помещаете данные в стек. Каждая часть данных
похожа на блюдо, и вы можете получить доступ к данным, только вытащив
данные из верхней части стека. Вот пример стека, реализованного на Python:
class Stack:
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def peek(self):
return self.items[len(self.items)-1]
def size(self):
return len(self.items)
Двумя наиболее важными методами в определении стека являются push
и pop. Push позволяет помещать данные в верхнюю часть стека, а pop – из-
влекать их из стека.
Стеки важны для понимания рекурсии. Рекурсия – фундаментальная часть
программирования. Большинство начинающих программистов борются с ре-
160
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
