<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>@python__lounge</title><author><name>@python__lounge</name></author><id>https://teletype.in/atom/python__lounge</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/python__lounge?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@python__lounge?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=python__lounge"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/python__lounge?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-06-15T21:15:56.746Z</updated><entry><id>python__lounge:zWhdmS5SUAF</id><link rel="alternate" type="text/html" href="https://teletype.in/@python__lounge/zWhdmS5SUAF?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=python__lounge"></link><title>14 Python-пакетов, про которые вы скорее всего не знали</title><published>2021-05-27T18:25:39.373Z</published><updated>2021-05-27T18:25:39.373Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/f5/fe/f5fef249-2d89-4947-87e9-ecc7bfb65fa5.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://telegra.ph/file/c53df7e635b6844559143.png&quot;&gt;Язык Python предоставляет всем пользователям возможность создавать свои пакеты и делиться ими со всем сообществом. Так появлялись очень популярные библиотеки для работы с данными (Pandas, Numpy, Matplotlib), для машинного обучения (TensorFlow, PyTorch), для веб разработки. Но есть много смешных, полезных или интересных пакетов, про которые вы вероятно никогда не слышали. Именно про них и пойдёт речь дальше.</summary><content type="html">
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://telegra.ph/file/c53df7e635b6844559143.png&quot; width=&quot;1302&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Язык Python предоставляет всем пользователям возможность создавать свои пакеты и делиться ими со всем сообществом. Так появлялись очень популярные библиотеки для работы с данными (&lt;code&gt;Pandas&lt;/code&gt;, &lt;code&gt;Numpy&lt;/code&gt;, &lt;code&gt;Matplotlib&lt;/code&gt;), для машинного обучения (&lt;code&gt;TensorFlow&lt;/code&gt;, &lt;code&gt;PyTorch&lt;/code&gt;), для веб разработки. Но есть много смешных, полезных или интересных пакетов, про которые вы вероятно никогда не слышали. Именно про них и пойдёт речь дальше.&lt;/p&gt;
  &lt;p&gt;Модули, которые не являются частью стандартной библиотеки Python, можно установить с помощью &lt;code&gt;pip install &amp;lt;название пакета&amp;gt;&lt;/code&gt;.&lt;/p&gt;
  &lt;h3&gt;tqdm&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: False&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Отличный инструмент для подключения диаграммы выполнения вашей программы. Название происходит от арабского слова &amp;quot;taqadum&amp;quot;, что означает &amp;quot;прогресс&amp;quot;.&lt;/p&gt;
  &lt;p&gt;Для создания прогресс-бара необходим лишь один вызов функции:&lt;/p&gt;
  &lt;pre&gt;from tqdm import tqdm
from tqdm.notebook import tqdm # для использования в Jupyter Notebook

for i in tqdm(range(10000)):
    ...
&lt;/pre&gt;
  &lt;p&gt;Пример прогресс-бара:&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/webt/av/dk/mx/avdkmxn5gneaw6fcyayooybd3va.gif&quot; width=&quot;800&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;В реальном времени показывается процент выполнения, визуальное представление, сколько итераций завершилось, скорость выполнения и ожидаемое время окончания. Также модуль не требует никаких внешних библиотек.&lt;/p&gt;
  &lt;h3&gt;prettytable&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: False&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Пакет для красивого оформления и вывода таблиц. Есть поддержка заголовков столбцов, импорт из CSV и SQL. Синтаксис опять же очень простой:&lt;/p&gt;
  &lt;pre&gt;from prettytable import PrettyTable

x = PrettyTable()
x.field_names = [&amp;quot;Country&amp;quot;, &amp;quot;Capital&amp;quot;, &amp;quot;is_russia&amp;quot;]
x.add_row([&amp;quot;Russia&amp;quot;, &amp;quot;Moscow&amp;quot;, True])
x.add_rows([[&amp;quot;Argentina&amp;quot;, &amp;quot;Buenos Aires&amp;quot;, False], [&amp;quot;Jamaica&amp;quot;, &amp;quot;Kingston&amp;quot;, False]])
x.add_column(&amp;quot;Starts with A&amp;quot;, [False, True, False])

print(x)
&lt;/pre&gt;
  &lt;p&gt;Вывод:&lt;/p&gt;
  &lt;pre&gt;+-----------+--------------+-----------+---------------+
|  Country  |   Capital    | is_russia | Starts with A |
+-----------+--------------+-----------+---------------+
|   Russia  |    Moscow    |    True   |     False     |
| Argentina | Buenos Aires |   False   |      True     |
|  Jamaica  |   Kingston   |   False   |     False     |
+-----------+--------------+-----------+---------------+
&lt;/pre&gt;
  &lt;p&gt;Подробнее можно почитать на &lt;a href=&quot;https://pypi.org/project/prettytable/&quot; target=&quot;_blank&quot;&gt;странице проекта&lt;/a&gt;.&lt;/p&gt;
  &lt;h3&gt;emoji&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: False&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Модуль позволяет получить эмодзи по его текстовому названию. Может быть полезно в проектах, где используются эмодзи, но не хочется хранить их как символ, потому что IDE может плохо их отображать. При этом по названию будет понятен смысл смайлика. Также при написании приложения для обмена сообщениями это будет удобной фичей для пользователей.&lt;/p&gt;
  &lt;p&gt;Есть поддержка нескольких языков и синонимов (алиасов).&lt;/p&gt;
  &lt;pre&gt;import emoji

