CoolPython
4.65K subscribers
20 photos
44 links
Канал об основах Python и хороших практиках разработки. Создаём системность в обрывочных знаниях.

Тем, кто хочет понимать, что пишет!
Download Telegram
Как устроены словари в Python, часть 1.

Представьте себе огромную библиотеку, в которой вы хотите найти «Пикник на обочине». Как это сделать?

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

Для этого определим функцию, которая получает название книги и возвращает число. Такая функция-справочник:

«Пикник на обочине» -> 1
«Декамерон» -> 2
«Уловка 22» -> 3
...

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

1) вычислить номер книги по названию,
2) найти ее на полке с этим номером.

Получается сложность O(1) и это очень быстро! Положить книгу на свое место тоже можно за фиксированное число шагов, вне зависимости от размера библиотеки. Побочным эффектом такого подхода будет то, что в библиотеке не будет дубликатов книг, ведь все копии Декамерона будут попадать в ячейку с одним и тем же адресом.

Функция, которая ставит объекту в соответствие число, называется хеш-функцией. Любая хеш-функция должна удовлетворять требованию:

Если два объекта идентичны, то их хеши равны.

Идеальная хеш-функция должна удовлетворять еще одному требованию:

Если у двух объектов одинаковый хеш, то это одинаковые объекты.

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

«Облачный атлас» -> 10
«Москва-Петушки» -> 10

то это значит, что мы должны поставить две разные книги на одну полку. И когда мы приходим за книгой с номером 10, становится непонятно, какую из книг выбрать.
🔥1
Как устроены словари в Python, часть 2.

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

И если вдруг оказывается, что «Облачный атлас» и «Москва-Петушки» нужно положить в одну и ту же ячейку, то такую ситуацию называют коллизией.

Способов бороться с хеш-коллизиями концептуально два.

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

Это как если положить все книги с одинаковым номером на одну полку. Тогда при поиске книги придется найти нужную полку, взять первую книгу и прочитать название. Если не та — проверить следующую, и так далее. В худшем случае все n книг попадут на одну полку и сложность получится O(n).

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

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

В Python множествах и словарях используется метод открытой адресации.

Какие из всего этого выводы?

🐙 Ключи словаря должны быть хешируемыми.
🐙 Словари неэффективны по памяти. Если экономите память, используйте кортежи.
🐙 Поиск по ключу в итоге не O(1), но все равно очень быстрый.
🐙 Модифицировать словарь, по которому итерируешься — плохая идея. Интерпретатор может решить, что пора ресайзить хеш-таблицу и тогда старые данные переедут в новую табличку. Не стоит.

И все то же самое применимо к множествам, потому что они реализованы похожим образом.

🐙 А ещё становится понятно, что множества работают сильно быстрее списоков.

Кстати, пост не содержит совсем никаких рекомендаций, что почитать на досуге. 🐠

#словари #множества #реализация #алгоритмы #хеширование
👍2🔥1
Дублирование объектов в множестве

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

Создадим класс Client и добавим его в множество (сразу определила метод repr, чтобы были красивые принты):

class Client:
def __init__(self, user_name):
self.user_name = user_name

def __repr__(self):
return self.user_name

fish1 = Client(user_name="catfish")

clients = set()
clients.add(fish1)

print(clients) # {catfish}

Круто, все работает из коробки! Попробуем теперь добавить второй клиент, чтобы увидеть, что дублирования нет:

fish2 = Client(user_name="catfish")
clients.add(fish2)

print(clients) # {catfish, catfish}

Как же так? Мы добавили в множество два совершенно одинаковых экземпляра и ждали, что останется только один, но сохранились оба.

Интерпретатор при добавлении объекта в множество следует правилу:

если a == b, то обязательно hash(a) == hash(b)

То есть, сравнивает между собой как сами объекты, так и их хеши.

Для того, чтобы сравнивать объекты, множества и словари используют магический метод eq. Для пользовательских объектов он определен по умолчанию. И по умолчанию два разных объекта не равны, даже если они абсолютно идентичны:

print(fish1 == fish2) # False

По умолчанию все объекты в Python также хешируемы. Когда мы пытаемся добавить объект в множество, мы используем магический метод этого объекта hash, который тоже определен по умолчанию. Часто люди думают, что хеш объекта совпадает с его адресом в памяти, но это не всегда так:

🦑 в версиях Python 2.6 и ниже да, было просто hash(x) = id()
🦑
с версии 2.6 и выше: hash(x)==id(x)/16