print(emoji.emojize(&amp;#x27;Habr is :thumbsup:&amp;#x27;, use_aliases=True))  # Habr is 
print(emoji.demojize(&amp;#x27;Habr is &amp;#x27;))  # Habr is :thumbsup:
print(emoji.demojize(&amp;#x27;Habr es &amp;#x27;, language=&amp;#x27;es&amp;#x27;))  # Habr es :pulgar_hacia_arriba:
&lt;/pre&gt;
  &lt;p&gt;Шпаргалку по названиям эмодзи можно посмотреть &lt;a href=&quot;https://www.webfx.com/tools/emoji-cheat-sheet/&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;&lt;/p&gt;
  &lt;h3&gt;uuid&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: True&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Пакет для генерации случайных &lt;code&gt;id&lt;/code&gt;, поддерживает разные модели генерации. Например, &lt;code&gt;uuid4&lt;/code&gt; генерирует рандомный &lt;code&gt;id&lt;/code&gt;, а &lt;code&gt;uuid3&lt;/code&gt; на основе md5 от названия объекта, для которого генерируется &lt;code&gt;id&lt;/code&gt;. Как всегда, подробности можно найти в &lt;a href=&quot;https://docs.python.org/3/library/uuid.html&quot; target=&quot;_blank&quot;&gt;документации&lt;/a&gt;.&lt;/p&gt;
  &lt;pre&gt;import uuid

print(uuid.uuid4())  # example: 2d8959d3-a5b3-42ef-8723-b4f4532f6d14
print(uuid.uuid3(uuid.NAMESPACE_URL, &amp;quot;https://habr.com/ru/users/otter18/&amp;quot;))  # e521cf18-5147-3137-9ca5-51bcbebe1b74
&lt;/pre&gt;
  &lt;h3&gt;freegames&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: False&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Хотите поиграть на рабочем месте, но игры слишком много весят? Или вы боитесь, что босс увидит, чем вы занимаетесь? Выход есть!&lt;/p&gt;
  &lt;pre&gt;python -m pip install freegames
&lt;/pre&gt;
  &lt;p&gt;Теперь вы можете запустить любую игру из списка:&lt;/p&gt;
  &lt;pre&gt;ant
bagels
bounce
cannon
connect
crypto
fidget
flappy
guess
life
maze
memory
minesweeper
pacman
paint
pong
simonsays
snake
tictactoe
tiles
tron
&lt;/pre&gt;
  &lt;p&gt;Для запуска игры &lt;code&gt;snake&lt;/code&gt; используйте команду&lt;/p&gt;
  &lt;pre&gt;python -m freegames.snake
&lt;/pre&gt;
  &lt;p&gt;Крестики-нолики:&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/webt/xg/hb/yl/xghbyla_cjw7u4_1v77pfy47yds.png&quot; width=&quot;523&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Игра на запоминание чисел:&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/webt/yu/ui/kd/yuuikdmhjkccxumltsvdhgbiixc.png&quot; width=&quot;518&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Все игры сделаны с помощью черепашки-рисовальщика.&lt;/p&gt;
  &lt;h3&gt;howdoi&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: False&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Ещё один прекрасный модуль, который улучшит вашу жизнь. Теперь вам не надо открывать несколько вопросов на &lt;code&gt;StackOverflow&lt;/code&gt;, на которые нет понятного ответа с кодом. Вместо этого для вас решение найдёт &lt;code&gt;howdoi&lt;/code&gt;:&lt;/p&gt;
  &lt;pre&gt;$ howdoi install all python modules in project
pip install -r requirements.txt
&lt;/pre&gt;
  &lt;p&gt;Также есть поддержка подсветки, определённых сайтов и поисковых систем.&lt;/p&gt;
  &lt;h3&gt;pyjokes&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: False&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Если в вашей крови течёт &lt;code&gt;PEP&lt;/code&gt;, а дома вас ждёт домашняя змея в клетке, то и шутки вам нужны соответствующие. Вы всегда можете получить их с помощью модуля &lt;code&gt;pyjokes&lt;/code&gt;.&lt;/p&gt;
  &lt;pre&gt;import pyjokes

print(pyjokes.get_joke())
&lt;/pre&gt;
  &lt;p&gt;Также есть отдельная категория с шутками Чака Норриса. &lt;code&gt;Don&amp;#x27;t worry about tests, Chuck Norris&amp;#x27;s test cases cover your code too.&lt;/code&gt;&lt;/p&gt;
  &lt;h3&gt;Fabulous&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: False&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Вам не достаточно красивого вывода таблиц в консоль? А как насчёт вывода текста с тенями или даже картинок? Теперь это возможно! (Использовать только в небольших количествах во избежание перелома чувства прекрасного)&lt;/p&gt;
  &lt;pre&gt;from fabulous import text, image
print(text.Text(&amp;quot;Барсук!&amp;quot;, color=&amp;#x27;#ff8c00&amp;#x27;, shadow=True, skew=5))
print(image.Image(&amp;quot;барсук.png&amp;quot;))
&lt;/pre&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/webt/b0/tu/c1/b0tuc1ht24mfhjoazcuzqjfbcdu.png&quot; width=&quot;2304&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3&gt;art&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: False&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Всё, что вам может понадобится для вывода красивых надписей или артов в терминал.&lt;/p&gt;
  &lt;pre&gt;import art

print(art.randart())  # \m/(-_-)\m/
art.tprint(&amp;quot;Habr&amp;quot;, &amp;quot;mix&amp;quot;)  # надпись Habr буквами из разных языков и шрифтов, которые могут не читаться вашими устройствами
&lt;/pre&gt;
  &lt;p&gt;Мне особенно нравится вывод больших букв:&lt;/p&gt;
  &lt;pre&gt;import art

print(art.text2art(&amp;quot;Habr&amp;quot;, font=&amp;#x27;block&amp;#x27;))
&lt;/pre&gt;
  &lt;pre&gt; .----------------.  .----------------.  .----------------.  .----------------.
| .--------------. || .--------------. || .--------------. || .--------------. |
| |  ____  ____  | || |      __      | || |   ______     | || |  _______     | |
| | |_   ||   _| | || |     /  \     | || |  |_   _ \    | || | |_   __ \    | |
| |   | |__| |   | || |    / /\ \    | || |    | |_) |   | || |   | |__) |   | |
| |   |  __  |   | || |   / ____ \   | || |    |  __&amp;#x27;.   | || |   |  __ /    | |
| |  _| |  | |_  | || | _/ /    \ \_ | || |   _| |__) |  | || |  _| |  \ \_  | |
| | |____||____| | || ||____|  |____|| || |  |_______/   | || | |____| |___| | |
| |              | || |              | || |              | || |              | |
| &amp;#x27;--------------&amp;#x27; || &amp;#x27;--------------&amp;#x27; || &amp;#x27;--------------&amp;#x27; || &amp;#x27;--------------&amp;#x27; |
 &amp;#x27;----------------&amp;#x27;  &amp;#x27;----------------&amp;#x27;  &amp;#x27;----------------&amp;#x27;  &amp;#x27;----------------&amp;#x27;
&lt;/pre&gt;
  &lt;p&gt;Полный список возможностей можно увидеть в &lt;a href=&quot;https://pypi.org/project/art/&quot; target=&quot;_blank&quot;&gt;документации&lt;/a&gt;.&lt;/p&gt;
  &lt;h3&gt;pyperclip&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: False&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Модуль для работы с буфером обмена. Подобные возможности также есть в модуле &lt;code&gt;Tkinter&lt;/code&gt; (и я пользовался этим способом, потому что он выдавался в Гугле первым):&lt;/p&gt;
  &lt;pre&gt;from tkinter import Tk

tk = Tk()
tk.withdraw()
tk.clipboard_clear()
tk.clipboard_append(&amp;#x27;New clipboard comtent&amp;#x27;)
tk.update()
tk.destroy()
&lt;/pre&gt;
  &lt;p&gt;Но pyperclip делает жизнь проще:&lt;/p&gt;
  &lt;pre&gt;import pyperclip
pyperclip.copy(&amp;#x27;Habr is the best!&amp;#x27;)
pyperclip.paste()
&lt;/pre&gt;
  &lt;p&gt;Под капотом стоит обработка версии системы и поиск соответствующего способа работы с буфером обмена. Например, с помощью командной строки.&lt;/p&gt;
  &lt;h3&gt;moviepy&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: False&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Пакет для редактирования видео. Имеет такие полезные функции как: обрезка, добавления текста поверх ихображения, поворот видео, создания GIF, коррекция цвета и много другое.&lt;/p&gt;
  &lt;p&gt;Одним словом, если вы хотите автоматизировать простую работу над видео, то это прекрасный инструмент для ваших целей.&lt;/p&gt;
  &lt;p&gt;Пример из официальной документации: наложение двух видеофайлов и движущегося текста&lt;/p&gt;
  &lt;pre&gt;from moviepy.editor import *

# импорт основного видеоряда
ukulele = VideoFileClip(&amp;quot;ukulele.MOV&amp;quot;, audio=False).subclip(60+33, 60+50).crop(486, 180, 1196, 570)
w,h = moviesize = ukulele.size

# импорт пианино
piano = (VideoFileClip(&amp;quot;piano.mp4&amp;quot;,audio=False).subclip(30,50).resize((w/3,h/3)).margin( 6,color=(255,255,255).margin( bottom=20, right=20, opacity=0).set_pos((&amp;#x27;right&amp;#x27;,&amp;#x27;bottom&amp;#x27;)) )

# создание текста
txt = TextClip(&amp;quot;V. Zulkoninov - Ukulele Sonata&amp;quot;, font=&amp;#x27;Amiri-regular&amp;#x27;, color=&amp;#x27;white&amp;#x27;,fontsize=24)
txt_col = txt.on_color(size=(ukulele.w + txt.w,txt.h-10), color=(0,0,0), pos=(6,&amp;#x27;center&amp;#x27;), col_opacity=0.6)

# создание траектории движения текста: зависимость координаты от времени
txt_mov = txt_col.set_pos(lambda t: (max(w/30,int(w-0.5*w*t)), max(5*h/6,int(100*t))))

# генерация финального результата
final = CompositeVideoClip([ukulele,txt_mov,piano])
final.subclip(0,5).write_videofile(&amp;quot;.avi&amp;quot;,fps=24,codec=&amp;#x27;libx264&amp;#x27;)
&lt;/pre&gt;
  &lt;p&gt;Результат: &lt;a href=&quot;https://youtu.be/AqGZ4JFkQTU&quot; target=&quot;_blank&quot;&gt;https://youtu.be/AqGZ4JFkQTU&lt;/a&gt;&lt;/p&gt;
  &lt;h3&gt;Antigravity&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: True&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;При подключении модуля &lt;code&gt;antigravity&lt;/code&gt; в браузере открывается комикс с сайта &lt;code&gt;xkcd.com&lt;/code&gt; (&lt;code&gt;xkcd.ru&lt;/code&gt; — версия на русском). В нём рассказывается про то, насколько язык Python простой. Перевод с английского языка:&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/webt/s3/ha/zh/s3hazhv_kekav5jjmrmqaaczvn4.png&quot; width=&quot;518&quot; /&gt;
    &lt;figcaption&gt;I wrote 20 short programs in Python yesterday. It was wonderful. Perl, I&amp;#x27;m leaving you.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p&gt;Приписка автора комикса: &lt;code&gt;I wrote 20 short programs in Python yesterday. It was wonderful. Perl, I&amp;#x27;m leaving you.&lt;/code&gt;&lt;/p&gt;
  &lt;h3&gt;hello&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: True&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Все же помнят свой первый вывод &lt;code&gt;Hello World!&lt;/code&gt;? Теперь это сделать ещё легче, надо просто написать в консоли &lt;code&gt;import __hello__&lt;/code&gt;.&lt;/p&gt;
  &lt;h3&gt;please-debug-my-code&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;built-in: False&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;И завершает этот список модуль, сделанный мной. В нём живёт маленький помощник, который отдебагает вам любой код.&lt;/p&gt;
  &lt;pre&gt;import please_debug_my_code

print(please_debug_my_code.please_help(&amp;quot;code&amp;quot;, &amp;quot;...&amp;quot;))
&lt;/pre&gt;
  &lt;p&gt;Только он иногда не в настроении, и его надо хорошо попросить (или прочитать исходный код и понять, что он любит).&lt;/p&gt;
  &lt;p&gt;Заключение&lt;/p&gt;
  &lt;p&gt;Библиотека pypi содежит более 307 тысяч проектов, среди которых вы можете найти подходящий пакет в любой жизненной ситуации. А если такого пакета нет, то почему бы не создать его и не поделиться им со всеми?&lt;/p&gt;
  &lt;hr /&gt;

</content></entry><entry><id>python__lounge:wbZEP0Euo-3</id><link rel="alternate" type="text/html" href="https://teletype.in/@python__lounge/wbZEP0Euo-3?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=python__lounge"></link><title>Разукрашиваем вывод в консоли: теория и практика</title><published>2021-05-24T16:07:42.920Z</published><updated>2021-05-24T16:07:42.920Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/27/1e/271e2b9c-5f23-44a7-b0b4-569d286381ad.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://telegra.ph/file/17efa7170dd1aa0b2d19e.png&quot;&gt;Консоль привлекает многих своей минималистичностью и эстетикой, но даже в ней иногда хочется выделить определённый фрагмент, чтобы показать его роль или значимость. Например, отметить зелёным текстом сообщение об успешном выполнении операции или обозначить длинный текст ошибки курсивом. О том, как это делать, а также о реализации на питоне — читайте далее.</summary><content type="html">
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://telegra.ph/file/17efa7170dd1aa0b2d19e.png&quot; width=&quot;1302&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Консоль привлекает многих своей минималистичностью и эстетикой, но даже в ней иногда хочется выделить определённый фрагмент, чтобы показать его роль или значимость. Например, отметить зелёным текстом сообщение об успешном выполнении операции или обозначить длинный текст ошибки курсивом. О том, как это делать, а также о реализации на питоне — читайте далее.&lt;/p&gt;
  &lt;h3&gt;Управляющие последовательности ANSI&lt;/h3&gt;
  &lt;p&gt;&lt;strong&gt;&lt;em&gt;ANSI escape sequences&lt;/em&gt;&lt;/strong&gt; или &lt;em&gt;Управляющие последовательности ANSI&lt;/em&gt; — это стандарт, дающий возможность управлять курсором, цветами, начертание в текстовых консолях. Такие последовательности воспринимаются отрисовщиком терминала, как команды отображать последующий текст в определенном формате. Есть также последовательность, которая сбрасывает предыдущие команды, и отображение текста становиться обычным. Существует несколько форматов управляющих последовательностей, различающихся возможностями и появившимися в разных версиях кодировок. Поговорим об этих форматах подробнее.&lt;/p&gt;
  &lt;h3&gt;8 основных цветов и стили&lt;/h3&gt;
  &lt;p&gt;Для обычного пользователя разнообразия цветов этого формата будет более чем исчерпывающим. Но прежде чем задумываться о том, что терракотовый цвет гораздо круче красного, и он вам уж точно нужен, давайте разберемся, как устроена самая простая версия &lt;em&gt;управляющей последовательности ANSI&lt;/em&gt; для форматирования текста.&lt;/p&gt;
  &lt;p&gt;Чтобы изменить текущий цвет шрифта или фона можно использовать следущий синтаксис:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Начинается управляющая последовательность с любого из этих трёх представлений: &lt;code&gt;\x1b[&lt;/code&gt; (hex) или &lt;code&gt;\u001b[&lt;/code&gt; (Unicode) или &lt;code&gt;\033[&lt;/code&gt; (oct)&lt;/li&gt;
    &lt;li&gt;Далее следуют аргументы, разделённые между собой &lt;code&gt;;&lt;/code&gt;(можно указывать в любом порядке)&lt;/li&gt;
    &lt;li&gt;В конце ставится буква &lt;code&gt;m&lt;/code&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h4&gt;Возможные аргументы&lt;/h4&gt;
  &lt;p&gt;Изменения стиля&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://telegra.ph/file/e648ca455645510e0270f.png&quot; width=&quot;622&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Изменения цвета шрифта&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://telegra.ph/file/f059c1a569ed15603a0b9.png&quot; width=&quot;613&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Изменения цвета фона&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://telegra.ph/file/3bf1e1f78092283f26465.png&quot; width=&quot;626&quot; /&gt;
  &lt;/figure&gt;
  &lt;h4&gt;Бонус: другие интересные модификаторы, которые могут поддерживаться не всеми платформами&lt;/h4&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://telegra.ph/file/3157c8091026c2a4e934a.png&quot; width=&quot;655&quot; /&gt;
  &lt;/figure&gt;
  &lt;h4&gt;Бонус: другие интересные модификаторы, которые могут поддерживаться не всеми платформами&lt;/h4&gt;
  &lt;p&gt;Модификатор Код 38 RGB цвет (см. раздел &amp;quot;Совсем много цветов&amp;quot;) 21 Двойное подчёркивание 51 Обрамлённый 52 Окружённый 53 Надчёркнутый&lt;/p&gt;
  &lt;p&gt;Пример корректного синтаксиса: &lt;code&gt;\033[3;36;44m&lt;/code&gt;. После вывода этой конструкции стиль будет изменён для всего последующего текста. Чтобы вернуться к изначальному состоянию можно использовать &lt;code&gt;\033[0m&lt;/code&gt;, тогда весь текст с этого места вернётся к изначальному форматированию.&lt;/p&gt;
  &lt;p&gt;Давайте поэкспементируем. Для примеров я буду использовать Python.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/835/894/115/8358941159fb2d9f1d29910b8db791c4.png&quot; width=&quot;1050&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Важно заметить, что форматирование повлияло и на консоль питона, а не только на ее вывод. Именно поэтому очень важно закрывать все &amp;quot;тэги&amp;quot; изменения форматирования.&lt;/p&gt;
  &lt;h4&gt;Часто используемые сочетания &lt;em&gt;(copy-paste-able)&lt;/em&gt;&lt;/h4&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://telegra.ph/file/3087a5b5c49fb1c92fdbd.png&quot; width=&quot;659&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3&gt;Больше цветов: аж целых 256&lt;/h3&gt;
  &lt;p&gt;Некоторые терминалы поддерживают вывод целых 256 цветов. Если команда &lt;code&gt;echo $TERM&lt;/code&gt; выводит &lt;code&gt;xterm-256color&lt;/code&gt;, то ваш терминал всё корректно обработает.&lt;/p&gt;
  &lt;p&gt;В этом формате синтаксис немного другой:&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/4b1/291/de3/4b1291de34861f1f7c978fa0f056e3ae.png&quot; width=&quot;817&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Для генерации кодов цветов можно использовать &lt;a href=&quot;http://terminal-color-builder.mudasobwa.ru/&quot; target=&quot;_blank&quot;&gt;генератор&lt;/a&gt;.&lt;/p&gt;
  &lt;p&gt;А палитру доступных цветов можно увидеть на картинке ниже.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Палитра цветов&lt;/strong&gt;&lt;/p&gt;
  &lt;h3&gt;Совсем много цветов&lt;/h3&gt;
  &lt;p&gt;&lt;em&gt;Этот формат не всегда поддерживается стандартными консолями.&lt;/em&gt;&lt;/p&gt;
  &lt;p&gt;Некотрые будут негодовать: &lt;em&gt;&amp;quot;256 цветов и нет моего любимого терракотового, какой ужас!&amp;quot;&lt;/em&gt;. Для таких ценителей существует формат, который уже поддерживает 24 битные цвета (3 канала RGB по 256 градаций).&lt;/p&gt;
  &lt;p&gt;Для не ценителей поясню, что терракотовый кодируется как — (201, 100, 59) или #c9643b.&lt;/p&gt;
  &lt;p&gt;Синтаксис в этом формате выглядит вот так:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;code&gt;\033[38;2;⟨r⟩;⟨g⟩;⟨b⟩m&lt;/code&gt; — цвет текста&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;\033[48;2;⟨r⟩;⟨g⟩;⟨b⟩m&lt;/code&gt; — цвет фона&lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/webt/z0/5h/zo/z05hzos4wahkvhlheh4ew3mrerk.png&quot; width=&quot;1223&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3&gt;Python: Использование библиотеки Colorama&lt;/h3&gt;
  &lt;p&gt;Библиотека Colorama позволяет форматировать текст, не запоминая коды цветов. Рассмотрим её использование на примере:&lt;/p&gt;
  &lt;pre&gt;from colorama import init, Fore, Back, Style

init()

print(Fore.RED + &amp;#x27;some red text\n&amp;#x27; + Back.YELLOW + &amp;#x27;and with a yellow background&amp;#x27;)
print(Style.DIM + &amp;#x27;and in dim text\n&amp;#x27; + Style.RESET_ALL + &amp;#x27;back to normal now&amp;#x27;)
&lt;/pre&gt;
  &lt;p&gt;Вывод программы:&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/470/8e2/51d/4708e251dfeaf72375abc2fe77010012.png&quot; width=&quot;403&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;&lt;code&gt;Style&lt;/code&gt; позволяет изменить стиль, &lt;code&gt;Fore&lt;/code&gt; — цвет шрифта, &lt;code&gt;Back&lt;/code&gt; — цвет фона. Использовать переменные из colorama нужно также, как и коды изменения стиля. Но плюс использования библиотеки в том, что &lt;code&gt;Fore.RED&lt;/code&gt; более читаем, чем &lt;code&gt;\033[0;31m&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Если в &lt;code&gt;colorama.init()&lt;/code&gt; указать параметр &lt;code&gt;autoreset=True&lt;/code&gt;, то стиль будет автоматически сбрасываться (в конец каждого print будут добавлены сбрасывающие стили последовательности), поэтому вам не придётся об этом каждый раз вспоминать.&lt;/p&gt;
  &lt;h3&gt;А что не так с Windows?&lt;/h3&gt;
  &lt;p&gt;Просто так синтаксис, описанный в начале статьи, не работает в командной строке Windows. Поддержка цветов появлялась постепенно, причём в странном варианте. В начале программы надо сделать системный вызов, активирующий отрисовку цветов. А в более старых версиях необходимо заменить все ANSI последовательности на системные вызовы.&lt;/p&gt;
  &lt;p&gt;Но &lt;code&gt;colorama.init()&lt;/code&gt; сделает всё за вас в большинстве версий Windows. Однако если вы используете другую операционную систему, то функцию &lt;code&gt;init()&lt;/code&gt; вызывать в начале программы не обязательно. Также некоторые IDE на Windows (например, PyCharm) тоже поддерживают цвета без каких-либо махинаций.&lt;/p&gt;
  &lt;p&gt;А еще Windows не поддерживает многие модификаторы, такие как жирный текст. Подробнее можно почитать на &lt;a href=&quot;https://pypi.org/project/colorama&quot; target=&quot;_blank&quot;&gt;странице Colorama&lt;/a&gt;&lt;/p&gt;
  &lt;h3&gt;Termcolor&lt;/h3&gt;
  &lt;p&gt;Ещё одна библиотека для вывода цветного текста с более удачным, на мой взлгяд, синтаксисом.&lt;/p&gt;
  &lt;pre&gt;from termcolor import colored, cprint

text = colored(&amp;#x27;Hello, Habr!&amp;#x27;, &amp;#x27;red&amp;#x27;, attrs=[&amp;#x27;blink&amp;#x27;])
print(text)
cprint(&amp;#x27;Hello, Habr!&amp;#x27;, &amp;#x27;green&amp;#x27;, &amp;#x27;on_red&amp;#x27;)
&lt;/pre&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/webt/3v/6g/6t/3v6g6tu5lqqehoayg010kkvxgss.gif&quot; width=&quot;1142&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Кстати, проблему с Windows всё ещё можно починить с помощью &lt;code&gt;colorama.init()&lt;/code&gt;&lt;/p&gt;
  &lt;h3&gt;Выводы&lt;/h3&gt;
  &lt;p&gt;Стандартные 8 цветов позволяют разнообразить вывод в консоль и расставить акценты. 256 цветов намного расширяют возможности, хотя и поддерживаются не всеми консолями. Windows, к сожалению, не поддерживает многие основные модификаторы, например, курсив. Также есть некоторые цвета, которые не прописаны в стандартах, но могут поддерживаться вашей операционной системой. Если вы хотите больше цветов, то вы можете поискать их в Гугле.&lt;/p&gt;
  &lt;p&gt;Пока что не любой терминал поддерживает 24-битные цвета и все модификаторы, но мы вряд ли увидим сильные изменения в этой сфере. Так что пока нам остаётся выбирать самые красивые варианты из тех, что доступны в любимом терминале.&lt;/p&gt;
  &lt;h3&gt;Источники&lt;/h3&gt;
  &lt;ul&gt;
    &lt;li&gt;Картинки с синтаксисом из &lt;a href=&quot;https://stackabuse.com/how-to-print-colored-text-in-python/&quot; target=&quot;_blank&quot;&gt;статьи&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;Генератор из &lt;a href=&quot;https://habr.com/ru/post/161999/&quot; target=&quot;_blank&quot;&gt;статьи на Хабре&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/ANSI_escape_code&quot; target=&quot;_blank&quot;&gt;ANSI escape code&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;

</content></entry><entry><id>python__lounge:4GUAH55iZIc</id><link rel="alternate" type="text/html" href="https://teletype.in/@python__lounge/4GUAH55iZIc?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=python__lounge"></link><title>Распознавание некоторых современных CAPTCHA</title><published>2021-05-22T17:11:07.095Z</published><updated>2021-05-22T17:15:01.929Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/e0/d1/e0d1870f-eda9-4097-86a2-a8bf8597df0e.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://teletype.in/files/4b/32/4b324133-8d0d-484b-a8cc-ee2d5dcd7678.png&quot;&gt;Именно так называлась работа, представленная мной на Балтийском научно-инженерном конкурсе, и принёсшая мне очаровательную бумажку с римской единичкой, а также новенький ноутбук.</summary><content type="html">
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/4b/32/4b324133-8d0d-484b-a8cc-ee2d5dcd7678.png&quot; width=&quot;1302&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Именно так называлась работа, представленная мной на &lt;a href=&quot;http://baltic.contedu.ru/&quot; target=&quot;_blank&quot;&gt;Балтийском научно-инженерном конкурсе,&lt;/a&gt; и принёсшая мне очаровательную бумажку с римской единичкой, а также новенький ноутбук.&lt;/p&gt;
  &lt;p&gt; Работа заключалась в распознавании CAPTCHA, используемых крупными операторами сотовой связи в формах отправки SMS, и демонстрации недостаточной эффективности применяемого ими подхода. Чтобы не задевать ничью гордость, будем называть этих операторов иносказательно: красный, жёлтый, зелёный и синий.&lt;/p&gt;
  &lt;p&gt; Проект получил официальное название &lt;em&gt;Captchure&lt;/em&gt; и неофициальное &lt;em&gt;Breaking Defective Security Measures&lt;/em&gt;. Любые совпадения случайны.&lt;/p&gt;
  &lt;p&gt; Как ни странно, все (ну, почти все) эти CAPTCHA оказались довольно слабенькими. Наименьший результат — 20% — принадлежит жёлтому оператору, наибольший — 86% — синему. Таким образом, я считаю, что задача «демонстрации неэффективности» была успешно решена.&lt;/p&gt;
  &lt;p&gt; Причины выбора именно сотовых операторов тривиальны. Уважаемому Научному Жюри я рассказывал байку о том, что «сотовые операторы имеют достаточно денег, чтобы нанять программиста любой квалификации, и, в то же время, им необходимо минимизировать количество спама; таким образом, их CAPTCHA должны быть довольно мощными, что, как показывает моё исследование, совсем не так». На самом же деле всё было гораздо проще. Я хотел набраться опыта, &lt;s&gt;взломав&lt;/s&gt; распознав какую-нибудь простую CAPTCHA, и выбрал жертвой CAPTCHA красного оператора. А уже после этого, задним числом родилась вышеупомянутая история.&lt;/p&gt;
  &lt;p&gt; Итак, ближе к телу. Никакого мегапродвинутого алгоритма для распознавания всех четырёх видов CAPTCHA у меня нет; вместо него я написал 4 различных алгоритма для каждого вида CAPTCHA по отдельности. Однако, несмотря на то, что алгоритмы в деталях различны, в целом они оказались очень похожими.&lt;/p&gt;
  &lt;p&gt; Как и многие авторы до меня, я разбил задачу распознавания CAPTCHA на 3 подзадачи: предварительную обработку (препроцесс), сегментацию и &lt;a href=&quot;http://lurkmore.ru/Рекурсия&quot; target=&quot;_blank&quot;&gt;распознавание.&lt;/a&gt; На этапе препроцесса из исходного изображения удаляются различные шумы, искажения и пр. В сегментации из исходного изображения выделяются отдельные символы и производится из постобработка (например, обратный поворот). При распознавании символы по одному обрабатываются предварительно обученной нейросетью.&lt;/p&gt;
  &lt;p&gt; Существенно различался только препроцесс. Это связано с тем, что в различных CAPTCHA применяются различные методы искажения изображений, соответственно, и алгоритмы для удаления этих искажений сильно различаются. Сегментация эксплуатировала ключевую идею поиска компонентов связности с незначительными наворотами (значительными их пришлось сделать только у жёлто&lt;s&gt;-полосатых&lt;/s&gt;). Распознавание было абсолютно одинаковым у трёх операторов из четырёх — опять-таки, отличался только жёлтый оператор.&lt;/p&gt;
  &lt;p&gt; Код написан на &lt;a href=&quot;http://www.python.org&quot; target=&quot;_blank&quot;&gt;Python&lt;/a&gt; с применением библиотек &lt;a href=&quot;http://opencv.willowgarage.com/wiki/&quot; target=&quot;_blank&quot;&gt;OpenCV&lt;/a&gt; и &lt;a href=&quot;http://leenissen.dk/fann/wp/&quot; target=&quot;_blank&quot;&gt;FANN,&lt;/a&gt; которые не ставятся без напильника приличных размеров и бубна впридачу. Поэтому мои результаты будет воспроизвести непросто — по крайней мере, до тех пор, пока авторы вышеупомянутых библиотек не сделают нормальные привязки для Python.&lt;/p&gt;
  &lt;h4&gt;RedКак я уже сказал, первым кроликом я выбрал именно эту CAPTCHA. Думаю, несколько примеров прояснят ситуацию:&lt;/h4&gt;
  &lt;p&gt;Вот-вот, и мне тоже сначала показалось, что она очень простая… Однако, это впечатление возникло не на пустом месте. Итак:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Цвета исчерпываются градациями серого, причём символы — светлые, фон — тёмный&lt;/li&gt;
    &lt;li&gt;Отсутствует дополнительный шум&lt;/li&gt;
    &lt;li&gt;Постоянные искажения (то есть не изменяющиеся от картинки к картинке)&lt;/li&gt;
    &lt;li&gt;Символов всегда ровно пять&lt;/li&gt;
    &lt;li&gt;Размеры символов приблизительно одинаковы&lt;/li&gt;
    &lt;li&gt;Символы почти всегда связны&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Казалось, всё это сводит на нет достоинства этой CAPTCHA:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Слипающиеся буквы&lt;/li&gt;
    &lt;li&gt;Мерзкий дырявый шрифт&lt;/li&gt;
    &lt;li&gt;Очень маленький размер (83x23 px)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Естественно, эти «достоинства» являются таковыми только с точки зрения сложности автоматического распознавания. В соответствии с трёхэтапной схемой, упомянутой мной ранее, начнём с предварительной обработки изображения, а именно увеличения в 2 раза.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/bf8/51b/795/bf851b7955e5e23c3f779fb2218ff0fc.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/969/f84/d1e/969f84d1ea1b0b7e1b03b1e4df54b2ad.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/8e7/e99/8e6/8e7e998e6e1e3f4606fc58b16b5d713e.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Как я уже упоминал, искажения здесь постоянны, и, даже несмотря на то, что они не являются линейными, избавиться от них нетрудно. Параметры были подобраны эмпирически.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/52a/cde/261/52acde26162ba8cd080c45c899f86aec.png&quot; width=&quot;486&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/fb3/275/b10/fb3275b1039d1a013f46ce7c30053c43.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/be8/864/094/be8864094e7b7893a68eb12a7b24418b.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/9ef/5b0/563/9ef5b05630a7abcea4a7b29e402934c4.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Далее я применяю пороговое преобразование (в народе Threshold) с t=200 и инвертирую изображение:&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/86a/04c/1c3/86a04c1c34304181c6fcfb798b2a5b89.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/da9/79a/e7d/da979ae7dcd1fd9ac3ff7e915747f5ce.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/367/03f/222/36703f2225ac58fbe303eb222d9e3f66.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Наконец, белым закрашиваются мелкие (меньше 10px) чёрные связные области:&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/809/52a/c2e/80952ac2ea29f438fc9e0b293ee9b475.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/b9c/07f/eb6/b9c07feb6dacdf97d75eb061aa9fc3b1.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/bee/82a/4d0/bee82a4d087c4112727254ab1fb5bb76.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Далее следует сегментация. Как я уже сказал, здесь применяется поиск компонентов связности:&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/0e0/ae1/77d/0e0ae177dbf44b8628875c209ecba2bb.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/140/010/719/14001071925b9a8b147222b58c1f6495.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/e7f/509/6c5/e7f5096c57b767e5d95ec5d15ce01df5.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Иногда (редко, но бывает) буква распадается на несколько частей; для исправления этого досадного недоразумения я применяю довольно простую эвристику, оценивающую принадлежность нескольких компонентов связности к одному символу. Эта оценка зависит только от горизонтального положения и размеров описывающих прямоугольников (bounding boxes) каждого символа.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/0e0/ae1/77d/0e0ae177dbf44b8628875c209ecba2bb.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/ab8/58a/4da/ab858a4da385945f8e97367e96257907.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/e7f/509/6c5/e7f5096c57b767e5d95ec5d15ce01df5.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Нетрудно заметить, что многие символы оказались объединены в один компонент связности, в связи с чем надо их разделять. Здесь на помощь приходит тот факт, что на изображении всегда ровно 5 символов. Это позволяет с большой точностью вычислять, сколько символов находится в каждом найденном компоненте.&lt;/p&gt;
  &lt;p&gt; Для объяснения принципа работы такого алгоритма придётся немного углубиться в матчасть. Обозначим количество найденных сегментов за n, а массив ширин (&lt;a href=&quot;http://www.youtube.com/watch?v=2OuVmTP2aoE#t=0m51s&quot; target=&quot;_blank&quot;&gt;правильно сказал, да?&lt;/a&gt;) всех сегментов за widths[n]. Будем считать, что если после вышеупомянутых этапов n &amp;gt; 5, изображение распознать не удалось. Рассмотрим все возможные разложения числа 5 на целые положительные слагаемые. Их немного — всего 16. Каждое такое разложение соответствует некоторой возможной расстановке символов по найденным компонентам связности. Логично предположить, что чем шире получившийся сегмент, тем больше символов он содержит. Из всех разложений пятёрки выберем только те, в которых количество слагаемых равно n. Поделим каждый элемент из widths на widths[0] — как бы нормализуем их. То же самое проделаем со всеми оставшимися разложениями — поделим каждое число в них на первое слагаемое. А теперь (внимание, кульминация!) заметим, что получившиеся упорядоченные n-ки можно мыслить как точки в n-мерном пространстве. С учётом этого, найдём ближайшее по Евклиду разложение пятёрки к нормализованному widths. Это и есть искомый результат.&lt;/p&gt;
  &lt;p&gt; Кстати, в связи с этим алгоритмом мне в голову пришёл ещё один интересный способ искать все разложения числа на слагаемые, который я, правда, так и не реализовал, закопавшись в питоновских структурах данных. Вкратце — он довольно очевидно вылезает, если заметить, что количество разложений определённой длины совпадает с соответствующим уровнем треугольника Паскаля. Впрочем, я уверен, что этот алгоритм давным-давно известен.&lt;/p&gt;
  &lt;p&gt; Так вот, после определения количества символов в каждом компоненте наступает следующая эвристика — мы считаем, что разделители между символами тоньше, чем сами символы. Для того чтобы воспользоваться этим сокровенным знанием, расставим по сегменту n-1 разделителей, где n — количество символов в сегменте, после чего в небольшой окрестности каждого разделителя посчитаем проекцию изображения вниз. В результате этого проецирования мы получим информацию о том, сколько в каждом столбце пикселей принадлежат символам. Наконец, в каждой проекции найдём минимум и сдвинем разделитель туда, после чего покромсаем изображение по этим разделителям.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/1a2/053/173/1a205317387b50eff32c56e9d5460bb7.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/2ac/401/a23/2ac401a23e5ee2e19dda314b8954e22b.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/645/67f/dec/64567fdec7a500e9c87cf61a3cefa8d7.png&quot; width=&quot;166&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/c8d/e69/b49/c8de69b49e7f3ea0ffde1c008baa9eea.png&quot; width=&quot;267&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Наконец, распознавание. Как я уже говорил, для него я применяю нейросеть. Для её обучения сначала я прогоняю две сотни изображений под общим заголовком &lt;em&gt;trainset&lt;/em&gt; через уже написанные и отлаженные первые два этапа, в результате чего получаю папку с большим количеством аккуратно нарезанных сегментов. Затем руками вычищаю мусор (результаты неправильной сегментации, например), после чего результат привожу к одному размеру и отдаю на растерзание FANN. На выходе получаю обученную нейросеть, которая и используется для распознавания. Эта схема дала сбой только один раз — но об этом позже.&lt;/p&gt;

</content></entry><entry><id>python__lounge:HCZds-h7lVw</id><link rel="alternate" type="text/html" href="https://teletype.in/@python__lounge/HCZds-h7lVw?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=python__lounge"></link><title>Новые фичи в Python 3.10</title><published>2021-05-14T19:23:15.963Z</published><updated>2021-05-16T11:56:02.686Z</updated><summary type="html">&lt;img src=&quot;https://teletype.in/files/48/7c/487cdb0f-2d92-4285-aa0b-ff3a3184355e.png&quot;&gt;Если вам хочется попробовать все фичи великолепной последний версии Python, нужно установить альфа или бета-версию. Однако учитывая, что эти версии не стабильны, мы не хотим перезаписывать дефолтную установку языка. Будем устанавливать альфу Python 3.10 рядом с текущим интерпретатором. И в преддверии старта нового потока курса Fullstack-разработчик на Python — обозревать все новшества новой версии языка.</summary><content type="html">
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/48/7c/487cdb0f-2d92-4285-aa0b-ff3a3184355e.png&quot; width=&quot;1302&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Если вам хочется попробовать все фичи великолепной последний версии Python, нужно установить альфа или бета-версию. Однако учитывая, что эти версии не стабильны, мы не хотим перезаписывать дефолтную установку языка. Будем устанавливать альфу Python 3.10 рядом с текущим интерпретатором. И в преддверии старта нового потока курса &lt;a href=&quot;https://skillfactory.ru/python-fullstack-web-developer?utm_source=infopartners&amp;utm_medium=habr&amp;utm_campaign=habr_FPW&amp;utm_term=regular&amp;utm_content=040421&quot; target=&quot;_blank&quot;&gt;Fullstack-разработчик на Python&lt;/a&gt; — обозревать все новшества новой версии языка.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p&gt;Сделать это можно выполнив эти команды:&lt;/p&gt;
  &lt;pre&gt;wget https://www.python.org/ftp/python/3.10.0/Python-3.10.0a6.tgz
tar xzvf Python-3.10.0a6.tgz
cd Python-3.10.0a6
./configure --prefix=$HOME/python-3.10.0a6
make
make install
$HOME/python-3.10.0a6/bin/python3.10&lt;/pre&gt;
  &lt;p&gt;После запуска кода выше вы увидите приветствие от среды разработки IDLE:&lt;/p&gt;
  &lt;pre&gt;Python 3.10.0a6 (default, Mar 27 2021, 11:50:33) [GCC 9.3.0] on linux
Type &amp;quot;help&amp;quot;, &amp;quot;copyright&amp;quot;, &amp;quot;credits&amp;quot; or &amp;quot;license&amp;quot; for more information.
&amp;gt;&amp;gt;&amp;gt;&lt;/pre&gt;
  &lt;p&gt;А теперь, с установленным Python, мы можем посмотреть на все новые фичи и изменения.&lt;/p&gt;
  &lt;h2&gt;Улучшения в проверке типов&lt;/h2&gt;
  &lt;p&gt;Если вы пользуетесь проверкой типов, то будете счастливы услышать, что Python 3.10 включает много улучшений в проверке типов, среди них &lt;em&gt;оператор объединения типов&lt;/em&gt;, синтаксис которого теперь чище.&lt;/p&gt;
  &lt;pre&gt;# Function that accepts either &amp;#x60;int&amp;#x60; or &amp;#x60;float&amp;#x60;
# Old:
def func(value: Union[int, float]) -&amp;gt; Union[int, float]:
    return value

# New:
def func(value: int | float) -&amp;gt; int | float:
    return value&lt;/pre&gt;
  &lt;p&gt;Кроме того, это простое улучшение не ограничивается только аннотациями типа, оно может применяться с функциями isinstance() и issubclass():&lt;/p&gt;
  &lt;pre&gt;isinstance(&amp;quot;hello&amp;quot;, int | str)
# True&lt;/pre&gt;
  &lt;h2&gt;Изменения синтаксиса псевдонима типа&lt;/h2&gt;
  &lt;p&gt;В более &lt;a href=&quot;https://www.python.org/dev/peps/pep-0484&quot; target=&quot;_blank&quot;&gt;&lt;u&gt;ранних версиях Python&lt;/u&gt;&lt;/a&gt; добавлены псевдонимы типов, позволяющие создавать синонимы пользовательских классов. В Python 3.9 и более ранних версиях псевдонимы записывались так:&lt;/p&gt;
  &lt;pre&gt;FileName = str

def parse(file: FileName) -&amp;gt; None:
    ...&lt;/pre&gt;
  &lt;p&gt;Здесь FileName - псевдоним базового типа строки Python. Однако, начиная с Python 3.10, синтаксис определения псевдонимов типов будет изменён:&lt;/p&gt;
  &lt;pre&gt;FileName: TypeAlias = str

def parse(file: FileName) -&amp;gt; None:
    ...&lt;/pre&gt;
  &lt;p&gt;Благодаря этому простому изменению и программистам, и инструментам проверки типа проще отличить присваивание переменной от псевдонима. Новый синтаксис обратно совместим, так что вам не нужно обновлять старый код с псевдонимами.&lt;/p&gt;
  &lt;p&gt;Кроме этих 2 изменений появилось другое улучшение модуля typing - в предложениях по улучшению номер 612 оно называется Parameter Specification Variables. Однако это не то, что вы найдете в основной массе кода на Python, поскольку эта функциональность используется для пересылки параметра типов от одного вызываемого типа к другому вызываемому типу, например, в декораторах. Если вам есть где применить эту функциональность, &lt;a href=&quot;https://www.python.org/dev/peps/pep-0612&quot; target=&quot;_blank&quot;&gt;посмотрите её PEP&lt;/a&gt;.&lt;/p&gt;
  &lt;h2&gt;bit_count()&lt;/h2&gt;
  &lt;p&gt;Начиная с Python 3.10, чтобы посчитать количество битов в двоичном представлении целого числа, можно вызвать int.bit_count(). Функция известна как &lt;em&gt;Population Count (popcount)&lt;/em&gt;:&lt;/p&gt;
  &lt;pre&gt;value = 42
print(bin(value))
# &amp;#x27;0b101010&amp;#x27;
print(value.bit_count())
# 3&lt;/pre&gt;
  &lt;p&gt;Это, безусловно, хорошо, но давайте будем реалистами: реализация этой функции не так уж сложна, на самом деле это всего лишь одна строка:&lt;/p&gt;
  &lt;pre&gt;def bit_count(value):
    return bin(value).count(&amp;quot;1&amp;quot;)&lt;/pre&gt;
  &lt;p&gt;При этом popcount() — ещё одна удобная функция, она может пригодиться в какой-то момент; всевозможные полезные маленькие функции — одна из причин, по которой Python так популярен: на первый взгляд всё доступно из коробки.&lt;/p&gt;
  &lt;h2&gt;Модуль distutils устарел&lt;/h2&gt;
  &lt;p&gt;В новой версии функции не только добавляются, но также удаляются или объявляются устаревшими. Это касается пакета distutils, который объявлен устаревшим в 3.10 и будет удален в 3.12. На какое-то время его заменили пакетами setuptools и packaging, поэтому если вы работаете с каким-то из этих пакетов, у вас все будет хорошо. При этом вы, вероятно, должны проверить, не используется ли distutils в вашем коде, и начать готовиться к тому, чтобы в ближайшее время избавиться от этого модуля.&lt;/p&gt;
  &lt;h2&gt;Синтаксис менеджера контекста&lt;/h2&gt;
  &lt;p&gt;Контекстные менеджеры отлично подходят, чтобы открывать и закрывать файлы, работать с соединениями баз данных и делать многое другое, а в Python 3.10 они станут немного удобнее. Изменение позволяет в скобках указывать несколько контекстных менеджеров, что удобно, если вы хотите создать в одном операторе with несколько менеджеров:&lt;/p&gt;
  &lt;pre&gt;with (
    open(&amp;quot;somefile.txt&amp;quot;) as some_file,
    open(&amp;quot;otherfile.txt&amp;quot;) as other_file,
):
    ...

from contextlib import redirect_stdout

with (open(&amp;quot;somefile.txt&amp;quot;, &amp;quot;w&amp;quot;) as some_file,
      redirect_stdout(some_file)):
    ...&lt;/pre&gt;
  &lt;p&gt;В коде выше видно, что мы даже можем ссылаться на переменную, созданную одним контекстным менеджером (... as some_file) в следующем за ним менеджере!&lt;/p&gt;
  &lt;p&gt;Это всего лишь два из многих новых форматов в Python 3.10. Улучшенный синтаксис довольно гибок, поэтому я не буду утруждать себя и показать все возможные варианты; я почти уверен, что новый Python обработает всё, что вы ему скормите.&lt;/p&gt;
  &lt;h2&gt;Улучшения в производительности&lt;/h2&gt;
  &lt;p&gt;Как и во всех последних релизах Python, с Python 3.10 придут улучшения производительности. Первое — оптимизация конструкторов str(), bytes() и bytearray(), которые должны стать примерно на 30% быстрее (фрагмент, адаптированный из &lt;a href=&quot;https://bugs.python.org/issue41334&quot; target=&quot;_blank&quot;&gt;&lt;u&gt;примера&lt;/u&gt;&lt;/a&gt; в баг-трекере Python):&lt;/p&gt;
  &lt;pre&gt;~ $ ./python3.10 -m pyperf timeit -q --compare-to=python &amp;quot;str()&amp;quot;
Mean +- std dev: [python] 81.9 ns +- 4.5 ns -&amp;gt; [python3.10] 60.0 ns +- 1.9 ns: 1.36x faster (-27%)
~ $ ./python3.10 -m pyperf timeit -q --compare-to=python &amp;quot;bytes()&amp;quot;
Mean +- std dev: [python] 85.1 ns +- 2.2 ns -&amp;gt; [python3.10] 60.2 ns +- 2.3 ns: 1.41x faster (-29%)
~ $ ./python3.10 -m pyperf timeit -q --compare-to=python &amp;quot;bytearray()&amp;quot;
Mean +- std dev: [python] 93.5 ns +- 2.1 ns -&amp;gt; [python3.10] 73.1 ns +- 1.8 ns: 1.28x faster (-22%)&lt;/pre&gt;
  &lt;p&gt;Другой более заметной оптимизацией (если вы используете аннотации типов) является то, что параметры функции и их аннотации вычисляются уже не во время исполнения, а во время компиляции. Теперь функция с аннотациями параметров создаётся примерно в два раза быстрее.&lt;/p&gt;
  &lt;p&gt;Кроме того, есть еще несколько оптимизаций в разных частях ядра языка. Подробности о них вы можете найти в этих записях баг-трекера Python: &lt;a href=&quot;https://bugs.python.org/issue41718&quot; target=&quot;_blank&quot;&gt;&lt;u&gt;bpo-41718&lt;/u&gt;&lt;/a&gt;, &lt;a href=&quot;https://bugs.python.org/issue42927&quot; target=&quot;_blank&quot;&gt;&lt;u&gt;bpo-42927&lt;/u&gt;&lt;/a&gt; и &lt;a href=&quot;https://bugs.python.org/issue43452&quot; target=&quot;_blank&quot;&gt;&lt;u&gt;bpo-43452&lt;/u&gt;&lt;/a&gt;.&lt;/p&gt;
  &lt;h2&gt;Сопоставление шаблонов&lt;/h2&gt;
  &lt;p&gt;Одна масштабная фича, о которой вы, конечно, слышали, — это структурное сопоставление шаблонов, добавляющее оператор известное выражение case из других языков. Мы знаем, как работать с case, но посмотрите на вариацию в Python это не просто switch/case, но также несколько мощных особенностей, которые мы должны исследовать.&lt;/p&gt;
  &lt;p&gt;Простое сопоставление шаблонов состоит из ключевого слова match, за которым следует выражение, а его результат проверяется на соответствие шаблонам, указанным в последовательных операторах case:&lt;/p&gt;
  &lt;pre&gt;def func(day):
    match day:
        case &amp;quot;Monday&amp;quot;:
            return &amp;quot;Here we go again...&amp;quot;
        case &amp;quot;Friday&amp;quot;:
            return &amp;quot;Happy Friday!&amp;quot;
        case &amp;quot;Saturday&amp;quot; | &amp;quot;Sunday&amp;quot;:  # Multiple literals can be combined with &amp;#x60;|&amp;#x60;
            return &amp;quot;Yay, weekend!&amp;quot;
        case _:
            return &amp;quot;Just another day...&amp;quot;&lt;/pre&gt;
  &lt;p&gt;В этом простом примере мы воспользовались переменной day как выражением, которое затем сравнивается с конкретными строками в case. Кроме строк, вы также можете заметить case с маской _ — это эквивалент ключевого слова default в других языках. Хотя этот оператор можно опустить, в этом случае может произойти no-op, по существу это означает, что вернётся None.&lt;/p&gt;
  &lt;p&gt;Еще один момент, на который стоит обратить внимание в коде выше, это оператор |, позволяющий комбинировать несколько литералов | (другой его вариант — &lt;em&gt;or&lt;/em&gt;).&lt;/p&gt;
  &lt;p&gt;Как я уже упоминал, новое сопоставление шаблонов не заканчивается на базовом синтаксисе, напротив — оно привносит дополнительные возможности, например сопоставление сложных шаблонов:&lt;/p&gt;
  &lt;pre&gt;def func(person):  # person = (name, age, gender)
    match person:
        case (name, _, &amp;quot;male&amp;quot;):
            print(f&amp;quot;{name} is man.&amp;quot;)
        case (name, _, &amp;quot;female&amp;quot;):
            print(f&amp;quot;{name} is woman.&amp;quot;)
        case (name, age, gender):
            print(f&amp;quot;{name} is {age} old.&amp;quot;)
        
func((&amp;quot;John&amp;quot;, 25, &amp;quot;male&amp;quot;))
# John is man.&lt;/pre&gt;
  &lt;p&gt;Во фрагменте выше мы воспользовались кортежем как выражением сопоставления. Однако мы не ограничены кортежами: работать будет любой итерируемый тип. Также выше видно, что маска (wildcard) _ может применяться внутри сложных шаблонов и не только сама по себе, как в предыдущих примерах. Простые кортежи или списки — не всегда лучший подход, поэтому, если вы предпочитаете классы, код можно переписать так:&lt;/p&gt;
  &lt;pre&gt;from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int
    gender: str
    
def func(person):  # person is instance of &amp;#x60;Person&amp;#x60; class
    match person:
        # This is not a constructor
        case Person(name, age, gender) if age &amp;lt; 18:  # guard for extra filtering
            print(f&amp;quot;{name} is a child.&amp;quot;)
        case Person(name=name, age=_, gender=&amp;quot;male&amp;quot;):  # Wildcard (&amp;quot;throwaway&amp;quot; variable) can be used
            print(f&amp;quot;{name} is man.&amp;quot;)
        case Person(name=name, age=_, gender=&amp;quot;female&amp;quot;):
            print(f&amp;quot;{name} is woman.&amp;quot;)
        case Person(name, age, gender):  # Positional arguments work
            print(f&amp;quot;{name} is {age} years old.&amp;quot;)

func(Person(&amp;quot;Lucy&amp;quot;, 30, &amp;quot;female&amp;quot;))
# Lucy is woman.
func(Person(&amp;quot;Ben&amp;quot;, 15, &amp;quot;male&amp;quot;))
# Ben is a child.&lt;/pre&gt;
  &lt;p&gt;Здесь видно, что с шаблонами, написанными в стиле конструкторов, можно сопоставить атрибуты класса. При использовании этого подхода отдельные атрибуты также попадают в переменные (как и в показанные ранее кортежи), с которыми затем можно работать в соответствующем операторе case.&lt;/p&gt;
  &lt;p&gt;Выше мы можем увидеть другие особенности сопоставления шаблонов: во-первых выражение в case — это гард, который также является условием в if. Это полезно, когда сопоставления по значению не достаточно и вам нужны дополнительные проверки. Посмотрите на оставшееся выражение case: видно, что и ключевые слова, (name-name) и позиционные аргументы работают с синтаксисом, похожим на синтаксис конструкторов; то же самое верно для маски _ (или отбрасываемой переменной).&lt;/p&gt;
  &lt;p&gt;Сопоставление шаблонов также позволяет работать с вложенными шаблонами. Вложенные шаблоны могут использовать любой итерируемый тип: и конструируемый объект, и несколько таких объектов, которые возможно итерировать:&lt;/p&gt;
  &lt;pre&gt;match users:
    case [Person(...)]:
        print(&amp;quot;One user provided...&amp;quot;)
    case [Person(...), Person(...) as second]:  # &amp;#x60;as var&amp;#x60; can be used to capture subpattern
        print(f&amp;quot;There&amp;#x27;s another user: {second}&amp;quot;)
    case [Person(...), Person(...), *rest]:  # &amp;#x60;*var&amp;#x60; can be used as unpacking
        print(...)&lt;/pre&gt;
  &lt;p&gt;В таких сложных шаблонах для дальнейшей обработки может быть полезно записать подшаблон в переменную. Это можно сделать с помощью ключевого слова as, как показано выше, во втором case.&lt;/p&gt;
  &lt;p&gt;Наконец, оператор * может использоваться для &lt;em&gt;&amp;quot;распаковки&amp;quot;&lt;/em&gt; переменных в шаблоне, это работает и с маской _ в шаблоне *_. Если вы хотите увидеть больше примеров и законченный туториал, пройдите по ссылке на &lt;a href=&quot;https://www.python.org/dev/peps/pep-0636/&quot; target=&quot;_blank&quot;&gt;&lt;u&gt;PEP 636&lt;/u&gt;&lt;/a&gt;.&lt;/p&gt;

</content></entry><entry><id>python__lounge:OA-e5higT</id><link rel="alternate" type="text/html" href="https://teletype.in/@python__lounge/OA-e5higT?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=python__lounge"></link><title>Игра Тетрис на PyQt5</title><published>2021-04-20T19:59:51.180Z</published><updated>2021-04-21T19:48:08.752Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/84/4a/844a7c56-5cf1-4e9a-8ce8-b1d8e953357e.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://pythonworld.ru/uploads/gui/pyqt5/tetrominoes.png&quot;&gt;@python_lounge</summary><content type="html">
  &lt;p&gt;@python_lounge&lt;/p&gt;
  &lt;p&gt;Игра Тетрис – одна из самых популярных компьютерных игр. Оригинальная игра была разработана и запрограммирована русским программистом Алексеем Пажитновым в 1985 году. С тех пор Тетрис доступен на почти каждой компьютерной платформе в множестве вариаций.&lt;/p&gt;
  &lt;p&gt;Создание простой компьютерной игры на PyQt5 – отличный способ повышения навыков программирования.&lt;/p&gt;
  &lt;p&gt;Тетрисом называется игра-головоломка с падающими блоками. В этой игре, мы имеем 7 разных фигур, называемых так: S-фигура, Z-фигура, T-фигура, L-фигура, фигура-линия, фигура &amp;quot;Г&amp;quot;, и квадрат. Каждая из этих фигур формируется с помощью четырёх квадратиков. Фигуры падают вниз на доску. Цель игры Тетрис – перемещать и вращать фигуры так, чтобы их приземлилось как можно больше. Если мы сумеем сформировать ряд, ряд разрушается и мы получаем очки. Мы играем в Тетрис до тех пор, пока не достигнем верха.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://pythonworld.ru/uploads/gui/pyqt5/tetrominoes.png&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2&gt;Разработка&lt;/h2&gt;
  &lt;p&gt;У нас нет изображений для нашего Тетриса, мы рисуем тетрамино, используя доступное в программном инструментарии PyQt5 инструменты &lt;a href=&quot;https://pythonworld.ru/gui/pyqt5-painting.html&quot; target=&quot;_blank&quot;&gt;рисования&lt;/a&gt;. У каждой компьютерной игры имеется математическая модель. Также и в Тетрисе.&lt;/p&gt;
  &lt;p&gt;Некоторые идеи, применяющиеся в игре:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Мы используем &lt;em&gt;QtCore.QBasicTimer&lt;/em&gt;(), чтобы создать игровой цикл.&lt;/li&gt;
    &lt;li&gt;Тетрамино рисуются.&lt;/li&gt;
    &lt;li&gt;Фигуры перемещаются по принципу &amp;quot;кубик за кубиком&amp;quot; (не &amp;quot;пиксель за пикселем&amp;quot;).&lt;/li&gt;
    &lt;li&gt;Математически, доска – это просто список чисел.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Код содержит четыре класса: Tetris, Board, Tetrominoe и Shape. Класс Tetris организовывает игру. Board – это то, где пишется игровая логика. Класс Tetrominoe содержит имена всех частей тетриса и класс Shape содержит код для частей тетриса.&lt;/p&gt;
  &lt;p&gt;Код игры Тетрис:&lt;/p&gt;
  &lt;pre&gt;#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys, random
from PyQt5.QtWidgets import QMainWindow, QFrame, QDesktopWidget, QApplication
from PyQt5.QtCore import Qt, QBasicTimer, pyqtSignal
from PyQt5.QtGui import QPainter, QColor


class Tetris(QMainWindow):

    def __init__(self):
        super().__init__()

        self.initUI()


    def initUI(self):

        self.tboard = Board(self)
        self.setCentralWidget(self.tboard)

        self.statusbar = self.statusBar()
        self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)

        self.tboard.start()

        self.resize(180, 380)
        self.center()
        self.setWindowTitle(&amp;#x27;Tetris&amp;#x27;)
        self.show()


    def center(self):

        screen = QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width()-size.width())/2,
            (screen.height()-size.height())/2)


class Board(QFrame):

    msg2Statusbar = pyqtSignal(str)

    BoardWidth = 10
    BoardHeight = 22
    Speed = 300

    def __init__(self, parent):
        super().__init__(parent)

        self.initBoard()


    def initBoard(self):

        self.timer = QBasicTimer()
        self.isWaitingAfterLine = False

        self.curX = 0
        self.curY = 0
        self.numLinesRemoved = 0
        self.board = []

        self.setFocusPolicy(Qt.StrongFocus)
        self.isStarted = False
        self.isPaused = False
        self.clearBoard()


    def shapeAt(self, x, y):
        return self.board[(y * Board.BoardWidth) + x]


    def setShapeAt(self, x, y, shape):
        self.board[(y * Board.BoardWidth) + x] = shape


    def squareWidth(self):
        return self.contentsRect().width() // Board.BoardWidth


    def squareHeight(self):
        return self.contentsRect().height() // Board.BoardHeight


    def start(self):

        if self.isPaused:
            return

        self.isStarted = True
        self.isWaitingAfterLine = False
        self.numLinesRemoved = 0
        self.clearBoard()

        self.msg2Statusbar.emit(str(self.numLinesRemoved))

        self.newPiece()
        self.timer.start(Board.Speed, self)


    def pause(self):

        if not self.isStarted:
            return

        self.isPaused = not self.isPaused

        if self.isPaused:
            self.timer.stop()
            self.msg2Statusbar.emit(&amp;quot;paused&amp;quot;)

        else:
            self.timer.start(Board.Speed, self)
            self.msg2Statusbar.emit(str(self.numLinesRemoved))

        self.update()


    def paintEvent(self, event):

        painter = QPainter(self)
        rect = self.contentsRect()

        boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight()

        for i in range(Board.BoardHeight):
            for j in range(Board.BoardWidth):
                shape = self.shapeAt(j, Board.BoardHeight - i - 1)

                if shape != Tetrominoe.NoShape:
                    self.drawSquare(painter,
                        rect.left() + j * self.squareWidth(),
                        boardTop + i * self.squareHeight(), shape)

        if self.curPiece.shape() != Tetrominoe.NoShape:

            for i in range(4):

                x = self.curX + self.curPiece.x(i)
                y = self.curY - self.curPiece.y(i)
                self.drawSquare(painter, rect.left() + x * self.squareWidth(),
                    boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
                    self.curPiece.shape())


    def keyPressEvent(self, event):

        if not self.isStarted or self.curPiece.shape() == Tetrominoe.NoShape:
            super(Board, self).keyPressEvent(event)
            return

        key = event.key()

        if key == Qt.Key_P:
            self.pause()
            return

        if self.isPaused:
            return

        elif key == Qt.Key_Left:
            self.tryMove(self.curPiece, self.curX - 1, self.curY)

        elif key == Qt.Key_Right:
            self.tryMove(self.curPiece, self.curX + 1, self.curY)

        elif key == Qt.Key_Down:
            self.tryMove(self.curPiece.rotateRight(), self.curX, self.curY)

        elif key == Qt.Key_Up:
            self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)

        elif key == Qt.Key_Space:
            self.dropDown()

        elif key == Qt.Key_D:
            self.oneLineDown()

        else:
            super(Board, self).keyPressEvent(event)


    def timerEvent(self, event):

        if event.timerId() == self.timer.timerId():

            if self.isWaitingAfterLine:
                self.isWaitingAfterLine = False
                self.newPiece()
            else:
                self.oneLineDown()

        else:
            super(Board, self).timerEvent(event)


    def clearBoard(self):

        for i in range(Board.BoardHeight * Board.BoardWidth):
            self.board.append(Tetrominoe.NoShape)


    def dropDown(self):

        newY = self.curY

        while newY &amp;gt; 0:

            if not self.tryMove(self.curPiece, self.curX, newY - 1):
                break

            newY -= 1

        self.pieceDropped()


    def oneLineDown(self):

        if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
            self.pieceDropped()


    def pieceDropped(self):

        for i in range(4):

            x = self.curX + self.curPiece.x(i)
            y = self.curY - self.curPiece.y(i)
            self.setShapeAt(x, y, self.curPiece.shape())

        self.removeFullLines()

        if not self.isWaitingAfterLine:
            self.newPiece()


    def removeFullLines(self):

        numFullLines = 0
        rowsToRemove = []

        for i in range(Board.BoardHeight):

            n = 0
            for j in range(Board.BoardWidth):
                if not self.shapeAt(j, i) == Tetrominoe.NoShape:
                    n = n + 1

            if n == 10:
                rowsToRemove.append(i)

        rowsToRemove.reverse()


        for m in rowsToRemove:

            for k in range(m, Board.BoardHeight):
                for l in range(Board.BoardWidth):
                        self.setShapeAt(l, k, self.shapeAt(l, k + 1))

        numFullLines = numFullLines + len(rowsToRemove)

        if numFullLines &amp;gt; 0:

            self.numLinesRemoved = self.numLinesRemoved + numFullLines
            self.msg2Statusbar.emit(str(self.numLinesRemoved))

            self.isWaitingAfterLine = True
            self.curPiece.setShape(Tetrominoe.NoShape)
            self.update()


    def newPiece(self):

        self.curPiece = Shape()
        self.curPiece.setRandomShape()
        self.curX = Board.BoardWidth // 2 + 1
        self.curY = Board.BoardHeight - 1 + self.curPiece.minY()

        if not self.tryMove(self.curPiece, self.curX, self.curY):

            self.curPiece.setShape(Tetrominoe.NoShape)
            self.timer.stop()
            self.isStarted = False
            self.msg2Statusbar.emit(&amp;quot;Game over&amp;quot;)



    def tryMove(self, newPiece, newX, newY):

        for i in range(4):

            x = newX + newPiece.x(i)
            y = newY - newPiece.y(i)

            if x &amp;lt; 0 or x &amp;gt;= Board.BoardWidth or y &amp;lt; 0 or y &amp;gt;= Board.BoardHeight:
                return False

            if self.shapeAt(x, y) != Tetrominoe.NoShape:
                return False

        self.curPiece = newPiece
        self.curX = newX
        self.curY = newY
        self.update()

        return True


    def drawSquare(self, painter, x, y, shape):

        colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
                      0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]

        color = QColor(colorTable[shape])
        painter.fillRect(x + 1, y + 1, self.squareWidth() - 2,
            self.squareHeight() - 2, color)

        painter.setPen(color.lighter())
        painter.drawLine(x, y + self.squareHeight() - 1, x, y)
        painter.drawLine(x, y, x + self.squareWidth() - 1, y)

        painter.setPen(color.darker())
        painter.drawLine(x + 1, y + self.squareHeight() - 1,
            x + self.squareWidth() - 1, y + self.squareHeight() - 1)
        painter.drawLine(x + self.squareWidth() - 1,
            y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)


class Tetrominoe(object):

    NoShape = 0
    ZShape = 1
    SShape = 2
    LineShape = 3
    TShape = 4
    SquareShape = 5
    LShape = 6
    MirroredLShape = 7


class Shape(object):

    coordsTable = (
        ((0, 0),     (0, 0),     (0, 0),     (0, 0)),
        ((0, -1),    (0, 0),     (-1, 0),    (-1, 1)),
        ((0, -1),    (0, 0),     (1, 0),     (1, 1)),
        ((0, -1),    (0, 0),     (0, 1),     (0, 2)),
        ((-1, 0),    (0, 0),     (1, 0),     (0, 1)),
        ((0, 0),     (1, 0),     (0, 1),     (1, 1)),
        ((-1, -1),   (0, -1),    (0, 0),     (0, 1)),
        ((1, -1),    (0, -1),    (0, 0),     (0, 1))
    )

    def __init__(self):

        self.coords = [[0,0] for i in range(4)]
        self.pieceShape = Tetrominoe.NoShape

        self.setShape(Tetrominoe.NoShape)


    def shape(self):
        return self.pieceShape


    def setShape(self, shape):

        table = Shape.coordsTable[shape]

        for i in range(4):
            for j in range(2):
                self.coords[i][j] = table[i][j]

        self.pieceShape = shape


    def setRandomShape(self):
        self.setShape(random.randint(1, 7))


    def x(self, index):
        return self.coords[index][0]


    def y(self, index):
        return self.coords[index][1]


    def setX(self, index, x):
        self.coords[index][0] = x


    def setY(self, index, y):
        self.coords[index][1] = y


    def minX(self):

        m = self.coords[0][0]
        for i in range(4):
            m = min(m, self.coords[i][0])

        return m


    def maxX(self):

        m = self.coords[0][0]
        for i in range(4):
            m = max(m, self.coords[i][0])

        return m


    def minY(self):

        m = self.coords[0][1]
        for i in range(4):
            m = min(m, self.coords[i][1])

        return m


    def maxY(self):

        m = self.coords[0][1]
        for i in range(4):
            m = max(m, self.coords[i][1])

        return m


    def rotateLeft(self):

        if self.pieceShape == Tetrominoe.SquareShape:
            return self

        result = Shape()
        result.pieceShape = self.pieceShape

        for i in range(4):

            result.setX(i, self.y(i))
            result.setY(i, -self.x(i))

        return result


    def rotateRight(self):

        if self.pieceShape == Tetrominoe.SquareShape:
            return self

        result = Shape()
        result.pieceShape = self.pieceShape

        for i in range(4):

            result.setX(i, -self.y(i))
            result.setY(i, self.x(i))

        return result


if __name__ == &amp;#x27;__main__&amp;#x27;:

    app = QApplication([])
    tetris = Tetris()
    sys.exit(app.exec_())&lt;/pre&gt;
  &lt;p&gt;Игра немного упрощена для более легкого понимания. Игра начинается сразу же после её запуска. Мы можем приостановить игру, нажав клавишу p. Клавиша Space будет немедленно бросать блок тетриса вниз. Игра идёт на постоянной скорости, ускорение не реализуется. Очки – это число линий, который мы удалили.&lt;/p&gt;
  &lt;pre&gt;self.tboard = Board(self)
self.setCentralWidget(self.tboard)&lt;/pre&gt;
  &lt;p&gt;Экземпляр класса Board создаётся и устанавливается центральным виджетом приложения.&lt;/p&gt;
  &lt;pre&gt;self.statusbar = self.statusBar()
self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)&lt;/pre&gt;
  &lt;p&gt;Мы создаём &lt;a href=&quot;https://pythonworld.ru/gui/pyqt5-menustoolbars.html&quot; target=&quot;_blank&quot;&gt;строку состояния&lt;/a&gt;, где мы будем отображать сообщения. Мы будем отображать три возможных сообщения: количество уже удалённых линий, сообщение паузы, или сообщение «Игра окончена». msgStatusbar – это пользовательский &lt;a href=&quot;https://pythonworld.ru/gui/pyqt5-eventssignals.html&quot; target=&quot;_blank&quot;&gt;сигнал&lt;/a&gt;, который реализуется в классе Board. showMessage() – это встроенный метод, который отображает сообщение в строке состояния.&lt;/p&gt;
  &lt;pre&gt;self.tboard.start()&lt;/pre&gt;
  &lt;p&gt;Эта строка инициирует игру.&lt;/p&gt;
  &lt;pre&gt;class Board(QFrame):

    msg2Statusbar = pyqtSignal(str)
    ...&lt;/pre&gt;
  &lt;p&gt;Создаётся пользовательский сигнал. msgStatusbar – это сигнал, который срабатывает, когда мы хотим написать сообщение или количество очков в строку состояния.&lt;/p&gt;
  &lt;pre&gt;BoardWidth = 10
BoardHeight = 22
Speed = 300&lt;/pre&gt;
  &lt;p&gt;Это переменные класса Board. BoardWidth и BoardHeight определяют размер доски в блоках. Speed определяет скорость игры. Каждые 300 мс будет начинаться цикл новой игры.&lt;/p&gt;
  &lt;pre&gt;...
self.curX = 0
self.curY = 0
self.numLinesRemoved = 0
self.board = []
...&lt;/pre&gt;
  &lt;p&gt;В методе initBoard() мы инициализируем несколько важных переменных. Переменная self.board – это список чисел от 0 до 7. Она представляет местоположение различных фигур и оставляет фигуры на доске.&lt;/p&gt;
  &lt;pre&gt;def shapeAt(self, x, y):
    return self.board[(y * Board.BoardWidth) + x]&lt;/pre&gt;
  &lt;p&gt;Метод shapeAt() определяет тип фигуры в данном блоке.&lt;/p&gt;
  &lt;pre&gt;def squareWidth(self):
    return self.contentsRect().width() // Board.BoardWidth&lt;/pre&gt;
  &lt;p&gt;Доска может динамически менять размер (например, при изменении размера окна). Как следствие, размер блока может меняться. squareWidth() вычисляет ширину простого квадратика в пикселях и возвращает её. Board.BoardWidth – это размер доски в блоках.&lt;/p&gt;
  &lt;pre&gt;for i in range(Board.BoardHeight):
    for j in range(Board.BoardWidth):
        shape = self.shapeAt(j, Board.BoardHeight - i - 1)

        if shape != Tetrominoe.NoShape:
            self.drawSquare(painter,
                rect.left() + j * self.squareWidth(),
                boardTop + i * self.squareHeight(), shape)&lt;/pre&gt;
  &lt;p&gt;Рисование игры разделяется на два шага. Первым шагом, мы рисуем все фигуры, или оставляем фигуры, которые были сброшены вниз доски. Все квадратики запоминаются в списке переменных self.board. Доступ к переменной получают, используя метод shapeAt().&lt;/p&gt;
  &lt;pre&gt;if self.curPiece.shape() != Tetrominoe.NoShape:

    for i in range(4):

        x = self.curX + self.curPiece.x(i)
        y = self.curY - self.curPiece.y(i)
        self.drawSquare(painter, rect.left() + x * self.squareWidth(),
            boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
            self.curPiece.shape())&lt;/pre&gt;
  &lt;p&gt;Следующий шаг – это рисование упавших вниз частей.&lt;/p&gt;
  &lt;pre&gt;elif key == Qt.Key_Right:
    self.tryMove(self.curPiece, self.curX + 1, self.curY)&lt;/pre&gt;
  &lt;p&gt;В методе keyPressEvent(), мы проверяем нажатые клавиши. Если мы нажали клавишу правой стрелки, мы пробуем передвинуть часть вправо. Мы говорим &amp;quot;пробуем&amp;quot;, поскольку часть может быть на правом крае.&lt;/p&gt;
  &lt;pre&gt;elif key == Qt.Key_Up:
    self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)&lt;/pre&gt;
  &lt;p&gt;Клавиша стрелки вверх будет поворачивать падающую часть влево.&lt;/p&gt;
  &lt;pre&gt;elif key == Qt.Key_Space:
    self.dropDown()&lt;/pre&gt;
  &lt;p&gt;Клавиша «Пробел» будет немедленно бросать падающую часть.&lt;/p&gt;
  &lt;pre&gt;elif key == Qt.Key_D:
    self.oneLineDown()&lt;/pre&gt;
  &lt;p&gt;Нажимая клавишу «d», часть спустится вниз на один блок. Это может быть использовано, чтобы слегка ускорить падение части.&lt;/p&gt;
  &lt;pre&gt;def tryMove(self, newPiece, newX, newY):

    for i in range(4):

        x = newX + newPiece.x(i)
        y = newY - newPiece.y(i)

        if x &amp;lt; 0 or x &amp;gt;= Board.BoardWidth or y &amp;lt; 0 or y &amp;gt;= Board.BoardHeight:
            return False

        if self.shapeAt(x, y) != Tetrominoe.NoShape:
            return False

    self.curPiece = newPiece
    self.curX = newX
    self.curY = newY
    self.update()
    return True&lt;/pre&gt;
  &lt;p&gt;В методе tryMove(), мы пробуем переместить наши фигуры. Если фигура находится на краю доски или примыкает к некоторой другой части, мы возвращаем значение &amp;quot;Ложь&amp;quot;. В противном случае, мы перемещаем текущую падающую часть в новую позицию.&lt;/p&gt;
  &lt;pre&gt;def timerEvent(self, event):

    if event.timerId() == self.timer.timerId():

        if self.isWaitingAfterLine:
            self.isWaitingAfterLine = False
            self.newPiece()
        else:
            self.oneLineDown()

    else:
        super(Board, self).timerEvent(event)&lt;/pre&gt;
  &lt;p&gt;В timerEvent (событии таймера), мы либо создаём новую фигуру после предыдущей, которая упала, либо мы передвигаем падающую часть на одну линию вниз.&lt;/p&gt;
  &lt;pre&gt;def clearBoard(self):

    for i in range(Board.BoardHeight * Board.BoardWidth):
        self.board.append(Tetrominoe.NoShape)&lt;/pre&gt;
  &lt;p&gt;Метод clearBoard() очищает доску путём установки Tetrominoe.Noshape на каждый блок доски.&lt;/p&gt;
  &lt;pre&gt;def removeFullLines(self):

    numFullLines = 0
    rowsToRemove = []

    for i in range(Board.BoardHeight):

        n = 0
        for j in range(Board.BoardWidth):
            if not self.shapeAt(j, i) == Tetrominoe.NoShape:
                n = n + 1

        if n == 10:
            rowsToRemove.append(i)

    rowsToRemove.reverse()


    for m in rowsToRemove:

        for k in range(m, Board.BoardHeight):
            for l in range(Board.BoardWidth):
                    self.setShapeAt(l, k, self.shapeAt(l, k + 1))

    numFullLines = numFullLines + len(rowsToRemove)
    ...&lt;/pre&gt;
  &lt;p&gt;Когда фигура падает, мы вызываем метод removeFullLines(). Мы обнаруживаем все полные линии и удаляем их. Обратите внимание, что мы развернули порядок удаляемых линий. В противном случае, это не будет работать правильно. В нашем случае, мы используем &amp;quot;никакую&amp;quot; гравитацию. Это означает, что части могут парить над пустыми промежутками.&lt;/p&gt;
  &lt;pre&gt;def newPiece(self):

    self.curPiece = Shape()
    self.curPiece.setRandomShape()
    self.curX = Board.BoardWidth // 2 + 1
    self.curY = Board.BoardHeight - 1 + self.curPiece.minY()

    if not self.tryMove(self.curPiece, self.curX, self.curY):

        self.curPiece.setShape(Tetrominoe.NoShape)
        self.timer.stop()
        self.isStarted = False
        self.msg2Statusbar.emit(&amp;quot;Game over&amp;quot;)&lt;/pre&gt;
  &lt;p&gt;Метод newPiece() случайным образом создаёт новую часть тетриса. Если часть не может прийти в свою начальную позицию, игра заканчивается.&lt;/p&gt;
  &lt;pre&gt;class Tetrominoe(object):
    NoShape = 0
    ZShape = 1
    SShape = 2
    LineShape = 3
    TShape = 4
    SquareShape = 5
    LShape = 6
    MirroredLShape = 7&lt;/pre&gt;
  &lt;p&gt;Класс Tetrominoe содержит в себе имена всех возможных фигур. Мы также имеем NoShape для пустого пространства.&lt;/p&gt;
  &lt;pre&gt;class Shape(object):

    coordsTable = (
        ((0, 0),     (0, 0),     (0, 0),     (0, 0)),
        ((0, -1),    (0, 0),     (-1, 0),    (-1, 1)),
        ...
    )
    ...&lt;/pre&gt;
  &lt;p&gt;Класс Shape хранит информацию о частях тетриса.&lt;/p&gt;
  &lt;p&gt;Набор coordsTable содержит в себе всевозможные значения координат наших частей тетриса. Это шаблон, из которого все части берут свои значения координат.&lt;/p&gt;
  &lt;p&gt;self.coords = [[0,0] for i in range(4)]&lt;/p&gt;
  &lt;p&gt;После создания, мы создаём пустой список координат. Список будет хранить координаты частей тетриса.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/63/82/6382463e-648c-459d-a5ef-17608014c0da.png&quot; width=&quot;272&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Изображение выше поможет понять значения координат. Для примера, набор (0, -1), (0, 0), (-1, 0), (-1, -1) представляет S-фигуру. Схема иллюстрирует фигуру.&lt;/p&gt;
  &lt;pre&gt;def rotateLeft(self):

    if self.pieceShape == Tetrominoe.SquareShape:
        return self

    result = Shape()
    result.pieceShape = self.pieceShape

    for i in range(4):

        result.setX(i, self.y(i))
        result.setY(i, -self.x(i))

    return result&lt;/pre&gt;
  &lt;p&gt;Метод rotateLeft() поворачивает часть влево. Квадрат не должен поворачиваться. Вот почему мы просто возвращаем ссылку на текущий объект. Новая часть создаётся и её координаты устанавливаются в одну из повернутых частей.&lt;/p&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/83/52/83528c65-4bfa-48b4-b493-927b72f57a53.png&quot; width=&quot;182&quot; /&gt;
  &lt;/figure&gt;

</content></entry><entry><id>python__lounge:5Ee3y8SHx</id><link rel="alternate" type="text/html" href="https://teletype.in/@python__lounge/5Ee3y8SHx?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=python__lounge"></link><title>Учебный проект на Python: алгоритм Дейкстры, OpenCV и UI</title><published>2021-04-12T18:24:33.518Z</published><updated>2021-04-12T18:24:33.518Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/b6/65/b6651cbd-d28c-472f-92d3-55dfbe57f558.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://habrastorage.org/web/3a8/810/442/3a8810442f124277a2a6d38536ea534a.png&quot;&gt;Лабиринты — это распространенная головоломка для людей, но они представляют из себя интересную задачу для программирования, которую мы можем решить, используя методы кратчайшего пути, такие как алгоритм Дейкстры.</summary><content type="html">
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/web/3a8/810/442/3a8810442f124277a2a6d38536ea534a.png&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Лабиринты — это распространенная головоломка для людей, но они представляют из себя интересную задачу для программирования, которую мы можем решить, используя методы кратчайшего пути, такие как алгоритм Дейкстры.&lt;/p&gt;
  &lt;h3&gt;Вспоминаем алгоритм Дейкстры&lt;/h3&gt;
  &lt;p&gt;Алгоритм Дейкстры — один из наиболее популярных алгоритмов теории графов. Он используется для поиска кратчайшего пути между узлами на ориентированном графе. Мы начнем с исходного узла и известных длин ребер между узлами.&lt;/p&gt;
  &lt;p&gt; Сначала мы присваиваем значение расстояния от источника всем узлам. Узел s получает значение 0, потому что это источник; остальные получают значения ∞ для начала.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/5d1/fd2/7e5/5d1fd27e586466fd29191c1a1547fb0c.png&quot; width=&quot;989&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Наш интересующий узел — это необработанный узел с наименьшим значением (показан серым), то есть s. Сначала мы «ослабляем» каждую смежную вершину до нашего интересующего узла, обновляя их значения до минимума их текущего значения или значения узла интереса плюс длину соединительного ребра…&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/704/261/adc/704261adc70c19804a624d723a5da6ae.png&quot; width=&quot;991&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Узел s теперь завершен (черный), а его соседи a и b приняли новые значения. Новый интересующий узел — b, поэтому мы повторяем процесс «ослабления» соседних узлов b и финализации значения кратчайшего пути для b.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/a9a/7ed/e36/a9a7ede36a3c7b7a713e5dec360a8a75.png&quot; width=&quot;991&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Пройдя через каждый узел, мы в итоге получим график, показывающий кратчайшую длину пути от источника до каждого узла.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/513/f77/cac/513f77cacd3cd542b11fb1bbaa27c64a.png&quot; width=&quot;552&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/bf0/c93/29d/bf0c9329d54d4796f6f7a2a045e77639.png&quot; width=&quot;552&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/d2b/08c/79c/d2b08c79cc8b19dd522b6a7a5607aab6.png&quot; width=&quot;989&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;&lt;em&gt;Наша финальная диаграмма после запуска алгоритма Дейкстры. Числа в каждом узле представляют кратчайшее возможное расстояние от исходного узла.&lt;/em&gt;&lt;/p&gt;
  &lt;h3&gt;Концептуализация изображений лабиринта&lt;/h3&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/984/82e/81b/98482e81b4323c066eaacaa0d11cd78f.png&quot; width=&quot;1400&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Мы можем представить себе изображение как матрицу пикселей. Каждый пиксель (для простоты) имеет значение RGB 0,0,0 (черный) или 255,255,255 (белый). Наша цель — создать кратчайший путь, который начинается на белом и не переходит на чёрные границы. Чтобы представить эту цель, мы можем рассматривать каждый пиксель как узел и рисовать ребра между соседними пикселями с длиной ребер, основанной на разнице значений RGB. Мы будем использовать формулу евклидова квадратного расстояния и добавим 0,1, чтобы гарантировать отсутствие длины пути 0 — (требование для алгоритма Дейкстры):&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/ece/7e5/3e1/ece7e53e1e273c0de926313402b3009d.jpg&quot; width=&quot;534&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Эта формула делает расстояние пересечения через границу лабиринта чрезмерно большим. Как мы видим, кратчайший путь от источника к месту назначения будет четко проходить вокруг барьера, а не через него.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/49b/fc5/5a6/49bfc55a6eeec216c364293e21afde16.png&quot; width=&quot;593&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3&gt;Реализация&lt;/h3&gt;
  &lt;p&gt;Мы можем использовать OpenCV, популярную библиотеку компьютерного зрения для Python, чтобы извлечь значения пикселей и показать наши изображения лабиринта. Давайте также определим координаты нашего начального и конечного местоположений, добавив точки в наш лабиринт&lt;/p&gt;
  &lt;pre&gt;import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread(&amp;#x27;maze.png&amp;#x27;) # read an image from a file using
cv2.circle(img,(5,220), 3, (255,0,0), -1) # add a circle at (5, 220)
cv2.circle(img, (25,5), 3, (0,0,255), -1) # add a circle at (5,5)
plt.figure(figsize=(7,7))
plt.imshow(img) # show the image
plt.show()&lt;/pre&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/129/d4b/7c7/129d4b7c70eda55f1bdb7661c7afc866.png&quot; width=&quot;285&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Мы создаем класс Vertex, который поможет нам отслеживать координаты. Мы также хотим отслеживать родительский узел, чтобы мы могли восстановить весь путь, как только найдем значения расстояния.&lt;/p&gt;
  &lt;pre&gt;class Vertex:
    def __init__(self,x_coord,y_coord):
        self.x=x_coord
        self.y=y_coord
        self.d=float(&amp;#x27;inf&amp;#x27;) #current distance from source node
        self.parent_x=None
        self.parent_y=None
        self.processed=False
        self.index_in_queue=None&lt;/pre&gt;
  &lt;p&gt;Нам нужно создать матрицу вершин, представляющую двухмерное расположение пикселей на изображении. Это будет основой для алгоритма Дейкстры. Мы также поддерживаем очередь с минимальной кучей приоритетов для отслеживания необработанных узлов.&lt;/p&gt;
  &lt;pre&gt;def find_shortest_path(img,src,dst):
    pq=[] #min-heap priority queue
    imagerows,imagecols=img.shape[0],img.shape[1]
    matrix = np.full((imagerows, imagecols), None) 
    #access matrix elements by matrix[row][col]
    #fill matrix with vertices
    for r in range(imagerows):
        for c in range(imagecols):
            matrix[r][c]=Vertex(c,r)
            matrix[r][c].index_in_queue=len(pq)
            pq.append(matrix[r][c])
    #set source distance value to 0
    matrix[source_y][source_x].d=0
    #maintain min-heap invariant (minimum d Vertex at list index 0)
    pq = bubble_up(pq, matrix[source_y][source_x].index_in_queue)&lt;/pre&gt;
  &lt;p&gt;Нам нужно несколько вспомогательных функций, чтобы помочь найти ребра и длину ребер между вершинами:&lt;/p&gt;
  &lt;pre&gt;#Implement euclidean squared distance formula
def get_distance(img,u,v):
    return 0.1 + (float(img[v][0])-float(img[u][0]))**2+(float(img[v][1])-float(img[u][1]))**2+(float(img[v][2])-float(img[u][2]))**2
#Return neighbor directly above, below, right, and left
def get_neighbors(mat,r,c):
    shape=mat.shape
    neighbors=[]
    #ensure neighbors are within image boundaries
    if r &amp;gt; 0 and not mat[r-1][c].processed:
         neighbors.append(mat[r-1][c])
    if r &amp;lt; shape[0] - 1 and not mat[r+1][c].processed:
            neighbors.append(mat[r+1][c])
    if c &amp;gt; 0 and not mat[r][c-1].processed:
        neighbors.append(mat[r][c-1])
    if c &amp;lt; shape[1] - 1 and not mat[r][c+1].processed:
            neighbors.append(mat[r][c+1])
    return neighbors&lt;/pre&gt;
  &lt;p&gt;Теперь мы можем реализовать алгоритм Дейкстры и присвоить значения расстояния (d) всем вершинам пикселей в изображении лабиринта:&lt;/p&gt;
  &lt;pre&gt;while len(pq) &amp;gt; 0:
    u=pq[0] #smallest-value unprocessed node
    #remove node of interest from the queue
    pq[0]=pq[-1] 
    pq[0].index_in_queue=0
    pq.pop()
    pq=bubble_down(pq,0) #min-heap function, see source code 
    
    u.processed=True
    neighbors = get_neighbors(matrix,u.y,u.x)
    for v in neighbors:
        dist=get_distance(img,(u.y,u.x),(v.y,v.x))
        if u.d + dist &amp;lt; v.d:
            v.d = u.d+dist
            v.parent_x=u.x #keep track of the shortest path
            v.parent_y=u.y
            idx=v.index_in_queue
            pq=bubble_down(pq,idx) 
            pq=bubble_up(pq,idx)&lt;/pre&gt;
  &lt;p&gt;Теперь мы можем вызвать функцию кратчайшего пути и нарисовать решение в нашем лабиринте:&lt;/p&gt;
  &lt;pre&gt;img = cv2.imread(&amp;#x27;maze.png&amp;#x27;) # read an image from a file using opencv (cv2) library
p = find_shortest_path(img, (25,5), (5,220))
drawPath(img,p)
plt.figure(figsize=(7,7))
plt.imshow(img) # show the image on the screen 
plt.show()&lt;/pre&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/4aa/c1d/13f/4aac1d13fca07f03cd5b3ab597b7a162.png&quot; width=&quot;225&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/4bd/cfe/895/4bdcfe89553bcbac1dbb60cd6e03fb76.png&quot; width=&quot;225&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Давайте попробуем другие лабиринты со всего Интернета.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/328/8c3/ce8/3288c3ce8b2e32ccad5ef94ebfcf8b96.jpg&quot; width=&quot;564&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/800/377/c55/800377c55b4dbc74167bb9544f9395c0.jpg&quot; width=&quot;564&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/592/2d6/932/5922d693210bf2a327d5b5e12ac6d7c1.png&quot; width=&quot;552&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/post_images/cf4/921/796/cf49217961fb2dc83f9c6097bbf6ef4e.png&quot; width=&quot;552&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Полный исходный код доступен на GitHub &lt;a href=&quot;https://github.com/maxwellreynolds/Maze&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;.&lt;/p&gt;

</content></entry><entry><id>python__lounge:FNLcJsB0h</id><link rel="alternate" type="text/html" href="https://teletype.in/@python__lounge/FNLcJsB0h?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=python__lounge"></link><title>Алгоритм Дейкстры для поиска кратчайшего пути в Python</title><published>2021-04-11T15:34:54.194Z</published><updated>2021-04-11T15:34:54.194Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/94/d6/94d64df5-04ae-4a71-bd6c-5120e7276f95.jpeg"></media:thumbnail><summary type="html">&lt;img src=&quot;https://python-scripts.com/wp-content/uploads/2019/11/%D0%93%D1%80%D0%B0%D1%84-%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC-%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B.jpg&quot;&gt;@python_lounge</summary><content type="html">
  &lt;p&gt;@python_lounge&lt;/p&gt;
  &lt;p data-align=&quot;center&quot;&gt;@python_lounge&lt;/p&gt;
  &lt;h3&gt;Создание графа для алгоритмы Дейкстры&lt;/h3&gt;
  &lt;p&gt;Важно понимать, граф представляет собой множество узлов (nodes), соединенных ребрами (edges).&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://python-scripts.com/wp-content/uploads/2019/11/%D0%93%D1%80%D0%B0%D1%84-%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC-%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B.jpg&quot; width=&quot;750&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Узел — это просто какой-то объект, а ребро — это связь между двумя узлами. Выше представлен &lt;em&gt;неориентированный граф&lt;/em&gt;, то есть без четких направлений, и здешние ребра являются двунаправленными. Существуют также &lt;em&gt;ориентированные графы&lt;/em&gt;, у ребер которых есть конкретно указанное направление.&lt;/p&gt;
  &lt;p&gt;Как узлы, так и ребра могут быть носителями информации. К примеру, граф выше представляет собой систему зданий, которые соединены между собой туннелями. В узлах может находиться информация о названии здания (например, строка «Библиотека»), в то время, как ребро может содержать информацию о длине туннеля.&lt;/p&gt;
  &lt;p&gt;Графам можно найти удачное применение в огромном количестве востребованных областей: веб-страницы (узлы) с ссылками на другие страницы (ребра), маршрутизация пакетов в сетях, социальные сети, приложения для создания карт улиц, моделирование молекулярных связей, различные области математики, лингвистика, социология. В общем и целом, это может быть любая система, в которой оперируют связанные между собой объекты.&lt;/p&gt;
  &lt;h3&gt;Реализация графов в Python&lt;/h3&gt;
  &lt;p&gt;Данный этап немного выходит за рамки темы, рассматриваемой в статье, поэтому мы не будем вдаваться в подробности. Два самых популярных способа реализации графа — через матрицу смежности или через список смежности. У каждого метода есть свои преимущества и недостатки. Сначала рассмотрим реализацию через матрицу смежности, так как его проще представить визуально и понять на интуитивном уровне. Позже станет яснее, отчего определение базовой реализации оказывает сильное влияние на время выполнения. Любая реализация может задействовать алгоритм Дейкстры, а пока важно различать API, или абстракции (методы), которые могут взаимодействовать с графом. Кратко осветим реализацию матрицы смежности на примере кода Python. Для ознакомления с реализацией списка смежности хорошим стартом станет данная статья.&lt;/p&gt;
  &lt;p&gt;Ниже представлена матрица смежности для предыдущего графа. Каждый ряд представляет один узел графа, как и каждый столбец. В данном случае ряд 0 и столбец 0 представляют узел «А»; ряд 1 и столбец 1 — узел «B», ряд 2 и столбец 2 — узел «C» и так далее, принцип понятен. Каждый локальный элемент {ряд, столбец} представляет ребро. Таким образом, каждый ряд показывает связь между одним узлом и всеми прочими узлами. Элемент «0» указывает на отсутствие ребра, в то время как «1» указывает на присутствие ребра, связывающего row_node и column_node в направлении row_node → column_node. Поскольку граф в примере является неориентированным, данная матрица равна его транспонированию (то есть матрица симметричная), поскольку каждая связь двунаправленная. Вы могли заметить, что главная диагональ матрицы представлена только нулями, а все оттого, что ни один узел не связан сам с собой. Таким образом, пространственная сложность данного представления нерасчетлива.&lt;/p&gt;
  &lt;pre&gt;Теперь разберемся с кодом. Обратите внимание, что здесь задействовано немного экстра-данных — так как нам было нужно, чтобы реальные объекты узлов содержали определенную информацию, в классе Graph был реализован массив объектов узлов, индексы которых соответствуют номеру их ряда (столбца) в матрице смежности. В Graph также был добавлен вспомогательный метод, который позволяет использовать либо номера индекса узла, либо объект узда в качестве аргументов для методов класса Graph. Данные классы нельзя считать примерами элегантности, однако они хорошо выполняют свою работу, излишне не усложняя процесс:class Node: 
    def __init__(self, data, indexloc = None):
        self.data = data
        self.index = indexloc
        
      
class Graph:
 
    @classmethod
    def create_from_nodes(self, nodes):
        return Graph(len(nodes), len(nodes), nodes)
 
  
    def __init__(self, row, col, nodes = None):
        # установка матрица смежности
        self.adj_mat = [[0] * col for _ in range(row)]
        self.nodes = nodes
        for i in range(len(self.nodes)):
            self.nodes[i].index = i
 
    # Связывает node1 с node2
    # Обратите внимание, что ряд - источник, а столбец - назначение 
    # Обновлен для поддержки взвешенных ребер (поддержка алгоритма Дейкстры)
    def connect_dir(self, node1, node2, weight = 1):
        node1, node2 = self.get_index_from_node(node1), self.get_index_from_node(node2)
        self.adj_mat[node1][node2] = weight
  
    # Опциональный весовой аргумент для поддержки алгоритма Дейкстры
    def connect(self, node1, node2, weight = 1):
        self.connect_dir(node1, node2, weight)
        self.connect_dir(node2, node1, weight)
 
    # Получает ряд узла, отметить ненулевые объекты с их узлами в массиве self.nodes
    # Выбирает любые ненулевые элементы, оставляя массив узлов
    # которые являются connections_to (для ориентированного графа)
    # Возвращает значение: массив кортежей (узел, вес)
    def connections_from(self, node):
        node = self.get_index_from_node(node)
        return [(self.nodes[col_num], self.adj_mat[node][col_num]) for col_num in range(len(self.adj_mat[node])) if self.adj_mat[node][col_num] != 0]
 
    # Проводит матрицу к столбцу узлов
    # Проводит любые ненулевые элементы узлу данного индекса ряда
    # Выбирает только ненулевые элементы
    # Обратите внимание, что для неориентированного графа
    # используется connections_to ИЛИ connections_from
    # Возвращает значение: массив кортежей (узел, вес)
    def connections_to(self, node):
      node = self.get_index_from_node(node)
      column = [row[node] for row in self.adj_mat]
      return [(self.nodes[row_num], column[row_num]) for row_num in range(len(column)) if column[row_num] != 0]
    
  
    def print_adj_mat(self):
      for row in self.adj_mat:
          print(row)
  
    def node(self, index):
      return self.nodes[index]
    
  
    def remove_conn(self, node1, node2):
      self.remove_conn_dir(node1, node2)
      self.remove_conn_dir(node2, node1)
  
    # Убирает связь в направленной манере (nod1 к node2)
    # Может принять номер индекса ИЛИ объект узла
    def remove_conn_dir(self, node1, node2):
      node1, node2 = self.get_index_from_node(node1), self.get_index_from_node(node2)
      self.adj_mat[node1][node2] = 0  
  
    # Может пройти от node1 к node2
    def can_traverse_dir(self, node1, node2):
      node1, node2 = self.get_index_from_node(node1), self.get_index_from_node(node2)
      return self.adj_mat[node1][node2] != 0  
  
    def has_conn(self, node1, node2):
      return self.can_traverse_dir(node1, node2) or self.can_traverse_dir(node2, node1)
  
    def add_node(self,node):
      self.nodes.append(node)
      node.index = len(self.nodes) - 1
      for row in self.adj_mat:
        row.append(0)    
      self.adj_mat.append([0] * (len(self.adj_mat) + 1))
 
    # Получает вес, представленный перемещением от n1
    # к n2. Принимает номера индексов ИЛИ объекты узлов
    def get_weight(self, n1, n2):
        node1, node2 = self.get_index_from_node(n1), self.get_index_from_node(n2)
        return self.adj_mat[node1][node2]
  
    # Разрешает проводить узлы ИЛИ индексы узлов  
    def get_index_from_node(self, node):
        if not isinstance(node, Node) and not isinstance(node, int):
            raise ValueError(&amp;quot;node must be an integer or a Node object&amp;quot;)
        if isinstance(node, int):
            return node
        else:
            return node.index

&lt;/pre&gt;
  &lt;p&gt;Здесь классы &lt;code&gt;Node&lt;/code&gt; и &lt;code&gt;Graph&lt;/code&gt; будут использованы для описания данного примера графа. Поместим вышеуказанный граф в код и посмотрим, получится ли в итоге верная матрица смежности:&lt;/p&gt;
  &lt;pre&gt;a = Node(&amp;quot;A&amp;quot;)
b = Node(&amp;quot;B&amp;quot;)
c = Node(&amp;quot;C&amp;quot;)
d = Node(&amp;quot;D&amp;quot;)
e = Node(&amp;quot;E&amp;quot;)
f = Node(&amp;quot;F&amp;quot;)
 
graph = Graph.create_from_nodes([a,b,c,d,e,f])
 
graph.connect(a, b)
graph.connect(a, c)
graph.connect(a, e)
graph.connect(b, c)
graph.connect(b, d)
graph.connect(c, d)
graph.connect(c, f)
graph.connect(d, e)
 
graph.print_adj_mat()
&lt;/pre&gt;
  &lt;p&gt;Матрицы смежности, полученная в выводе (из &lt;code&gt;graph.print_adj_mat()&lt;/code&gt;) после запуска кода, такая же, как и та, что была рассчитана ранее:&lt;/p&gt;
  &lt;pre&gt;[0, 1, 1, 0, 1, 0]
[1, 0, 1, 1, 0, 0]
[1, 1, 0, 1, 0, 1]
[0, 1, 1, 0, 1, 0]
[1, 0, 0, 1, 0, 0]
[0, 0, 1, 0, 0, 0]
&lt;/pre&gt;
  &lt;p&gt;При желании добавить расстояние ребрам графа нужно просто заменить единицы в данной матрице смежности на значения нужного расстояния. На данный момент класс Graph поддерживает данную функциональность, доказательством чему является код ниже. Для ясности демонстрации были добавлены произвольные значения длины:&lt;/p&gt;
  &lt;pre&gt;w_graph = Graph.create_from_nodes([a,b,c,d,e,f])
 
w_graph.connect(a, b, 5)
w_graph.connect(a, c, 10)
w_graph.connect(a, e, 2)
w_graph.connect(b, c, 2)
w_graph.connect(b, d, 4)
w_graph.connect(c, d, 7)
w_graph.connect(c, f, 10)
w_graph.connect(d, e, 3)
 
w_graph.print_adj_mat()
&lt;/pre&gt;
  &lt;p&gt;В результате выводится следующая матрица смежности:&lt;/p&gt;
  &lt;pre&gt;[0 , 5 , 10, 0, 2, 0]
[5 , 0 , 2 , 4 , 0 , 0]
[10, 2, 0, 7, 0, 10]
[0 , 4 , 7 , 0 , 3 , 0]
[2 , 0 , 0 , 3 , 0 , 0]
[0, 0 , 10, 0 , 0 , 0]
&lt;/pre&gt;
  &lt;p&gt;Визуально данный граф будет представлен следующим образом:&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://python-scripts.com/wp-content/uploads/2019/11/%D0%93%D1%80%D0%B0%D1%84-%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D0%B0-%D1%81%D0%BC%D0%B5%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8.jpg&quot; width=&quot;750&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Для того чтобы ребра содержали больше информации, нужно чтобы матрица содержала объекты ребер вместо простых целых чисел.&lt;/p&gt;
  &lt;h3&gt;Алгоритм Дейкстры&lt;/h3&gt;
  &lt;p&gt;Перед непосредственным составлением кода, осветим ключевые моменты темы:&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Главное условие: отрицательных длин ребер не бывает.&lt;/li&gt;
    &lt;li&gt;Алгоритм Дейкстры изначально создавался &lt;strong&gt;для поиска кратчайшего пути&lt;/strong&gt; между двумя конкретными узлами. Однако сегодня он также широко используется для поиска кратчайших путей между узлом источника и &lt;em&gt;всеми&lt;/em&gt; остальными узлами. В статье будет рассмотрен второй вариант. Для осуществления первого случая понадобится просто остановить алгоритм сразу после того, как узел назначения будет добавлен в набор &lt;code&gt;seen&lt;/code&gt;. По ходу дела все станет намного понятнее.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;Окей, примемся за дело. Нам нужно найти исходным узлом и всеми другими узлами (или узлом назначения), однако проверять КАЖДУЮ возможную комбинацию отправления-назначения не хочется. При наличии крупного графа на это потребуется огромное количество времени, и большая часть проверенных путей в конечном итоге окажется ненужной. По этой причине, пойдем напролом и задействуем &lt;em&gt;жадный&lt;/em&gt; подход. Наслаждайтесь моментом, ведь это тот редкий случай, когда жадность будет вознаграждена.&lt;/p&gt;
  &lt;p&gt;Итак, что мы понимаем под &lt;em&gt;жадным&lt;/em&gt; алгоритмом? По сути, это значит, что принятые решения будут обусловлены самым оптимальным выбором на данный конкретный момент времени. Метод подойдет далеко не для каждого случая. К примеру, при реализации шахматного бота фокус точно не пройдет — зачем брать враждебного ферзя, если через ход это обернется шахом со стороны противника. Для таких ситуаций больше подойдет &lt;a href=&quot;https://www.geeksforgeeks.org/minimax-algorithm-in-game-theory-set-1-introduction/&quot; target=&quot;_blank&quot;&gt;minimax&lt;/a&gt;. В рассматриваемом сейчас случае жадный алгоритм действительно является наилучшим вариантом, который значительно сокращает число проверок, что нужно совершить без потери точности. Как?&lt;/p&gt;
  &lt;p&gt;Скажем, мы находимся в исходном узле. Предположим, что начальное &lt;strong&gt;предварительное расстояние&lt;/strong&gt; от узла источника до каждого другого узла в графе является &lt;em&gt;бесконечностью&lt;/em&gt; (перепроверим позже). Известно, что по умолчанию расстояние от узла источника до этого же узла источника минимально (0), ведь отрицательных длин ребер быть не может. Наш исходный узел осматривает все соседние узлы и обновляет &lt;strong&gt;предварительное&lt;/strong&gt; расстояние от узла источника до длины ребра от узла источника до конкретного соседнего элемента (плюс 0). Обратите внимание, что НУЖНО проверить каждого ближайшего соседа, этого никак не пропустить. Затем алгоритм делает тот самый &lt;em&gt;жадный&lt;/em&gt; выбор для следующей оценки узла, который находится на ближайшем расстоянии к источнику. У нас исходный узел отмечен как &lt;code&gt;visited&lt;/code&gt;, поэтому к нему можно не возвращаться и сразу перейти к следующему узлу.&lt;/p&gt;
  &lt;p&gt;Теперь задумаемся, где мы сейчас находимся в плане логики, так как это важно для реализации. Узел, что сейчас оценивается (ближайший к источнику) больше НИКОГДА не будет оцениваться вновь в плане кратчайшего пути от исходного узла. Его &lt;strong&gt;предварительное расстояние&lt;/strong&gt; теперь стало точным расстоянием. Хотя вполне возможно, что пути от исходного узла к данному узлу могут проходить через иные маршруты, можно с уверенностью сказать, что они будут затратнее, нежели текущий путь. Он был выбран ввиду того, что &lt;em&gt;был кратчайшим&lt;/em&gt; по сравнению с любым другим узлом, связанным с источником. Следовательно, любой другой путь &lt;em&gt;будет длиннее&lt;/em&gt;, чем текущее расстояние от исходного узла до рассматриваемого узла.&lt;/p&gt;
  &lt;p&gt;При использовании графика из примера в случае установки исходного узла как &lt;code&gt;А&lt;/code&gt;, мы бы назначили предварительные расстояния для узлов &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;C&lt;/code&gt; и &lt;code&gt;E&lt;/code&gt;. Поскольку у &lt;code&gt;E&lt;/code&gt; было кратчайшее расстояние от &lt;code&gt;A&lt;/code&gt;, после этого был посещен узел &lt;code&gt;E&lt;/code&gt;. И теперь, хотя наверняка есть несколько других способов добраться из &lt;code&gt;А&lt;/code&gt; до &lt;code&gt;Е&lt;/code&gt;, они будут затратнее, нежели текущее расстояние &lt;code&gt;А&lt;/code&gt; → &lt;code&gt;Е&lt;/code&gt;. Другие маршруты должны проходить через &lt;code&gt;B&lt;/code&gt; или &lt;code&gt;С&lt;/code&gt;, а в результате проверки стало ясно, что они дальше от &lt;code&gt;A&lt;/code&gt;, чем &lt;code&gt;E&lt;/code&gt;. Жадный выбор был сделан, а это ограничивает общее количество проверок, которые нам нужно сделать, и при этом точность не теряется. Неплохо.&lt;/p&gt;
  &lt;p&gt;Продолжим логическую цепочку с использованием графа из примера. Просто повторим для &lt;code&gt;E&lt;/code&gt; действия, сделанные для &lt;code&gt;A&lt;/code&gt;. Обновляются все ближайшие соседние элементы к &lt;code&gt;E&lt;/code&gt; с предварительным расстоянием, равным &lt;code&gt;length(A у E) + edge_length(E к соседнему элементу)&lt;/code&gt;. Это верно в том случае, &lt;strong&gt;ЕСЛИ&lt;/strong&gt; данное расстояние меньше, чем текущее предварительное расстояние, или же предварительное расстояние еще не было установлено.&lt;/p&gt;
  &lt;blockquote&gt;&lt;strong&gt;Обратите внимание&lt;/strong&gt;: Для достижения данной функциональности здесь просто инициализируются все предварительные расстояния до бесконечности.&lt;/blockquote&gt;
  &lt;p&gt;Далее делается жадный выбор касательно того, какой узел должен оцениваться следующим. Выбирается один узел из графа с наименьшим предварительным расстоянием, и добавляется &lt;code&gt;E&lt;/code&gt; к набору &lt;code&gt;seen nodes&lt;/code&gt;. Теперь он повторно оцениваться не будет. У данного нового узла та же гарантия, что что и &lt;code&gt;E&lt;/code&gt; — его предварительное расстояние от &lt;code&gt;A&lt;/code&gt; является определенным минимальным расстоянием от &lt;code&gt;A&lt;/code&gt;. Чтобы понять это, давайте оценим возможности (хотя они могут не соответствовать графу примера, для ясности используем те же названия). Если следующий узел является соседом &lt;code&gt;E&lt;/code&gt;, но не &lt;code&gt;A&lt;/code&gt;, то он будет выбран, потому что его временное расстояние все еще короче, чем любого другого прямого соседа &lt;code&gt;A&lt;/code&gt;. По этой причине нет другого возможного кратчайшего пути, только через &lt;code&gt;E&lt;/code&gt;. Если следующий выбранный узел будет непосредственным соседом &lt;code&gt;A&lt;/code&gt;, то есть вероятность, что этот узел обеспечит более короткий путь к некоторым из соседей &lt;code&gt;E&lt;/code&gt;, чем сам &lt;code&gt;E&lt;/code&gt;.&lt;/p&gt;
  &lt;h3&gt;Обзор кода алгоритма Дейкстры на Python&lt;/h3&gt;
  &lt;p&gt;Давайте более четко и формально рассмотрим процесс реализации алгоритма Дейкстры.&lt;/p&gt;
  &lt;p&gt;ИНИЦИАЛИЗАЦИЯ&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Установите &lt;code&gt;provisional_distance&lt;/code&gt; для всех узлов от исходного узла до бесконечности.&lt;/li&gt;
    &lt;li&gt;Определите пустой набор &lt;code&gt;seen_nodes&lt;/code&gt;. Данный набор гарантирует, что узел, у которого уже есть кратчайший путь, не будет повторно рассмотрен, а также то, что не будут рассматриваться пути через узел, у которого более короткий путь к источнику, чем текущий путь. Помните, что узлы входят в &lt;code&gt;seen_nodes&lt;/code&gt; &lt;strong&gt;только&lt;/strong&gt; &lt;strong&gt;после доказательства&lt;/strong&gt; того, что в наличии есть абсолютное кратчайшее расстояние (а не только предварительное расстояние). Набор используется для получения времени поиска &lt;code&gt;O(1)&lt;/code&gt; вместо многократного выполнения поиска через массив &lt;code&gt;O(n)&lt;/code&gt;.&lt;/li&gt;
    &lt;li&gt;Установите &lt;code&gt;provisional_distance&lt;/code&gt; для исходного узла со значением 0, и массив, представляющий перескоки для простого включения самого исходного кода. Это будет полезно позже, когда мы проследим выбранный для графа путь для расчета минимального расстояния.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;ПРОЦЕДУРА ИТЕРАЦИИ&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Пока (while) все узлы увидеть (&lt;code&gt;seen&lt;/code&gt;) не удалось. Или, в случае поиска одного узла назначения, пока не удалось увидеть (&lt;code&gt;seen&lt;/code&gt;) данный узел назначения.&lt;/li&gt;
    &lt;li&gt;Установите &lt;code&gt;current_node&lt;/code&gt; для узла c самым малым предварительным расстоянием &lt;code&gt;provisional_distance&lt;/code&gt; во всем графе. Обратите внимание, что для первой &lt;a href=&quot;https://python-scripts.com/itertools&quot; target=&quot;_blank&quot;&gt;итерации&lt;/a&gt; это будет исходный узел &lt;code&gt;source_node&lt;/code&gt;, так как предварительное расстояние &lt;code&gt;provisional_distance&lt;/code&gt; установлено на 0.&lt;/li&gt;
    &lt;li&gt;Добавьте текущий узел &lt;code&gt;current_node&lt;/code&gt; к набору просмотренных узлов &lt;code&gt;seen_nodes&lt;/code&gt;.&lt;/li&gt;
    &lt;li&gt;Обновите &lt;code&gt;provisional_distance&lt;/code&gt; каждого соседнего элемента &lt;code&gt;current_node&lt;/code&gt; до (абсолютного) расстояния от &lt;code&gt;current_node&lt;/code&gt; до &lt;code&gt;source_node&lt;/code&gt; вдобавок к длине ребра от &lt;code&gt;current_node&lt;/code&gt; к данному соседнему элементу, ЕСЛИ данное значение меньше, чем текущее значение соседнего &lt;code&gt;provisional_distance&lt;/code&gt;. Если у соседнего элемента еще не было набора предварительного расстояния, помните, что он инициализирован до бесконечности, и по этой причине должен быть больше, чем данная сумма. При обновлении &lt;code&gt;provisional_distance&lt;/code&gt; также обновляются «перескоки», которые были сделаны для получения расстояния, задействуя конкатенацию перескоков &lt;code&gt;current_node&lt;/code&gt; к исходному узлу с самим &lt;code&gt;current_node&lt;/code&gt;.&lt;/li&gt;
    &lt;li&gt;Завершение цикла &lt;strong&gt;while&lt;/strong&gt;.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3&gt;Алгоритм Дейкстры через схемы и изображения&lt;/h3&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://python-scripts.com/wp-content/uploads/2019/11/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC-%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B_01.jpg&quot; width=&quot;750&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://python-scripts.com/wp-content/uploads/2019/11/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC-%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B_02.jpg&quot; width=&quot;750&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Обратите внимание, что в дальнейшем можно посетить либо &lt;code&gt;D&lt;/code&gt;, либо &lt;code&gt;B&lt;/code&gt;. Сейчас мы посетим &lt;code&gt;B&lt;/code&gt;.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://python-scripts.com/wp-content/uploads/2019/11/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC-%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B_03.jpg&quot; width=&quot;750&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://python-scripts.com/wp-content/uploads/2019/11/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC-%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B_04.jpg&quot; width=&quot;750&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://python-scripts.com/wp-content/uploads/2019/11/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC-%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B_05.jpg&quot; width=&quot;750&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://python-scripts.com/wp-content/uploads/2019/11/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC-%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B_06.jpg&quot; width=&quot;750&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Здесь программа завершается. В результате мы получаем &lt;strong&gt;кратчайшие пути для каждого узла графа&lt;/strong&gt;.&lt;/p&gt;
  &lt;h3&gt;Python код для алгоритма Дейкстры&lt;/h3&gt;
  &lt;p&gt;Посмотрим, как будет выглядеть &lt;strong&gt;реализация алгоритма Дейстры в Python&lt;/strong&gt;. Это экземпляр метода внутри раннее используемого класса &lt;code&gt;Graph&lt;/code&gt;, который задействует преимущества других методов и структуры:&lt;/p&gt;
  &lt;pre&gt; def dijkstra(self, node):        # Получает индекс узла (или поддерживает передачу int)
        nodenum = self.get_index_from_node(node)
        # Заставляет массив отслеживать расстояние от одного до любого узла
        # в self.nodes. Инициализирует до бесконечности для всех узлов, кроме 
        # начального узла, сохраняет &amp;quot;путь&amp;quot;, связанный с расстоянием. 
        # Индекс 0 = расстояние, индекс 1 = перескоки узла
        dist = [None] * len(self.nodes)
        for i in range(len(dist)):
            dist[i] = [float(&amp;quot;inf&amp;quot;)]
            dist[i].append([self.nodes[nodenum]])
        
        dist[nodenum][0] = 0
        # Добавляет в очередь все узлы графа
        # Отмечает целые числа в очереди, соответствующие индексам узла
        # локаций в массиве self.nodes 
        queue = [i for i in range(len(self.nodes))]
        # Набор увиденных на данный момент номеров 
        seen = set()
        while len(queue) &amp;gt; 0:
            # Получает узел в очереди, который еще не был рассмотрен
            # и который находится на кратчайшем расстоянии от источника
            min_dist = float(&amp;quot;inf&amp;quot;)
            min_node = None
            for n in queue: 
                if dist[n][0] &amp;lt; min_dist and n not in seen:
                    min_dist = dist[n][0]
                    min_node = n
            
            # Добавляет мин. расстояние узла до увиденного, убирает очередь
            queue.remove(min_node)
            seen.add(min_node)
            # Получает все следующие перескоки
            connections = self.connections_from(min_node)
            # Для каждой связи обновляет путь и полное расстояние от  
            # исходного узла, если полное расстояние меньше
            # чем текущее расстояние в массиве dist
            for (node, weight) in connections: 
                tot_dist = weight + min_dist
                if tot_dist &amp;lt; dist[node.index][0]:
                    dist[node.index][0] = tot_dist
                    dist[node.index][1] = list(dist[min_node][1])
                    dist[node.index][1].append(node)
        return dist
&lt;/pre&gt;
  &lt;p&gt;При использовании данного метода можно протестировать нашу картинку:&lt;/p&gt;
  &lt;pre&gt;a = Node(&amp;quot;A&amp;quot;)
&lt;/pre&gt;
  &lt;p&gt;&lt;code&gt;b = Node(&amp;quot;B&amp;quot;)&lt;/code&gt;&lt;/p&gt;
  &lt;pre&gt;c = Node(&amp;quot;C&amp;quot;)
d = Node(&amp;quot;D&amp;quot;)
e = Node(&amp;quot;E&amp;quot;)
f = Node(&amp;quot;F&amp;quot;)
 
w_graph = Graph.create_from_nodes([a,b,c,d,e,f])
 
w_graph.connect(a,b,5)
w_graph.connect(a,c,10)
w_graph.connect(a,e,2)
w_graph.connect(b,c,2)
w_graph.connect(b,d,4)
w_graph.connect(c,d,7)
w_graph.connect(c,f,10)
w_graph.connect(d,e,3)
 
print([(weight, [n.data for n in node]) for (weight, node) in w_graph.dijkstra(a)])
&lt;/pre&gt;
  &lt;p&gt;Для получения читабельного для людей вывода нужно отметить объекты узла в их &lt;code&gt;data&lt;/code&gt;. Результат станет следующим:&lt;/p&gt;
  &lt;p&gt;&lt;code&gt;[(0, [‘A’]), (5, [‘A’, ‘B’]), (7, [‘A’, ‘B’, ‘C’]), (5, [‘A’, ‘E’, ‘D’]), (2, [‘A’, ‘E’]), (17, [‘A’, ‘B’, ‘C’, ‘F’])]&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Здесь каждый &lt;a href=&quot;https://python-scripts.com/lists-tuples-dictionaries#tuple&quot; target=&quot;_blank&quot;&gt;кортеж&lt;/a&gt; — &lt;code&gt;(total_distance, [hop_path])&lt;/code&gt;. Это совпадает с рассматриваемой картинкой.&lt;/p&gt;
  &lt;h3&gt;Быстрый способ реализации алгоритма Дейкстры в Python&lt;/h3&gt;
  &lt;p&gt;На данный момент тем, кто заинтересован только в функциональности, хватит предоставленного материала. Однако, тем, кому важна скорость стоит продолжить чтение.&lt;/p&gt;
  &lt;p&gt;Остановимся на одном довольно важном моменте. В каждой итерации нам нужно найти узел с кратчайшим предварительным расстоянием, что требуется для следующего жадного решения. Сейчас осматривается &lt;code&gt;list&lt;/code&gt;, вызывая &lt;code&gt;queue&lt;/code&gt; (используя данные значения в &lt;code&gt;dist&lt;/code&gt;) с целью получения желаемого. У данного &lt;code&gt;queue&lt;/code&gt; может быть максимальная длина &lt;code&gt;n&lt;/code&gt;, что является числом узлов. Итерация по этому списку является операцией &lt;code&gt;O(n)&lt;/code&gt;, которую мы выполняем в каждой итерации &lt;a href=&quot;https://python-scripts.com/loops-for-while#while&quot; target=&quot;_blank&quot;&gt;цикла while&lt;/a&gt;. Так как &lt;strong&gt;цикл while&lt;/strong&gt; продолжается до тех пор, пока не будет просмотрен (&lt;code&gt;seen&lt;/code&gt;) каждый узел, сейчас операция &lt;code&gt;O(n)&lt;/code&gt; совершается &lt;code&gt;n&lt;/code&gt; раз. Наш алгоритм — &lt;code&gt;O(n2)&lt;/code&gt;. Это не очень хорошо, но и это не все.&lt;/p&gt;
  &lt;p&gt;Если взглянуть на реализацию матрицы смежности в Graph, можно заметить, что для нахождения связи нам пришлось осмотреть весь ряд (размера n). Это еще одна операция O(n) в цикле while. Как это исправить? Во-первых, можно использовать &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%9A%D1%83%D1%87%D0%B0_(%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85)&quot; target=&quot;_blank&quot;&gt;кучу&lt;/a&gt; для получения минимального предварительного расстояния в &lt;code&gt;O(lg(n))&lt;/code&gt; времени вместо &lt;code&gt;O(n)&lt;/code&gt; времени (при бинарной куче — отметьте, что куча Фибоначчи способна на это в &lt;code&gt;O(1)&lt;/code&gt;). Во-вторых, можно реализовать граф при помощи списка смежности, где у каждого узла есть список связанных узлов. Получается, что осматривать все узлы на наличие существующей связи не потребуется. Это показывает, почему важно понимать то, как мы представляем структуры данных. В случае реализаци кучи при помощи представления матрицы смежности асимптомическое время выполнения запуска алгоритма изменено не будет. (На заметку: Если вам не знакома нотация big-O, можете просмотреть &lt;a href=&quot;https://medium.com/cantors-paradise/basics-of-big-o-sorting-94d0c04d0f53&quot; target=&quot;_blank&quot;&gt;данную статью&lt;/a&gt;).&lt;/p&gt;
  &lt;h3&gt;Кучи в Python&lt;/h3&gt;
  &lt;p&gt;Такая структура данных, как куча, обычно используется для &lt;a href=&quot;https://python-scripts.com/queues&quot; target=&quot;_blank&quot;&gt;реализации очереди&lt;/a&gt; с приоритетом. По сути, они эффективны во время ситуаций, когда нужно быстро получить элемент с наивысшим приоритетом. В нашем случае элемент с высоким приоритетом — это наименьшее предварительное расстояние (&lt;code&gt;provisional distance&lt;/code&gt;) от оставшихся нерассмотренных узлов. Вместо того, чтобы каждый раз осматривать весь массив в поисках наименьшего предварительного расстояния (&lt;code&gt;provisional distance&lt;/code&gt;), можно использовать находящуюся там кучу, что готова передать узел с наименьшим предварительным расстоянием (&lt;code&gt;provisional distance&lt;/code&gt;). Как только расстояние будет извлечено из кучи, она быстро перестоится, будучи готовой к передаче нового значения в тот момент, когда оно будет нужно. Довольно неплохо! Для практики можете попробовать реализовать очень быструю &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8%D0%B5%D0%B2%D0%B0_%D0%BA%D1%83%D1%87%D0%B0&quot; target=&quot;_blank&quot;&gt;Фибоначчиеву кучу&lt;/a&gt;. А далее мы попробуем реализовать Binary MinHeap, что поможет разрешить наши задачи.&lt;/p&gt;
  &lt;p&gt;По сути бинарная куча является &lt;em&gt;полным бинарным деревом&lt;/em&gt;, что обладает &lt;em&gt;свойством кучи&lt;/em&gt;. Что это значит?&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Полное бинарное дерево&lt;/strong&gt; — это структура в виде дерева данных, где у КАЖДОГО родителя узла есть ровно два дочерних узла. Если дочерних узлов недостаточно, чтобы в последнем ряду родительских узлов было по 2 дочерних узла, дочерние узлы будут заполняться слева направо.&lt;/p&gt;
  &lt;p&gt;Как это будет выглядеть?&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://python-scripts.com/wp-content/uploads/2019/11/%D0%9F%D0%BE%D0%BB%D0%BD%D0%BE%D0%B5-%D0%B1%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D0%BE%D0%B5-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE.jpg&quot; width=&quot;750&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;&lt;strong&gt;Свойство кучи&lt;/strong&gt; (для минимальной кучи) Каждый родитель ДОЛЖЕН быть меньше или равен обоим дочерним элементам.&lt;/p&gt;
  &lt;p&gt;Применив данный принцип к указанному выше полному бинарному дереву, мы получим следующий результат:&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://python-scripts.com/wp-content/uploads/2019/11/%D0%A6%D0%B5%D0%BB%D0%BE%D0%B5-%D0%B1%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D0%BE%D0%B5-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE.jpg&quot; width=&quot;750&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Здесь представлен базовый массив &lt;code&gt;[2, 5, 4, 7, 9, 13, 18]&lt;/code&gt;. Как видите, сортировка сделана наполовину, но в данном случае нам и не нужна полная сортировка. Через минуту данный базовый массив станет намного понятнее.&lt;/p&gt;
  &lt;p&gt;Теперь у нас есть представление о том, что из себя представлят куча. Напишем код и посмотрим, как используются дополнительные методы для реализации требуемых пользователю действий.&lt;/p&gt;
  &lt;p&gt;Так как бинарная куча является специальной реализацией бинарного дерева, начнем с &lt;a href=&quot;https://python-scripts.com/object-oriented-programming-in-python#class&quot; target=&quot;_blank&quot;&gt;создания класса&lt;/a&gt; &lt;code&gt;BinaryTree&lt;/code&gt;, у которого будет наследовать рассматриваемая куча. Для реализации бинарного дерева нужно создать базовую структуру данных в виде массива, после чего структура дерева будет рассчитана через индексы узлов внутри массива. К примеру, у начального бинарного дерева (первое изображение &lt;em&gt;полного бинарного дерева&lt;/em&gt;) базовый массив будет &lt;code&gt;[5, 7, 18, 2, 9, 13, 4]&lt;/code&gt;. Мы обозначим связи между узлами при определении индекса узла в базовом массиве. Известно, что у каждого родителя ровно два дочерних элемента. Нулевой индекс является корнем в основе, индекс 1 — левый ребенок, а индекс 2 — правый ребенок. Если обобщить, левый ребенок индекса &lt;code&gt;i&lt;/code&gt; будет обладать индексом &lt;code&gt;2*i + 1&lt;/code&gt;, а правый — &lt;code&gt;2*i + 2&lt;/code&gt;. У узла индекса i родительский индекс — &lt;code&gt;floor((i-1) / 2)&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;Итак, класс &lt;code&gt;BinaryTree&lt;/code&gt; будет выглядеть следующим образом:&lt;/p&gt;
  &lt;pre&gt;class BinaryTree:
 
    def __init__(self, nodes = []):
        self.nodes = nodes
 
    def root(self):
        return self.nodes[0]
    
    def iparent(self, i):
        return (i - 1) // 2
    
    def ileft(self, i):
        return 2*i + 1
 
    def iright(self, i):
        return 2*i + 2
 
    def left(self, i):
        return self.node_at_index(self.ileft(i))
    
    def right(self, i):
        return self.node_at_index(self.iright(i))
 
    def parent(self, i):
        return self.node_at_index(self.iparent(i))
 
    def node_at_index(self, i):
        return self.nodes[i]
 
    def size(self):
        return len(self.nodes)
&lt;/pre&gt;
  &lt;p&gt;Теперь &lt;code&gt;MinHeap&lt;/code&gt; может наследовать &lt;code&gt;BinaryTree&lt;/code&gt; и задействовать требуемую функциональность. Теперь &lt;code&gt;BinaryTree&lt;/code&gt; можно будет вновь использовать в иных контекстах.&lt;/p&gt;
  &lt;p&gt;Окей, мы практически у финала. Сейчас нужно будет идентифицировать способности класса, который должны быть у класса &lt;code&gt;MinHeap&lt;/code&gt;, а затем реализовать их. Необходимо, чтобы куча могла:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Измениться, вместо неупорядученного бинарного дерева став минимальной кучей. Это делается через конкретизацию кучи;&lt;/li&gt;
    &lt;li&gt;Получите минимальное значение, а затем реструктурироваться для сохранения своих свойств. Это будет использоваться при посещении следующего узла;&lt;/li&gt;
    &lt;li&gt;Обновить (уменьшить) значение узла, сохранив свойство кучи. Это будет использоваться при обновлении предварительных расстояний.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Для осуществления данных условий начнем с создания блока, который сыграет важную роль при реализации первых двух &lt;a href=&quot;https://python-scripts.com/functions-python&quot; target=&quot;_blank&quot;&gt;функций&lt;/a&gt;. Напишем метод под названием &lt;code&gt;min_heapify_subtree&lt;/code&gt;. Данный метод будет рассматривать всю кучу как &lt;code&gt;heapified&lt;/code&gt; (то есть при этом поддерживая свойство кучи), кроме единственного узла 3-го поддерева. Мы будем рекурсивно накапливать это поддерево, идентифицируя его индекс родительского узла в точке &lt;code&gt;i&lt;/code&gt; и давая возможность правильно размещенному узлу найти верную позицию в куче. Для этого проверяем, меньше ли дочерние узлы, чем родительский. Если да, заменяем наименьший дочерний узел с родительским. Затем рекурсивно вызываем метод по индексу замененного родителя (который теперь является дочерним), чтобы убедиться, что он помещен в позицию для поддержки свойства кучи. Вот псевдокод:&lt;/p&gt;
  &lt;pre&gt;FUNCTION min_heapify_subtree(i)
  index_of_min = i
  IF left_child exists AND left_child &amp;lt; node[index_of_min]
    index_of_min = left_child_index
  ENDIF
  IF right_child exists AND right_child &amp;lt; node[index_of_min]
    index_of_min = right_child_index
  ENDIF 
  IF index_of_min != i
    swap(nodes[i], nodes[index_of_min])
    min_heapify_subtree(index_of_min)
  ENDIF
END
&lt;/pre&gt;
  &lt;p&gt;В худшем случае метод начинается с индекса 0 и рекурсивно распространит корневой узел вплоть до нижнего листа. Поскольку здесь куча является &lt;strong&gt;бинарным деревом&lt;/strong&gt;, у нас есть уровни &lt;code&gt;lg(n)&lt;/code&gt;, где &lt;code&gt;n&lt;/code&gt; — общее количество узлов. Поскольку каждая рекурсия метода выполняет фиксированное количество операций, то есть &lt;code&gt;O(1)&lt;/code&gt;, классификация времени выполнения &lt;code&gt;min_heapify_subtree&lt;/code&gt; — &lt;code&gt;O(lg (n))&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;Для превращения совершенно случайного массива в правильную кучу, просто нужно вызвать &lt;code&gt;min_heapify_subtree&lt;/code&gt; на каждом узле, начиная с нижних листьев. Задействуем метод &lt;code&gt;min_heapify&lt;/code&gt;:&lt;/p&gt;
  &lt;pre&gt;FUNCTION min_heapify
  FOR index from last_index to first_index
    min_heapify_subtree(index)
  ENDFOR
END
&lt;/pre&gt;
  &lt;p&gt;Данный метод выполняет метод &lt;code&gt;O(lg(n))&lt;/code&gt; n раз, поэтому его время выполнения будет &lt;code&gt;O(n*lg(n))&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;Нам нужно извлечь минимальное значение из кучи. Значение можно принять время за &lt;code&gt;O(1)&lt;/code&gt;, потому что оно всегда будет корневым узлом минимальной кучи (т.е. индекс 0 базового массива), но просто прочитать недостаточно. Требуется удаление, А ТАКЖЕ подтверждение того, что куча остается нагроможденной. Для этого удаляем корневой узел и заменяем его последним листом, а затем &lt;code&gt;min_heapify_subtree&lt;/code&gt; индексом 0, чтобы обеспечить сохранение свойства кучи:&lt;/p&gt;
  &lt;pre&gt;FUNCTION pop
  min_node = nodes[0]
  IF heap_size &amp;gt; 1 
    nodes[0] = nodes[nodes.length - 1]
    nodes.pop() // remove the last element from our nodes array
    min_heapify_subtree(0)
  ELSE IF heap_size == 1
    nodes.pop()
  ELSE
    return NIL // the heap is empty, so return NIL value
  ENDIF
  return min_node
END
&lt;/pre&gt;
  &lt;p&gt;Метод выполняется за постоянное время, за исключением &lt;code&gt;min_heapify_subtree&lt;/code&gt;. Можно сказать, что этот метод также &lt;code&gt;O(lg (n))&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;Отлично! На последнем этапе нужно будет добиться получения возможности обновления значний кучи (уменьшать их, поскольку обновляются предварительные расстояния (&lt;code&gt;provisional distance&lt;/code&gt;) до более низких значений), сохраняя свойство кучи.&lt;/p&gt;
  &lt;p&gt;Итак, создадим метод под названием &lt;code&gt;decrease_key&lt;/code&gt;, который принимает значение индекса обновляемого узла и новое значение. Мы хотим обновить значение этого узла, а затем добавить его туда, где он должен будет находиться, если станет меньше родителя. Таким образом, пока он не станет меньше родительского узла, поменяем его на родительский узел:&lt;/p&gt;
  &lt;pre&gt;FUNCTION decrease_key(index, value)
  nodes[index] = value
  parent_node_index = parent_index(index)
  WHILE index != 0 AND nodes[parent_node_index] &amp;gt; nodes[index]
    swap(nodes[i], nodes[parent_node_index])
    index = parent_node_index
    parent_node_index = parent_index(index)
  ENDWHILE
END
&lt;/pre&gt;
  &lt;p&gt;Окей, посмотрим, как все вышеперечисленное будет выглядеть в виде кода на Python:&lt;/p&gt;
  &lt;pre&gt;class MinHeap(BinaryTree):
 
    def __init__(self, nodes):
        BinaryTree.__init__(self, nodes)
        self.min_heapify()
 
    # Преобразует в кучу узлы, рассматривая, что все поддеревья уже кучи
    def min_heapify_subtree(self, i):
        size = self.size()
        ileft = self.ileft(i)
        iright = self.iright(i)
        imin = i
        if( ileft &amp;lt; size and self.nodes[ileft] &amp;lt; self.nodes[imin]):
            imin = ileft
        if( iright &amp;lt; size and self.nodes[iright] &amp;lt; self.nodes[imin]):
            imin = iright
        if( imin != i):
            self.nodes[i], self.nodes[imin] = self.nodes[imin], self.nodes[i]
            self.min_heapify_subtree(imin)
 
 
    # Преобразует в кучу массив, который ей не является
    def min_heapify(self):
        for i in range(len(self.nodes), -1, -1):
            self.min_heapify_subtree(i)
 
    def min(self):
        return self.nodes[0]
 
    def pop(self):
        min = self.nodes[0]
        if self.size() &amp;gt; 1:
            self.nodes[0] = self.nodes[-1]
            self.nodes.pop()
            self.min_heapify_subtree(0)
        elif self.size() == 1: 
            self.nodes.pop()
        else:
            return None
        return min
 
    # Обновляет значение узла, изменяя его для сохранения свойства кучи
    def decrease_key(self, i, val):
        self.nodes[i] = val
        iparent = self.iparent(i)
        while( i != 0 and self.nodes[iparent] &amp;gt; self.nodes[i]):
            self.nodes[iparent], self.nodes[i] = self.nodes[i], self.nodes[iparent]
            i = iparent
            iparent = self.iparent(i) if i &amp;gt; 0 else None
&lt;/pre&gt;
  &lt;p&gt;Хорошо, время для последнего шага. Теперь уже точно! Нам просто нужно выяснить, как внедрить структуру данных &lt;code&gt;MinHeap&lt;/code&gt; в метод &lt;code&gt;dijsktra&lt;/code&gt; в &lt;code&gt;Graph&lt;/code&gt;, который теперь должен быть реализован с помощью списка смежности. Нужно реализовать его, полностью используя преимущества среды выполнения кучи, при этом поддерживая класс &lt;code&gt;MinHeap&lt;/code&gt; настолько гибким, насколько это возможно. Это нужно для повторного использования в будущем.&lt;/p&gt;
  &lt;p&gt;Итак, сначала разберемся с реализацией списка смежности. Вместо матрицы, представляющей соединения между узлами, требуется, чтобы каждый узел соответствовал списку узлов, к которым он подключен. Таким образом, если мы выполняем итерацию по соединениям узла, нам не нужно проверять ВСЕ узлы, чтобы увидеть, какие из них подключены — в списке этого узла находятся только подключенные узлы.&lt;/p&gt;
  &lt;p&gt;Уже знакомый нам граф:&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://python-scripts.com/wp-content/uploads/2019/11/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC-%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B-%D0%B3%D1%80%D0%B0%D1%84.jpg&quot; width=&quot;750&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Список смежности графа будет выглядеть приблизительно следующим образом:&lt;/p&gt;
  &lt;pre&gt;[ 
  [ NodeA, [(NodeB, 5), (NodeC, 10), (NodeE, 2)] ],
  [ NodeB, [(NodeA, 5), (NodeC, 2), (NodeD, 4)] ],
  [ NodeC, [(NodeA, 10), (NodeB, 2), (NodeD, 7), (NodeF, 10)] ],
  [ NodeD, [(NodeB, 4), (NodeC, 7), (NodeE, 3)] ],
  [ NodeE, [(NodeA, 2), (NodeD, 3)] ],
  [ NodeF, [(NodeC, 10)]
]
Как видите, для получения определенной связи узлов нам больше не нужно проверять ВСЕ прочие узлы. Оставим API относительно простым, ради ясности оставим класс простым:
class Graph: 
 
    def __init__(self, nodes):
        self.adj_list = [ [node, []] for node in nodes ]
        for i in range(len(nodes)):
            nodes[i].index = i
 
 
    def connect_dir(self, node1, node2, weight = 1):
        node1, node2 = self.get_index_from_node(node1), self.get_index_from_node(node2)
        # Обратите внимание, что указанное ниже не защищает от добавления связи дважды
        self.adj_list[node1][1].append((node2, weight))
 
    def connect(self, node1, node2, weight = 1):
        self.connect_dir(node1, node2, weight)
        self.connect_dir(node2, node1, weight)
 
    
    def connections(self, node):
        node = self.get_index_from_node(node)
        return self.adj_list[node][1]
    
    def get_index_from_node(self, node):
        if not isinstance(node, Node) and not isinstance(node, int):
            raise ValueError(&amp;quot;node must be an integer or a Node object&amp;quot;)
        if isinstance(node, int):
            return node
        else:
            return node.index
&lt;/pre&gt;
  &lt;p&gt;Далее сосредоточимся на реализации кучи для достижения лучшего алгоритма, чем текущий алгоритм &lt;code&gt;O(n²)&lt;/code&gt;. Если вспомним метод &lt;code&gt;dijsktra&lt;/code&gt;, в классе &lt;code&gt;Graph&lt;/code&gt; матрицы смежности, то увидим, что мы перебираем всю очередь, чтобы найти минимальное предварительное расстояние &lt;code&gt;provisional distance&lt;/code&gt; (время выполнения &lt;code&gt;O(n)&lt;/code&gt;), используя узел с минимальным значением для установки текущего посещаемого узла. Затем перебираем все соединения узла и при необходимости сбрасываем их предварительное расстояние &lt;code&gt;provisional distance&lt;/code&gt; (проверьте метод &lt;code&gt;connections_to&lt;/code&gt; или &lt;code&gt;connections_from&lt;/code&gt;; вы увидите, что его время выполнения &lt;code&gt;O(n)&lt;/code&gt;). Эти два алгоритма &lt;code&gt;O(n)&lt;/code&gt; сводятся к времени выполнения &lt;code&gt;O(n)&lt;/code&gt;, потому что &lt;code&gt;O(2n) = O (n)&lt;/code&gt;. Делаем это для каждого узла графа, поэтому выполняем &lt;code&gt;O(n)&lt;/code&gt; алгоритм &lt;code&gt;n&lt;/code&gt; раз, тем самым получая время выполнения &lt;code&gt;O(n²)&lt;/code&gt;. Вместо этого сокращаем время выполнения до &lt;code&gt;O((n + e) lg (n))&lt;/code&gt;, где &lt;code&gt;n&lt;/code&gt; — количество узлов, а &lt;code&gt;e&lt;/code&gt; — количество ребер.&lt;/p&gt;
  &lt;p&gt;В реализации списка смежности внешнему &lt;strong&gt;циклу while&lt;/strong&gt; все еще нужно выполнять итерацию по всем узлам (&lt;code&gt;n&lt;/code&gt; итераций), но чтобы получить ребра для текущего узла внутренний цикл просто должен итерировать ТОЛЬКО по ребрам для данного конкретного узла. Таким образом, внутренний цикл, повторяющийся по краям узла, будет выполняться &lt;em&gt;только&lt;/em&gt; &lt;code&gt;O(n + e)&lt;/code&gt; раз. Внутри данного внутреннего &lt;strong&gt;цикла&lt;/strong&gt; нужно обновить предварительное расстояние для каждого из связанных узлов. Для этого будет использован метод &lt;code&gt;decrease_key&lt;/code&gt; кучи, который мы уже рассматривали, как &lt;code&gt;O(lg (n))&lt;/code&gt;. Таким образом, общее время выполнения будет равно &lt;code&gt;O((n + e) lg (n))&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Новый алгоритм:&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;ИНИЦИАЛИЗАЦИЯ&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Установите &lt;code&gt;provisional_distance&lt;/code&gt; всех узлов от источника до бесконечности, за исключением нашего исходного узла, значением которого будет &lt;code&gt;0&lt;/code&gt;. Добавьте данные к минимальной куче.&lt;/li&gt;
    &lt;li&gt;Вместо сохранения набора &lt;code&gt;seen_nodes&lt;/code&gt; определите, есть ли у вас осмотренные узлы. Это будет зависеть от того, что осталось в куче. Помните, что при использовании &lt;code&gt;pop()&lt;/code&gt; для узла, он удаляется из кучи. Следовательно, в соответствии с логикой он будет рассматриваться, как «увиденный».&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;ПРОЦЕДУРА ИТЕРАЦИИ&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Цикл &lt;strong&gt;while&lt;/strong&gt; выполняется до тех пор, пока куча &amp;gt; 0: (выполняется &lt;code&gt;n&lt;/code&gt; раз)&lt;/li&gt;
    &lt;li&gt;Установите &lt;code&gt;current_node&lt;/code&gt; для возвращения значения &lt;code&gt;heap.pop()&lt;/code&gt;.&lt;/li&gt;
    &lt;li&gt;Для &lt;code&gt;n&lt;/code&gt; в &lt;code&gt;current_node.connections&lt;/code&gt; используйте &lt;code&gt;heap.decrease_key&lt;/code&gt;, если данная связь все еще в куче (то есть не была рассмотрена), А ТАКЖЕ если текущее значение &lt;code&gt;provisional distance&lt;/code&gt; больше, чем предварительное расстояние &lt;code&gt;current_node&lt;/code&gt; вместе с весом соседнего элемента. Данный &lt;a href=&quot;https://python-scripts.com/loops-for-while#for&quot; target=&quot;_blank&quot;&gt;цикл for&lt;/a&gt; в общем будет выполняться &lt;code&gt;n + e&lt;/code&gt; раз, а его временную сложность можно представить, как &lt;code&gt;O(lg(n))&lt;/code&gt;.&lt;/li&gt;
    &lt;li&gt;Конец цикла &lt;strong&gt;while&lt;/strong&gt;.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;Есть две задачи, которые нужно решить при данной реализации:&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Задача 1&lt;/strong&gt;: Код написан таким образом, что куча работает с массивом чисел. Однако нам нужна инкапсуляция предварительного расстояния &lt;code&gt;provisional distance&lt;/code&gt; (метрики, которая использовалась при преобразовании в кучу), перескоков И узла, которому соответствует расстояние.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Задача 2&lt;/strong&gt;: Нужно проверить, находится ли узел внутри кучи, А ТАКЖЕ обновить его &lt;code&gt;provisional distance&lt;/code&gt;, используя метод &lt;code&gt;decrease_key&lt;/code&gt;, для которого требуется индекс данного узла в куче. Для сохранения своих свойств куча постоянно меняет индексы. Нужно убедиться, что данная задача не будет решена простым анализом кучи в поисках точного места узла. Данная операция &lt;code&gt;O(n)&lt;/code&gt; будет выполняться &lt;code&gt;(n + e)&lt;/code&gt; раз, следовательно, создание кучи и реализация списка смежности ни к чему не приведут, ведь нам нужно со всем управиться за &lt;code&gt;O(1)&lt;/code&gt; раз.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Решение 1&lt;/strong&gt;: Нам нужно сохранить реализацию кучи максимально гибкой. Чтобы принимать любой тип данных в качестве элементов в базовом массиве, можно просто принять необязательные анонимные функции (например, &lt;a href=&quot;https://python-scripts.com/lambda&quot; target=&quot;_blank&quot;&gt;лямбда-выражения)&lt;/a&gt; при создании экземпляра, которые предоставляются пользователем, чтобы указать, как он должен обращаться с элементами внутри массива, если эти элементы не являются простым целым числом. Значением по умолчанию для лямбд может быть функция, которая работает, если элементы массива являются просто числами. Таким образом, если требуется простая куча с числами, пользователю не нужно вставлять лямбды. Данные настраиваемые процедуры потребуются для сравнения элементов, а также для возможности уменьшить значение элемента. Можно назвать лямбду сравнения &lt;code&gt;is_less_than&lt;/code&gt;, и ее значение по умолчанию должно быть: &lt;code&gt;a, b: a &amp;lt; b&lt;/code&gt;. Лямбда-функция для возврата обновленного узла с новым значением может называться &lt;code&gt;update_node&lt;/code&gt;, и ее значение по умолчанию должно быть просто &lt;code&gt;lambda node, newval: newval&lt;/code&gt;. При передаче узла и нового значения пользователю дается возможность определить лямбду, которая обновляет существующий объект ИЛИ заменяет значение, которое там находится. В контексте реализации старого &lt;code&gt;Graph&lt;/code&gt;, поскольку узлы имели бы значения &lt;code&gt;[ provisional_distance, [nodes, in, hop, path]]&lt;/code&gt; лямбда &lt;code&gt;is_less_than&lt;/code&gt; могла бы выглядеть следующим образом: l&lt;code&gt;ambda a,b: a[0] &amp;lt; b[0]&lt;/code&gt;. Здесь можно было бы оставить значение второй лямбды по умолчанию и передать вложенный массив в &lt;code&gt;decrease_key&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;Тем не менее, вскоре станет ясно, что существует довольно простое решение вопроса, при котором создаются настраиваемые объекты узлы &lt;code&gt;node&lt;/code&gt; и передаются в &lt;code&gt;MinHeap&lt;/code&gt;. Гибкость, которая недавно упоминалась, позволит легко воплотить это элегантное решение. В частности, в приведенном ниже коде вы увидите, что лямбда &lt;code&gt;is_less_than&lt;/code&gt; превратиться в: &lt;code&gt;lambda a, b: a.prov_dist &amp;lt; b.prov_dist&lt;/code&gt;, а лямбда &lt;code&gt;update_node&lt;/code&gt; станет: &lt;code&gt;lambda node, data: node.update_data (data)&lt;/code&gt;, что намного аккуратнее использования вложенных массивы.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Решение 2&lt;/strong&gt;: Есть несколько способов решить данную задачу, но давайте попробуем выбрать тот, который идет рука об руку с Решением 1. Учитывая гибкость, которой мы добились в Решении 1, мы можем продолжать использовать эту стратегию для реализации дополняющего решения и здесь. Можно реализовать дополнительный массив внутри класса &lt;code&gt;MinHeap&lt;/code&gt;, который отображает исходный порядок вставленных узлов в их текущий порядок внутри массива узлов. Назовем этот список &lt;code&gt;order_mapping&lt;/code&gt;. Поддерживая этот список, мы можем получить любой узел из кучи за время &lt;code&gt;O(1)&lt;/code&gt;, учитывая, что мы знаем исходный порядок, в котором узел был вставлен в кучу. Таким образом, если порядок узлов, в котором создаётся куча, совпадает с номером индекса узлов &lt;code&gt;Graph&lt;/code&gt;, теперь у нас есть отображение узла &lt;code&gt;Graph&lt;/code&gt; до относительного местоположения этого узла в &lt;code&gt;MinHeap&lt;/code&gt; в постоянном времени! Чтобы иметь возможность поддерживать это отображение в актуальном состоянии за время &lt;code&gt;O(1)&lt;/code&gt;, все элементы, передаваемые в MinHeap как узлы, должны каким-то образом «знать» свой исходный индекс, а &lt;code&gt;MinHeap&lt;/code&gt; необходимо знать, как считывать исходный индекс с этих узлов. Это требуется для того чтобы он мог обновить значение &lt;code&gt;order_mapping&lt;/code&gt; по номеру индекса свойства индекса узла index до значения текущей позиции этого узла в списке узлов &lt;code&gt;MinHeap&lt;/code&gt;. Поскольку нам нужно разрешить использование &lt;code&gt;MinHeap&lt;/code&gt;, тем, кто не нуждается в этом отображении, А ТАКЖЕ нам нужно, чтобы любые типы данных были узлами кучи, мы можем снова разрешить добавление лямбда-выражения пользователем. Оно сообщит &lt;code&gt;MinHeap&lt;/code&gt;, как получить номер индекса из любого типа данных, что добавляются в кучу — его названием будет &lt;code&gt;get_index&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;Например, если бы данные для каждого элемента в куче были списком структуры &lt;code&gt;[data, index]&lt;/code&gt;, лямбда &lt;code&gt;get_index&lt;/code&gt; была бы: &lt;code&gt;lambda el: el [1]&lt;/code&gt;. Кроме того, мы можем установить для &lt;code&gt;get_index&lt;/code&gt; значение по умолчанию &lt;code&gt;None&lt;/code&gt;, и доверить ему принятие решений, поддерживается или нет массив &lt;code&gt;order_mapping&lt;/code&gt;. Таким образом, если пользователь не вводит лямбду, чтобы сообщить куче, как получить индекс из элемента, куча не будет отслеживать &lt;code&gt;order_mapping&lt;/code&gt;, что позволяет пользователю использовать кучу без этой функциональности только с базовыми типами данных, такими как целые числа. Лямбда &lt;code&gt;get_index&lt;/code&gt;, что будет в итоге использоваться, так как будет задействован пользовательский объект узла, будет очень простой: &lt;code&gt;lambda node: node.index ()&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;Результатом совмещения решений 1 и 2 является одно чистое решение. Создается класс &lt;code&gt;DijkstraNodeDecorator&lt;/code&gt; для декорирования всех узлов, составляющих граф. Этот декоратор предоставит дополнительные данные о &lt;code&gt;provisional distance&lt;/code&gt; (инициализированном в бесконечность) и &lt;code&gt;hoop list&lt;/code&gt; (инициализированном в пустой массив). Кроме того, он будет реализован с помощью метода, который позволит объекту обновляться самому. Потом это можно использовать в лямбда-выражении для &lt;code&gt;decrease_key&lt;/code&gt;. &lt;code&gt;DijkstraNodeDecorator&lt;/code&gt; сможет получить доступ к индексу декорируемого узла, и мы воспользуемся этим фактом, указав куче, как получить индекс узла при помощи лямбды &lt;code&gt;get_index&lt;/code&gt; из Решения 2.&lt;/p&gt;
  &lt;h3&gt;Финальный код алгоритма Дейкстры Python:&lt;/h3&gt;
  &lt;pre&gt;class DijkstraNodeDecorator:
    
    def __init__(self, node):
        self.node = node
        self.prov_dist = float(&amp;#x27;inf&amp;#x27;)
        self.hops = []
 
    def index(self):
        return self.node.index
 
    def data(self):
        return self.node.data
    
    def update_data(self, data):
        self.prov_dist = data[&amp;#x27;prov_dist&amp;#x27;]
        self.hops = data[&amp;#x27;hops&amp;#x27;]
        return self
&lt;/pre&gt;
  &lt;p&gt;Использоваля для декорирования узлов в реализации Graph ниже.&lt;/p&gt;
  &lt;pre&gt;class MinHeap(BinaryTree):
 
    def __init__(self, nodes, is_less_than = lambda a,b: a &amp;lt; b, get_index = None, update_node = lambda node, newval: newval):
        BinaryTree.__init__(self, nodes)
        self.order_mapping = list(range(len(nodes)))
        self.is_less_than, self.get_index, self.update_node = is_less_than, get_index, update_node
        self.min_heapify()
 
    # Изменение в кучу узлов, предполагается, что все поддеревья уже кучи
    def min_heapify_subtree(self, i):
 
        size = self.size()
        ileft = self.ileft(i)
        iright = self.iright(i)
        imin = i
        if( ileft &amp;lt; size and self.is_less_than(self.nodes[ileft], self.nodes[imin])):
            imin = ileft
        if( iright &amp;lt; size and self.is_less_than(self.nodes[iright], self.nodes[imin])):
            imin = iright
        if( imin != i):
            self.nodes[i], self.nodes[imin] = self.nodes[imin], self.nodes[i]
            # Если есть лямбда для получения абсолютного индекса узла
            # обновляет массив order_mapping для указания, где находится индекс
            # в массиве узлов (осмотр для этого индекса будет 0(1))
            if self.get_index is not None:
                self.order_mapping[self.get_index(self.nodes[imin])] = imin
                self.order_mapping[self.get_index(self.nodes[i])] = i
            self.min_heapify_subtree(imin)
 
 
    # Превращает в кучу массив, который еще ей не является
    def min_heapify(self):
        for i in range(len(self.nodes), -1, -1):
            self.min_heapify_subtree(i)
 
    def min(self):
        return self.nodes[0]
 
    def pop(self):
        min = self.nodes[0]
        if self.size() &amp;gt; 1:
            self.nodes[0] = self.nodes[-1]
            self.nodes.pop()
            # Обновляет order_mapping, если можно
            if self.get_index is not None:
                self.order_mapping[self.get_index(self.nodes[0])] = 0
            self.min_heapify_subtree(0)
        elif self.size() == 1: 
            self.nodes.pop()
        else:
            return None
        # Если self.get_index существует, обновляет self.order_mapping для указания, что
        # узел индекса больше не в куче
        if self.get_index is not None:
            # Устанавливает значение None для self.order_mapping для обозначения непринадлежности к куче 
            self.order_mapping[self.get_index(min)] = None
        return min
 
    # Обновляет значение узла и подстраивает его, если нужно, чтобы сохранить свойства кучи
    def decrease_key(self, i, val):
        self.nodes[i] = self.update_node(self.nodes[i], val)
        iparent = self.iparent(i)
        while( i != 0 and not self.is_less_than(self.nodes[iparent], self.nodes[i])):
            self.nodes[iparent], self.nodes[i] = self.nodes[i], self.nodes[iparent]
            # Если есть лямбда для получения индекса узла 
            # обновляет массив order_mapping для указания, где именно находится индекс
            # в массиве узлов (осмотр этого индекса O(1))
            if self.get_index is not None:
                self.order_mapping[self.get_index(self.nodes[iparent])] = iparent
                self.order_mapping[self.get_index(self.nodes[i])] = i
            i = iparent
            iparent = self.iparent(i) if i &amp;gt; 0 else None
 
    def index_of_node_at(self, i):
        return self.get_index(self.nodes[i])
&lt;/pre&gt;
  &lt;p&gt;&lt;code&gt;MinHeap&lt;/code&gt; модифицирован с лямбдой, что позволяет любому типу данных использоваться в виде узла.&lt;/p&gt;
  &lt;pre&gt;class Graph: 
 
 
    def __init__(self, nodes):
        self.adj_list = [ [node, []] for node in nodes ]
        for i in range(len(nodes)):
            nodes[i].index = i
 
 
    def connect_dir(self, node1, node2, weight = 1):
        node1, node2 = self.get_index_from_node(node1), self.get_index_from_node(node2)
        # Отмечает, что нижеуказанное не предотвращает от добавления связи дважды
        self.adj_list[node1][1].append((node2, weight))
 
    def connect(self, node1, node2, weight = 1):
        self.connect_dir(node1, node2, weight)
        self.connect_dir(node2, node1, weight)
 
    
    def connections(self, node):
        node = self.get_index_from_node(node)
        return self.adj_list[node][1]
    
    def get_index_from_node(self, node):
        if not isinstance(node, Node) and not isinstance(node, int):
            raise ValueError(&amp;quot;node must be an integer or a Node object&amp;quot;)
        if isinstance(node, int):
            return node
        else:
            return node.index
 
    def dijkstra(self, src):
        
        src_index = self.get_index_from_node(src)
        # Указывает узлы к DijkstraNodeDecorators
        # Это инициализирует все предварительные расстояния до бесконечности
        dnodes = [ DijkstraNodeDecorator(node_edges[0]) for node_edges in self.adj_list ]
        # Устанавливает предварительное расстояние исходного узла до 0 и его массив перескоков к его узлу
        dnodes[src_index].prov_dist = 0
        dnodes[src_index].hops.append(dnodes[src_index].node)
        # Устанавливает все методы настройки кучи
        is_less_than = lambda a, b: a.prov_dist &amp;lt; b.prov_dist
        get_index = lambda node: node.index()
        update_node = lambda node, data: node.update_data(data)
 
        # Подтверждает работу кучи с DijkstraNodeDecorators с узлами
        heap = MinHeap(dnodes, is_less_than, get_index, update_node)
 
        min_dist_list = []
 
        while heap.size() &amp;gt; 0:
            # Получает узел кучи, что еще не просматривался (&amp;#x27;seen&amp;#x27;)
            # и находится на минимальном расстоянии от исходного узла
            min_decorated_node = heap.pop()
            min_dist = min_decorated_node.prov_dist
            hops = min_decorated_node.hops
            min_dist_list.append([min_dist, hops])
            
            # Получает все следующие перескоки. Это больше не O(n^2) операция
            connections = self.connections(min_decorated_node.node)
            # Для каждой связи обновляет ее путь и полное расстояние от 
            # исходного узла, если общее расстояние меньше, чем текущее расстояние
            # в массиве dist
            for (inode, weight) in connections: 
                node = self.adj_list[inode][0]
                heap_location = heap.order_mapping[inode]
                if(heap_location is not None):
                    tot_dist = weight + min_dist
                    if tot_dist &amp;lt; heap.nodes[heap_location].prov_dist:
                        hops_cpy = list(hops)
                        hops_cpy.append(node)
                        data = {&amp;#x27;prov_dist&amp;#x27;: tot_dist, &amp;#x27;hops&amp;#x27;: hops_cpy}
                        heap.decrease_key(heap_location, data)
 
        return min_dist_list
&lt;/pre&gt;
  &lt;pre&gt;a = Node(&amp;#x27;a&amp;#x27;)
b = Node(&amp;#x27;b&amp;#x27;)
c = Node(&amp;#x27;c&amp;#x27;)
d = Node(&amp;#x27;d&amp;#x27;)
e = Node(&amp;#x27;e&amp;#x27;)
f = Node(&amp;#x27;f&amp;#x27;)
 
g = Graph([a,b,c,d,e,f])
 
g.connect(a,b,5)
g.connect(a,c,10)
g.connect(a,e,2)
g.connect(b,c,2)
g.connect(b,d,4)
g.connect(c,d,7)
g.connect(c,f,10)
g.connect(d,e,3)
 
print([(weight, [n.data for n in node]) for (weight, node) in g.dijkstra(a)])
&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Результат вывода финального кода:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Как видите, вывод такой же, как и в прошлом варианте реализации алгоритма. Сам код выглядит теперь намного аккуратнее. Кроме того, сейчас нам удалось добиться реализации алгоритма Дейкстры за время &lt;code&gt;O((n + e)lg(n))&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

</content></entry></feed>