То есть, рассчитывать на то, что hash(x) = id(), нельзя. Но можно рассчитывать на то, что Python объектах по умолчанию есть hash, который зависит от id() и на то, что хеш объекта в течение его жизни не меняется.

Так как в нашем примере объекты разные и располагаются в разных ячейках памяти, то

print(hash(fish1), hash(fish2)) # 8786876890805 8786876904409

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

Для этого исправим определение класса:

class Client:
def __init__(self, user_name):
self.user_name = user_name

def __repr__(self):
return self.user_name

def __hash__(self):
return hash(self.user_name)

def __eq__(self, other):
if self.user_name == other.user_name:
return True
else:
return False

Теперь при добавлении объектов в множество все работает как ожидали:

clients = set()

fish1 = Client(user_name="catfish")
clients.add(fish1)
fish2 = Client(user_name="catfish")
clients.add(fish2)

print(clients) # {catfish}

Помните, для того, чтобы объект класса, который вы определили сами, можно было добавить во множество или словарь, нужно:

🦑 Перегрузить методы hash и eq, причем оба, иначе не заработает
🦑 Если объекты идентичны, то и их хеши должны быть равны
🦑 В хэш-функцию должно попадать то, что однозначно идентифицирует объект
🦑 Хеш объекта не должен меняться в течение его жизни (иначе будете ловить чудеса в run time) 🐠

#словари #множества #реализация #хеширование
🔥1
Магические методы и пустые списки

Методы с двойным подчеркиванием в начале и в конце, например, __eq__, __hash__, __init__, в Python называют магическими методами. Их назвали так, потому что они добавляют магию в поведение класса.

Например, метод __init__ неявно вызывается при инициализации объекта:

Foo:
def __init__(self, a, b):
self.a = a
self.b = b

foo = Foo(7, 9) # вызывается __init__

print(foo.a, foo.b) # 7, 9

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

Зачем нам это? Это нестареющая классика: чтобы проверить, словарь или список на непустоту, начинающие часто используют стиль как в C++:

if len(a) != 0:
pass

В то время как PEP8 рекомендует делать просто:

if not a:
pass

И это работает ровно потому, что в момент if a: вызывается метод __bool__ класса список.

Для пользовательских объектов, где __bool__ не перегружен, по умолчанию возвращается True.

if foo:
print(True)
else:
print(False)

# True

Но если добавить

Foo:
def __init__(self, a, b):
self.a = a
self.b = b

def __bool__(self):
return False

То поведение изменится:

if foo:
print(True)
else:
print(False)

# False

Кстати, в случае, если метода __bool__ в классе нет, то интерпретатор будет искать метод __len__ (длина). Поведение такое же: если __len__ возвращает 0, то логическое значение оценивается как False.

Что с этим делать? Помнить, что falsy (значения, которые оцениваются как False) в Python это:

🐙 пустые словари {},
🐙 списки [],
🐙 кортежи (),
🐙 множества set(),
🐙 строки "",
🐙 пустые range(0),
🐙 нули любого численного типа: 0, 0.0, 0j
🐙 константы None и собственно False

А truthy значения это:

🐙 любые пользовательские объекты по умолчанию
🐙 непустые словари, множества, строки, списки, ...
🐙 константа True

И писать проверку на непустоту красиво:

not a:
pass

#magic #dunder #bool #len #truthy #falsy #начинающим
🔥1
Как правильно произносить название языка?

По-простому говорят «питон», так же название языка и переводится.

За это можно огребсти в сообществе, потому что по-православному, канонично говорить «пайтон». Ведь создатель языка Гвидо Ван Россум назвал язык в честь шоу «Летающий цирк Монти Пайтона», которое он очень любил.

Кстати, название для шоу рождалось в муках. Среди имен, из которых выбирали, были:

- Owl Stretching Time,
- The Toad Elevating Moment,
- A Horse, a Spoon and a Bucket,
- Vaseline Review,
- Bun, Wackett, Buzzard, Stubble and Boot.

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

Название шоу не означает буквально ничего, а слово Python выбрали, потому что хотели добавить в название склизкости за счет змеи.

Семейство змей, в свою очередь, назвал питонидами французский зоолог Франсуа-Мари Доден в честь дракона, которого, согласно мифу, умертвил Аполлон у входа в Дельфийский оракул. Дракона звали Пифоном.

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

#название #начинающим
1
Дэвид Бизли, звезда Python сообщества и автор бомбических докладов про асинхронность в Python, выпустил бесплатный курс Practical Python Programming.

Курс не рассчитан на новичков, скорее на программистов, которые уже знакомы с основами Python или с другим языком. Курс не про веб-программирование, а про сам язык, так что инженерам и ученым тоже будет полезно.

Бизли сам пишет на Python 25 лет и половину этого времени преподает его, доверять можно. Очень рекомендую!
🔥1
Целочисленное деление

Как вы знаете, в Python есть два способа делить целые числа:

Оператор / при делении целого числа на целое возвращает результат типа float.

>>> 3 / 2
1.5 # float

А оператор // при делении целого на целое возвращает целое с округлением вниз, по-английски этот оператор еще называют floor division.

>>> 3 // 2
1 # int

Копалась в истории языка и узнала, что изначально деление целых чисел в Python было только целочисленное, и для него Гвидо использовал символ /. Но быстро стало понятно, что в языке, где программист не объявляет типы явно, использование такого символа неудобно и будет приводить к обилию ошибок в программах.

Поэтому часть сообщества (включая Гвидо) настаивала на том, что оператором целочисленного деления нужно выбрать символ //, а оператор деления, который возвращает тип float, должен быть /. Другие разработчики говорили, что целочисленное деление нужно писать как /, аргументируя это тем, что «научиться обращаться с целочисленным делением — это как обряд инициации для начинающих программистов», да и в C целочисленное деление пишется именно так.

Вот что Гвидо пишет об этом в своем блоге (в моем вольном переводе):

Чтобы услышать мнение человека, далекого от программирования, я спросил свою подругу, которая занималась средневековой английской литературой. Разговор был примерно такой:

Я: Вот ты берешь два целых числа. Хорошо?
Она: Хорошо!
Я: Теперь ты можешь их сложить, вычесть, умножить в программе, правильно?
Она: Хм, конечно!
Я: А теперь предположим, что ты их делишь...
Она: Но в этом нет смысла! Целые числа образуют кольцо, а не поле. Ты ведь сказал, что взял эти числа из Z, верно?
Я: Ага.

Вот и все. Это точка зрения непрограммиста (с которой мне удобно согласиться). Целочисленное деление — это чушь и его не нужно писать как "/", даже если в C делают именно так;-)

Действительно, с точки зрения непрограммиста, целочисленное деление довольно странная история, ведь над целыми числами, как и над матрицами, нет деления в принципе!

Источники: [1], [2].

Кстати, я сейчас готовлю курс по пайтон для начинающих (не то чтобы я считала, что на рынке мало курсов, но я просто люблю учить людей). Как считаете, стоит добавить этот фрагмент в курс, или лучше вырезать?
👍1
Работа с JSON

Хотела собрать для менти ссылок по работе с JSON, но не нашла, чтобы в одном месте последовательно, исчерпывающе и без воды рассказали бы все самое важное, что питонисту нужно знать о работе с этим форматом.

Поэтому сделала лонгрид по JSON'у сама и делюсь с вами! Статью в PDF можно посмотреть на моем Patreon'e, она бесплатная. Рассчитана на уровень джуниоров и стажеров. В канал выложить не могу, потому что слишком много текста, а в тексте картинки, схемы и даже один мем!

Из головных болей: не получилось сверстать так идеально, как хотелось. Начала с LaTeX, поняла, что верстается долго, перешла в markdown, но там некрасиво легли картинки. Наконец, сделала в Notion и все равно недовольна. Дайте знать, если умеете красиво верстать без боли и адских страданий или можете рассказать, на какой платформе вам удобнее читать лонгриды.

🐠

Один из отзывов: Прочитал твою статью про json! Восторг! Ты так хорошо и спокойно объясняешь! Ты смогла мои обрывочные знания систематизировать.

Спасибо вам за такие хорошие слова🤍 Стараюсь для вас.
🔥1
Открываю курс по Python.

Этой осенью ко мне в очередной раз зашла пара онлайн школ с предложением написать для них курс по Python. В очередной раз отказавшись, я решила сделать такой курс сама. После двух месяцев интенсивной и кропотливой работы я открываю продажи этого курса. 🐠

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

О курсе:
🐟 Теория и задачи ждут вас на платформе Stepik.
🐟 Сейчас курс продается за 2700 рублей и можно пройти первые 5 модулей. Остальные 7 модулей я открою для прохождения в течение 7 недель.
🐟 Я внимательно слежу за комментариями и буду обновлять и дополнять материал там, где это будет нужно.
🐟 Когда я открою последний модуль, цена вырастет в полтора раза. Так что предлагаю заинтересованным опередить меня!

Научиться писать на Python под моим наблюдением можно здесь.
👍1
Блок else в циклах

Многие теряются при виде блока else в циклах. А между тем в Python он используется не только в условных конструкциях. Рассмотрим на примере цикла for:
 
for i in range(1, 3):
print(i)
else:
print("loop finished normally")
# 1
# 2
# loop finished normally

Блок else выполняется, если выполняется условие выхода из цикла. Это значит, что если цикл прерывается как-то иначе, например, ключевым словом break или исключением, то блок else выполнен не будет:
 
for i in range(1, 4):
if i == 2:
raise Exception("error occurred")
else:
print("loop finished normally")
# Exception: error occurred

Чаще всего этим пользуются, когда ищут что-нибудь в цикле с использованием ключевого слова break:
 
numbers = [1, 3, 11, 5]
for number in numbers:
if number % 2 == 0:
print ("even number found")
break
else:
print ("no even numbers in the list")
# no even numbers in the list

С циклами while можете попробовать самостоятельно. 🐠

#циклы #for #while #else
🔥1
Печать списков

Если вдруг на собеседовании вам понадобится вывести список (или множество) чисел на печать одной строкой, то вот изящный способ это сделать:
 
>>> numbers = [1, 2, 3, 4, 5]
>>> print(*numbers)
1 2 3 4 5


Пользуйтесь! 🐠

#списки #множества #iterables #print
🔥1
Прямой эфир о резюме

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

Поэтому предлагаю вам вот такой формат: мы с моей коллегой, senior разработчицей и основательницей школы IT girl school Алисой проведем в четверг в 20:00 прямой эфир, на котором разберем и перепишем три джуниорских резюме. Увидите на примерах, как лучше писать, чтобы вас заметили работодатели.

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

На эфир приглашаем стажеров, джуниор программистов и тех, кто интересуется темой. Старт в 20:00 в четверг 24 декабря, участие бесплатное. Добавьте событие в свои календари, чтобы не забыть.

Ссылка на эфир: https://us02web.zoom.us/j/86743972942?pwd=eHVIeUFvSklGaUZaK3N2WnlUSDFxZz09
Резюме для разбора можно прислать мне или Алисе.

Ставьте огонек, если собираетесь прийти, чтобы я понимала, сколько нас будет🔥
👍1
Вот запись вчерашнего эфира с разбором резюме. Сначала там рассказ минут на 10 от меня про самые частые ошибки в резюме, а дальше мы с Алисой разбираем и комментируем 4 совершенно разных документа и отвечаем на вопросы из чата.

Запись вот: https://youtu.be/AZe4_XVRuxA

Если то, что мы делаем на эфире, кажется вам полезным и близким, но все равно непонятно, как именно вам получить повышение или работу, то приходите в личку @chakchurina, обсудим, что можно сделать в вашем случае.
Прикольные новости Jupyter: теперь его можно запустить прямо в excel'е. Кому-то покажется, что это дорога в ад, а кого-то избавит от необходимости выбирать между привычными инструментами для аналитики. Что думаете?

Источник: https://www.pyxll.com/blog/python-jupyter-notebooks-in-excel/
Замена в списке

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

>>> a = [1, 2, 3, 4, 10]
>>> b = [5, 6, 7]

нужно получить:

[1, 5, 6, 7, 10]

Делаем:

>>> a[1:4] = b

Получаем:

>>> a
[1, 5, 6, 7, 10]

Готово, вы восхитительны!

#списки #коллекции #слайсы
🔥1
Релиз NumPy

Вчера вышел самый крупный в истории NumPy релиз: версия 1.20.0. Библиотека в этой версии поддерживает Python 3.7 — 3.9, а ниже нет. Если работаете с NumPy, посмотрите полный список изменений. 🐠

#новости
👍1
Друзья, я закончила свой курс по Python для начинающих.

🐙 Несколько мест переработала по отзывам.
🐙 Когда я пишу, промахиваюсь по клавишам и в курсе было много опечаток, которые мы со студентами исправили.
🐙 Сделала чат поддержки, в котором помогаю и отвечаю на вопросы (после регистрации приходит ссылка на почту).
🐙 Курс одобрен командой Степик и попал в рекомендации платформы.
🐙 Как и обещала, когда закончила делать материалы, подняла цену на курс.
🐙 Добавила сертификат о прохождении.
🐙 Вернула историческое название, так что в сертификате будет именно оно.

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

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

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

Группа стартует 15 февраля и я рассчитываю на 6 недель: по 2 раздела в неделю. Получится плотно, но я буду помогать с задачами в чате, созваниваться по Зуму, делать review решений в группе. Набираю всего 30 человек, потому что больше мне самой будет сложно.

Если давно хотели выучить Python, но не знали, с чего начать, то давайте начнем вместе. 🐠

PS: после регистрации на почту придет ссылка на группу, не пропустите ее.
👍2
Круглый стол по Kubernetes

Допустим, в вашей компании решили перейти на Kubernetes. Это должно было повысить стабильность сервисов, увеличить скорость разработки... Но в реальности этого не произошло. В чем причина: Kubernetes не работает или процессы не отлажены?

Приходите на бесплатный круглый стол 11 февраля в 19:00. Поговорим о том, кто какие задачи решает в кластере Kubernetes, как уменьшить количество необходимых ресурсов для перехода на k8s, за счет чего сокращается time-to-market и причем тут вообще разработчик.

На встрече будут инженеры эксплуатации и разработчики из Mail.ru Cloud Solutions, Слёрм и Southbridge.

#ad
👍1
Музыкальная пауза

Песни, которые слышим чаще, чем хочется

1. func (1)
2. dct ['key'] = lst [index]
3. ham[ 1 ]
4. counter +=1
5. complex(real, imag = 0.0)
6. import sys, os
7. except Exception:
8. if type(obj) is
int:
9. if len(values)
== 0:
10. if flag == True

https://youtu.be/hgI0p1zf31k
👍1
Задача про скобки.

Супер распространенная задача со скрининг-собеседований.

На вход подается строка, состоящая из круглых скобок. Выведите True, если скобки вложены правильно и False, если нет.

Например, если входная строка
(()(())), то ответ должен быть True.
А если ()), то False.

Это базовая задача на алгоритмы и решается она за один проход по строке (O(n)). Идея здесь следующая: нужно завести переменную-стек, которая будет хранить состояние скобки на i-том шаге и в зависимости от состояния принимать решение о том, валидная строка или нет.

Вот видео с более подробным описанием решения.

Я видела варианты, в которых люди пишут свой класс для стека, но в качестве него сработает и обычный список, если использовать только методы append() и pop().

Вот возможное решение, которое мне нравится:

def if_balanced(string):
stack = []
for i in string:
if i == "(":
stack.append(i)
elif i == ")":
if not stack:
return False
stack.pop()
if stack:
return False
return True


print(if_balanced("()()())"))

Усложненная форма этой же задачи: на вход подаются строка со скобками разных видов, например, ([]{}) -> True, ({}([]{)}) -> False.

Можете решить ее самостоятельно. 🐠

UPD: в комментарии накинули решение без стека:)
🔥1
Задача о циклическом сдвиге

Предложила ее менти как тренировочную.

Циклический сдвиг массива — это когда каждый элемент кроме последнего сдвигается вправо, а последний элемент массива становится первым.

На вход подаются массив A и целое число K. Сделайте циклический сдвиг входного массива K раз и верните получившийся массив.

Например, для данных:

A = [2, 5, 1, 4, 6]
K = 3


Циклический сдвиг должен быть выполнен трижды:

[2, 5, 1, 4, 6] -> [6, 2, 5, 1, 4]
[6, 2, 5, 1, 4] -> [4, 6, 2, 5, 1]
[4, 6, 2, 5, 1] -> [1, 4, 6, 2, 5]

И программа должна вернуть

[1, 4, 6, 2, 5]


Если K = 0, то сдвиг не делается.

Для K = 5:

[2, 5, 1, 4, 6] -> [2, 5, 1, 4, 6]


Наивное решение:

1. Определяем, что такое ротировать массив один раз.
2. Делаем это K раз.

def rotate(A, K):
""" Rotates a list K times """
if not A:
return []

for i in range(K):
A = rotate_once(A)
return A


def rotate_once(A):
""" Rotates a list once """
A.insert(0, A[-1])
del A[-1]
return A


Это решение не оптимальное, потому что каждый insert() передвигает все элементы исходного списка. Также оно не учитывает, что после len(A) ротаций список возвращается в исходное состояние.

Как решила ученица. Она заметила, что индексы элементов в новом массиве несложно рассчитать аналитически:

def rotate(A, K):
""" Rotates a list K times """
result = []
if K == 0:
return A
else:
for i in range(len(A)):
new_index = (i - K) % len(A)
result.append(A[new_index])
return result


Это круче, потому что
🐙 результат получается за один проход
🐙 и время работы не увеличивается на больших K.

Очень радуюсь, когда получается у учеников!
👍2