from mistralai import Mistral
import pyperclip
import random

questions = {1: {'theory': 'Большие данные (Big Data) — это объёмные и сложные наборы данных, которые традиционные методы обработки не могут эффективно анализировать, хранить и обрабатывать. Три основных свойства больших данных — это разнообразие, высокая скорость поступления и большой объем.\nПричины возникновения задач обработки больших данных\n1. Увеличение объёма данных: С развитием технологий, особенно интернета, социальных сетей и IoT (интернета вещей), объём данных растёт экспоненциально.\n2. Разнообразие данных: Данные поступают из различных источников (тексты, изображения, видео, сенсорные данные), что создает необходимость в их интеграции и анализе.\n3. Скорость генерации данных: Данные генерируются в реальном времени, что требует мгновенной обработки и анализа.\n4. Требования бизнеса: Компании стремятся лучше понять своих клиентов и рынок, что ведет к необходимости анализа больших объёмов данных для выявления паттернов и трендов.\n5. Развитие технологий: Появление мощных вычислительных ресурсов и технологий хранения данных (например, Hadoop, Spark) позволяет обрабатывать большие данные более эффективно.\n6. Необходимость анализа: Взаимодействие данных может предоставлять важные инсайты, которые можно использовать для оптимизации процессов, повышения конкурентоспособности и принятия обоснованных решений.'},
 2: {'theory': 'Современное аппаратное обеспечение для больших данных\nМногоядерные процессоры и GPU с технологиями для параллельных вычислений (CUDA и ROCm).\nПамять и хранение: Высокоскоростная память (DDR5, HBM), SSD, распределенные файловые системы (HDFS, S3).\nРаспределенные системы: Кластеры, контейнеризация (Docker, Kubernetes).\nПроблемы масштабируемости параллельных вычислений\nЗакон Амдала: Последовательные части кода снижают эффективность масштабирования.\nЗакон Густавара: Стоимость и сложность коммуникации между узлами системы возрастают быстрее, чем увеличивается их производительность.\nОграничения дискового ввода-вывода, сетевые задержки, неравномерное распределение задач (часть узлов простаивает)\nВероятность сбоев растет с увеличением масштабов.\nРешения\nСокращение последовательных операций.\nИспользование высокоскоростных сетей (InfiniBand, 5G).\nИспользование платформ для работ с БД (Spark, Hadoop)\nИспользование моделей для распределенной работы с БД: MapReduce, DAG.\nНеобходимо параллельно (иронично) развивать производительность оборудования, оптимизировать алгоритмы и улучшать эффективность ПО.'},
 3: {'theory': 'Малые объемы данных (до сотен МБ)\nХарактеристики: Данные легко помещаются в оперативную память.\nИнструменты:\nPython: библиотеки pandas, numpy.\nSQL: для работы с реляционными базами данных.\nПринципы:\nРабота в памяти.\nБыстрая итерация и обработка.\nСредние объемы данных (от сотен МБ до десятков ГБ)\nХарактеристики: Данные могут не помещаться в память, но легко обрабатываются на одном компьютере.\nИнструменты:\nPython: dask, modin (распараллеливание операций pandas).\nApache Arrow и Parquet: для хранения данных в колоночном формате.\nSQL: работа с базами данных.\nПринципы:\nИспользование потоковой обработки.\nУменьшение объема данных через фильтрацию на ранних этапах.\nБольшие объемы данных (от десятков ГБ до сотен ТБ)\nХарактеристики: Данные обрабатываются распределенными системами.\nИнструменты:\nHadoop, Spark (Scala, Python API pyspark).\nApache Flink для потоковой обработки.\nGoogle BigQuery, Amazon Redshift (облачные решения).\nПринципы:\nРаспределенная обработка данных.\nПартиционирование данных.\nСохранение результатов на диск после каждой стадии.\nОчень большие объемы данных (петабайты и выше)\nХарактеристики: Требуется масштабируемая распределенная обработка.\nИнструменты:\nApache Spark, Dask на кластерных вычислительных системах.\nMapReduce.\nОблачные сервисы: AWS S3, Azure Synapse, Google Cloud Storage.\nПринципы:\nМинимизация пересылок данных между узлами.\nКомпьютинг "ближе" к данным.\nВстроенная функция map() позволяет применить функцию к каждому элементу последовательности\nФункция имеет следующий формат: mар(<Функция>, <Последовательность1>[, ... ,\n<ПоследовательностьN>])\nФункция возвращает объект, nоддерживающий итерацию, а не сnисок.\nФункция filter() nозволяет выnолнить nроверку элементов nоследовательности.\nФормат функции: filtеr(<Функция>, <Последовательность>)\nЕсли в nервом nараметре вместо названия функции указать значение None , то каждый элемент nоследонательности будет nроверен на соответствие булевскому значению True.\nЕсли элемент в логическом контексте возвращает значение False, то он не будет добавлен в возвращаемый результат.\nФункция возвращает объект, nоддерживающий итерацию, а не сnисок.\nФункция reduce()\nfunctools.reduce(funct, iterable[, initializer])\nВычисляет функцию от двух элементов последовательно для элементов последовательности слева направо таким образом, что результатом вычисления становится единственное значение, которое становится первым аргументом для следующей итерации применения funct'},
 4: {'theory': 'Общая память:\nХарактеристики: Все процессоры совместно используют одну память с единым адресным пространством. Виды: UMA (равный доступ ко всем участкам памяти) и NUMA (время доступа зависит от расположения памяти).\nПреимущества: Удобство программирования, быстрая передача данных между процессорами.\nНедостатки: Ограниченная масштабируемость, необходимость синхронизации, проблемы когерентности кэшей.\nРаспределенная память:\nХарактеристики: Каждый процессор использует собственную память, обмен данными осуществляется через сеть.\nПреимущества: Высокая масштабируемость, рост объема памяти пропорционально числу процессоров.\nНедостатки: Высокая сложность программирования (использование передачи сообщений), низкая скорость и высокая латентность обмена данными.'},
 5: {'theory': 'Подходы к декомпозиции задач на параллелизуемые подзадачи:\nФункциональная декомпозиция (Task/Functional decomposition)\nРаспределение вычислений по подзадачам\nДекомпозиция по данным\nРаспределение данных по подзадачам\nВысокая масштабируемость (многие тысячи ядер)\nВозможность использовать недорогие массовые компоненты (CPU, RAM, сети)\nГеометрическая декомпозиция\nДанные задачи разбиваются на области (желательно равного размера) по "геометрическому" принципу (например n-мерная решетка с регулярным шагом)\nС каждой областью данных ассоциируется свой обработчик, обычно применяющий стандартный алгоритм обработки и при необходимости, обменивающийся данными с обработчиками, работающими с соседними областями.\nРекурсивный параллелизм\nОперации Split и Merge могут стать узким местом т. к. выполняются последовательно.\nЗадания порождаются динамически (балансировка загрузки потоков)\nСтепень параллелизма изменяется в ходе выполнения алгоритма'},
 6: {'theory': '1. Модель "задача/канал"\nКаждая задача выполняется независимо.\nВзаимодействие осуществляется через специализированные механизмы обмена (каналы).\nКаналы определяют порядок передачи данных, избегая прямого обращения задач к памяти друг друга.\nХорошо подходит для архитектур с распределенной памятью, так как требует минимальной синхронизации между задачами.\n2. Модель передачи сообщений\nКаждая вычислительная единица (процесс или поток) имеет локальную память.\nСообщения передаются явно, что требует явного указания получателя и отправителя.\nЧасто применяется в распределенных системах.\nНаиболее эффективна в кластерах и суперкомпьютерах с сетью передачи данных, где важна изоляция памяти.\n3. Модель разделяемой общей памяти\nОбщая область памяти доступна всем вычислительным единицам.\nСинхронизация достигается через семафоры, мьютексы, блокировки или атомарные операции.\nОсновное внимание уделяется контролю доступа к разделяемым данным.\nОриентирована на системы с общей памятью (многопроцессорные или многоядерные системы).\n4. Модели, использующие параллелизм данных\nВся вычислительная система работает над одной операцией, но на разных данных (подход SIMD — Single Instruction, Multiple Data).\nИспользуется в обработке массивов, матриц и других крупных наборов данных.\nЭффективны на GPU и векторных процессорах, которые оптимизированы для массовых параллельных вычислений.'},
 7: {'theory': 'Профилирование — сбор характеристик работы программы, таких как:\nвремя выполнения отдельных фрагментов (например, функций)\nчисло верно предсказанных условных переходов\nчисло кэш-промахов\nобъем используемой оперативной памяти\nи т. д.\nИнструмент, используемый для анализа работы, называют профайлером (profiler). Обычно профилирование выполняется в процессе оптимизации программы.\n%time измеряет время выполнения кода один раз.\n%timeit многократно исполняет код и оценивает среднее время.\n%time sum(range(1000000))\n%timeit sum(range(1000000))\n%prun анализирует функции, подсчитывает вызовы и измеряет время выполнения.\n%lprun выполняет построчный анализ (требуется line_profiler).\ndef compute():\ntotal = 0\nfor i in range(10000):\ntotal += i\nreturn total\n%prun compute()\n%memit оценивает использование памяти.\n%mprun анализирует память построчно (требуется memory_profiler).\n%memit [x**2 for x in range(100000)]\nПринципы оптимизации\nСокращение времени: Используйте профилирование для замены медленных операций более быстрыми, например, списки на массивы NumPy.\nОптимизация памяти: Профилируйте потребление памяти, чтобы устранить утечки.\nСложность алгоритмов: Улучшайте алгоритмы, снижая их временную и пространственную сложность.\nimport numpy as np\n%timeit [x**2 for x in range(100000)] # Медленно\n%timeit np.arange(100000)**2 # Быстрее\nПрофилирование помогает точно определить слабые места кода, а оптимизация улучшает производительность, что особенно важно для вычислительно сложных задач.'},
 8: {'theory': 'референс: https://habr.com/ru/companies/otus/articles/769448/\nGIL, сокращение от Global Interpreter Lock, представляет собой важную концепцию в Python. Он представляет собой мьютекс, который блокирует доступ к объекту Python interpreter в многопоточных средах, разрешая выполнять лишь одну инструкцию за раз. Это означает, что в многозадачной среде Python, в один и тот же момент времени только один поток может активно выполнять Python-код. Этот механизм, хоть и заботится о безопасности и целостности данных, одновременно становится камнем преткновения для тех, кто стремится максимально задействовать многозадачность и использовать полностью потенциал многоядерных процессоров.\nПричины существования GIL\nБезопасность памяти: GIL гарантирует, что внутренняя структура данных Python (например, подсчет ссылок) не будет повреждена при многопоточном доступе.\nУпрощение реализации: Поддержка GIL упрощает разработку интерпретатора CPython.\nИсторическое наследие: GIL был создан в 1990-х, когда одноядерные процессоры доминировали.\nПроблемы, вызванные GIL\nОграничение производительности: GIL блокирует одновременное выполнение потоков, из-за чего Python не использует преимущества многоядерных процессоров.\nИскусственная конкуренция: Потоки вынуждены ждать освобождения GIL, что приводит к задержкам.\nНеэффективность многопоточности: Потоки переключаются друг с другом, что создает накладные расходы без реального параллелизма.\nСпособы обхода ограничений GIL\nМодуль multiprocessing позволяет создавать независимые процессы, каждый из которых имеет свой собственный интерпретатор Python и память. Это эффективно использует многоядерные процессоры.\nРасширения Python на C или C++ могут выполнять вычисления без блокировки GIL. NumPy эффективно обрабатывает массивы данных за счет работы на низком уровне.\nДля задач ввода-вывода (например, сетевые запросы или операции с файлами) эффективнее использовать асинхронные подходы. Модуль asyncio позволяет выполнять множество операций ввода-вывода параллельно без многопоточности.\nСторонние библиотеки для параллелизма\nDask: Используется для параллельных вычислений с массивами и данными.\nJoblib: Простой способ параллелизации циклов.\nRay: Фреймворк для распределенных вычислений.'},
 9: {'theory': 'Модуль multiprocessing включен в стандартную библиотеку Python, он предоставляет возможность создания и управления процессами, что позволяет выполнять параллельные вычисления и улучшать производительность программ, особенно на многоядерных процессорах.\nОсновные возможности модуля:\nпозволяет избегать ограничения GIL для параллельных вычислений\nпозволяет избегать использования примитивов для синхронизации (модель передачи сообщений)\nвключает абстракции с интерфейсом, похожим на класс threading.Thread\nКласс multiprocessing.Pool предоставляет удобный способ управления пулом рабочих процессов. Он позволяет распределять задачи между несколькими процессами, что упрощает параллельное выполнение задач.\nОсновные методы и атрибуты multiprocessing.Pool:\napply(func, args=(), kwds={}):\nБлокирующий вызов функции func с аргументами args и kwds.\nВозвращает результат выполнения функции.\nmap(func, iterable, chunksize=None):\nПрименяет функцию func к каждому элементу итерируемого объекта iterable.\nВозвращает список результатов.\nchunksize: Размер чанков, которые будут переданы каждому рабочему процессу.\napply_async(func, args=(), kwds={}, callback=None, error_callback=None):\nАсинхронный вызов функции func с аргументами args и kwds.\nВозвращает объект AsyncResult, который можно использовать для получения результата позже.\ncallback: Функция, которая будет вызвана с результатом выполнения func.\nerror_callback: Функция, которая будет вызвана в случае ошибки.\nmap_async(func, iterable, chunksize=None, callback=None, error_callback=None):\nАсинхронная версия метода map.\nВозвращает объект AsyncResult.\nПример использования:'},
 10: {'theory': 'Главные различия между потоками и процессами:\nПроцесс — экземпляр программы во время выполнения, независимый объект, которому выделены системные ресурсы. Каждый процесс выполняется в отдельном адресном пространстве: один процесс не может получить доступ к переменным и структурам данных другого.\nПоток — определенный способ выполнения процесса. Когда один поток изменяет ресурс процесса, это изменение сразу же становится видно другим потокам этого процесса. Поток использует то же самое пространства стека, что и процесс, а множество потоков совместно используют данные своих состояний.\nОсновные планировщики в Dask:\ndask.threaded.get — планировщик, построенный на основе thread pool\ndask.multiprocessing.get — планировщик, построенный на основе process pool\ndask.get — синхронный планировщик, удобный для выполнения отладки\ndistributed.Client.get — распределенный планировщик для выполнения графов на нескольких машинах. Оформлен как внешний модуль distributed.\nРазличия в планировщиках Dask:\nЗаложенная программная основа, которая по умолчанию есть в Python (threading, multiprocessing, как примеры)\nКаждый из планировщиков подходит только под определённые архитектуры (простота замены планировщика обеспечивает легкость адаптации к различным уровням масштабирования и архитектурам).'},
 11: {'theory': 'Граф зависимостей задач\nРаспространенным подходом к параллельным вычислениям является планирование задач.\n* При этом подходе программа разбивается на большое количество задач (tasks) среднего размера (блоков последовательных вычислений, обычно представляющих собой вызов функции для некоторого набора данных).\n* Эти задачи представляются в виде вершин ориентированного графа зависимостей задач (task graph), с дугами отражающими зависимость одной задачи от данных, рассчитанных другой задачей.\nЭтот подход позволяет программисту явно определить участки кода, подлежащие распараллеливанию.\nГраф в Dask — это словарь, который сопоставляет ключи вычислениям:\nКлюч может быть строкой, байтовой строкой, целым числом, числом с плавающей запятой или их кортежем, Задача — это кортеж, в котором первый элемент является вызываемым объектом (например, функцией). Задачи представляют собой атомарные единицы работы, которые должны выполняться одним рабочим процессом.\nПринцип работы\nDask автоматически создает граф задач при использовании функций dask.delayed или других высокоуровневых API. Этот граф отражает зависимости между задачами.\nПосле создания графа, Dask может выполнить его параллельно или распределенно. Это позволяет эффективно использовать ресурсы компьютера и ускоряет вычисления.\nПример использования: Например, если у вас есть функция, которая вызывает другие функции, Dask может декомпозировать эту функцию и проанализировать граф зависимостей до самого низкого уровня. Это позволяет оптимизировать выполнение задач и распараллеливать их эффективно'},
 12: {'theory': '1. Dask Arrays:\n- Специфика: Dask Arrays представляют собой многоразмерные массивы, которые разделены на блоки и могут быть обработаны параллельно. Это позволяет выполнять операции на больших массивах, превышающих объем оперативной памяти и обработка больших множеств числовых данных.\n- Принцип выбора: Используйте Dask Arrays, когда вам нужно работать с большими многомерными массивами, и ваши операции можно распараллелить.\n2. Dask DataFrames:\n- Специфика: Dask DataFrames аналогичны Pandas DataFrames, но разбиты на части и распределены по многим узлам. Это позволяет обрабатывать большие наборы табличных данных, которые не умещаются в память.\n- Принцип выбора: Выберите Dask DataFrames, когда работаете с большими объемами табличных данных, например, при анализе данных из CSV-файлов или баз данных. Это идеальный выбор для тех, кто уже знаком с Pandas и хочет масштабировать свои решения.\n3. Dask Bags:\n- Специфика: Dask Bags представлены как коллекции неопределенного формата данных, которые могут быть обработаны с помощью функций, схожих с Python списками. Это структура данных, которая отлично подходит для неструктурированных и полуструктурированных данных.\n- Принцип выбора: Используйте Dask Bags, когда работаете с большими объемами неструктурированных данных, такими как текстовые файлы, JSON-объекты или списки. Dask Bags подходят для задач, где данные не нужно организовывать в строгие таблицы.'},
 13: {'theory': 'Dask.Array – это структура данных, которая позволяет работать с большими многомерными массивами, разбитыми на более мелкие части (чанки). Эти чанки могут быть обработаны параллельно, что позволяет эффективно использовать ресурсы многоядерных процессоров и кластеров.\nРеализация Dask Array:\nDask Array представляет собой сетку из массивов NumPy, обработку которых он организует порождая для каждой операции со всем массивом множество операций с массивами NumPy.\nМассивы NumPy могут:\nнаходится в оперативной в памяти\nнаходится в распределенной оперативной в памяти кластера (т.е. хранится на узлах кластера)\nнаходится на диске (по крайней мере часть времени вычислений).\nСпецифика реализации Dask.Array\nРазбиение на чанки\nМассив Dask.Array разбивается на чанки, которые представляют собой подмассивы оригинального массива. Размер чанков можно задать вручную или автоматически определить на основе размера данных и доступных ресурсов.\nЛенивые вычисления\nОперации над Dask.Array выполняются лениво. Это означает, что операции не выполняются сразу, а записываются в граф задач. Граф задач представляет собой набор операций, которые будут выполнены позже, когда это потребуется.\nПараллельное выполнение\nDask использует параллельное выполнение для обработки чанков. Это позволяет эффективно использовать ресурсы многоядерных процессоров и кластеров. Параллельное выполнение может быть настроено с использованием различных планировщиков, таких как dask.distributed.'},
 14: {'theory': 'Dask Array реализует подмножество интерфейса NumPy ndarray, используя алгоритмы в блочной форме\nБольшой массив разбивается на относительно небольшие блоки которые обрабатываются независимо\nЭта техника позволяет:\nоперировать массивами, большими чем оперативная память\nиспользовать все доступные ядра.\nКоординация задач, возникающих при исполнении блочной формы алгоритмов, осуществляется при помощи реализованного в Dask графа зависимостей задач\nРеализация Dask Array:\nDask Array представляет собой сетку из массивов NumPy, обработку которых он организует порождая для каждой операции со всем массивом множество операций с массивами NumPy.\nМассивы NumPy могут:\nнаходится в оперативной в памяти\nнаходится в распределенной оперативной в памяти кластера (т.е. хранится на узлах кластера)\nнаходится на диске (по крайней мере часть времени вычислений).\nDask Array поддерживает большинство интерфейсов NumPy, в частности:\nАрифметические операции и скалярные функции: +, *, exp, log, ...\nАгрегирующие функции (в т.ч. вдоль осей): sum(), mean(), std(), sum(axis=0), ...\nУмножение матриц, свёртка тензоров: tensordot\nПолучение срезов: x[:100, 500:100:-2]\nПрихотливое индексирование вдоль одной оси: x[:, [10, 1, 5]]\nРаботу с протоколами массивов __array__ и __array_ufunc__\nНекоторые операции линейной алгебры: svd, qr, solve, solve_triangular, lstsq\nВ отличии от NumPy:\nНе реализована большая часть пакета np.linalg\nНе поддерживаются операции с массивами неизвестного размера\nОперации наподобие sort , которые по своей сути сложно выполнять параллельно не поддерживаются. Зачастую, вместо таких операций предлагается альтернативная функция, дружественная к параллельному вычислению\nНе поддерживаются операции типа tolist, т.к. это очень неэффективно для больших наборов данных, тем более что, обход этих данных в циклах очень неэффективен.'},
 15: {'theory': 'При помощи dask.delayed можно распараллелить произвольный алгоритм, написанный на Python.\ndask.delayed имеет смысл применять, если работа алгоритма плохо ложится на логику, предлагаемую dask.Bag, dask.Array или dask.DataFrame.\ndask.delayed позволяет быстро превратить существующий алгоритм, имеющий потенциал распараллеливания, в параллельный. Для этого очень удобно использовать аннотации @delayed.\nИспользование dask.delayed позволяет генерировать граф зависимостей задач, который будет исполняться параллельно с помощью планировщика Dask.\nПараллельную обработку данных с помощью dask.delayed можно сочетать с использованием dask.Bag, dask.Array или dask.DataFrame за счет применения функций from_delayed to_delayed.\nПример:\nfrom time import sleep\ndef inc(x):\nsleep(1) # имитируем длительные вычисления\nreturn x + 1\ndef add(x, y):\nsleep(1) # имитируем длительные вычисления\nreturn x + y\n%%time\n# Последовательное выполнение трех операций займет 3 секунды:\nx = inc(1)\ny = inc(2)\nz = add(x, y)'},
 16: {'theory': 'dask.delayed – это декоратор, который позволяет откладывать выполнение функций и создавать граф задач для параллельного выполнения. Этот декоратор является ключевым инструментом в Dask для создания параллельных и распределенных вычислений.\n1. `pure`: если значение этого параметра установлено на `True`, то `dask` будет использовать кэш, чтобы избежать повторного вычисления функций с одними и теми же аргументами. По умолчанию этот параметр установлен на `False`.\n2. `nout`: позволяет указать количество выходных значений функции. Этот параметр может быть полезен, если функция возвращает несколько значений, и вы хотите использовать только одно из них.\n3. `key`: позволяет задать уникальный идентификатор для отложенной функции. Это может быть полезно, если вы хотите явно указать идентификатор, который будет использоваться для отслеживания выполнения функции.\n4. `priority`: позволяет задать приоритет выполнения отложенной функции. Это может быть полезно, если вы хотите убедиться, что некоторые функции будут выполнены раньше других.\nЭто только некоторые из дополнительных параметров, которые можно использовать при работе с `dask.delayed`. В зависимости от конкретной задачи, могут быть полезны и другие параметры, такие как `retries`, `retry_delay`, `max_retries`, `resources` и т.д.'},
 17: {'theory': 'dask.delayed позволяет превратить обычные функции или операции в ленивые вычисления, которые выполняются только тогда, когда результат действительно необходим. Это удобно для параллелизации задач и работы с вычислительными графами.\nЛенивые вычисления: Выражения не выполняются сразу, а добавляются в граф задач, который позже может быть выполнен.\nПараллелизация: Задачи автоматически распараллеливаются при их выполнении.\nГибкость: Подходит для работы с произвольными функциями и объектами, включая те, которые не поддерживаются напрямую Dask (например, собственные классы или сложные структуры данных).\nОбъекты dask.delayed создаются путем аннотации функций с помощью @delayed или явного вызова delayed(). Ленивые объекты поддерживают базовые операции, такие как сложение, умножение и доступ к атрибутам.\nОграничения:\nНевозможность прямого выполнения циклов и условий, требуется их оборачивать в функции.\nDask не может управлять объектами, которые зависят от сторонних ресурсов (например, открытые файлы), если они не были явно сериализованы.\nГраф задач хранится в памяти. Если граф становится слишком большим, это может вызвать проблемы с производительностью и объемом доступной памяти.'},
 18: {'theory': "Dask.DataFrame — это одна из ключевых структур данных в библиотеке Dask, предназначенная для работы с большими объемами табличных данных, превышающими объем оперативной памяти.\nСпецифика реализации:\n- Параллелизация: Dask.DataFrame разбивается на множество более мелких Pandas DataFrames, которые могут обрабатываться параллельно, что позволяет значительно ускорить вычисления.\n- Ленивая вычислительная модель: Dask использует ленивую модель вычисления, что означает, что операции не выполняются немедленно, а помещаются в очередь и выполняются только тогда, когда это действительно необходимо (например, при вызове метода .compute()).\n- Совместимость с Pandas: Dask.DataFrame предоставляет API, схожий с Pandas, поэтому пользователи, знакомые с Pandas, смогут легко переходить на Dask.\nПрименение:\nDask.DataFrame полезен в следующих случаях:\n- Когда данные слишком велики, чтобы уместиться в оперативной памяти одной машины.\n- При необходимости обработки и анализа данных из файлов (CSV, Parquet и др.) или баз данных.\n- Для выполнения операций, требующих больших ресурсов, таких как агрегация, объединение и фильтрация данных.\nПроцедура создания Dask.DataFrame:\nСоздание Dask.DataFrame можно выполнить несколькими способами:\n1. Из CSV файлов:\nimport dask.dataframe as dd\ndf = dd.read_csv('your_file.csv')\n2. Из Pandas DataFrame:\nimport dask.dataframe as dd\nimport pandas as pd\npdf = pd.DataFrame({'a': range(10), 'b': range(10, 20)})\nddf = dd.from_pandas(pdf, npartitions=2)\n3. Из других источников данных (например, Parquet):\nimport dask.dataframe as dd\nddf = dd.read_parquet('your_file.parquet')\n4. Из JSON файлов:\nimport dask.dataframe as dd\nddf = dd.read_json('your_file.json')"},
 19: {'theory': 'Ограничения Dask.DataFrame:\nПоддерживается только часть операций pandas.\nКаждая часть данных должна помещаться в оперативную память.\nМедленное перераспределение данных при использовании индексов.\nНеэффективен для небольших наборов данных.\nПользовательские функции должны быть сериализуемы (допустим, с использованием cloudpickle)\nОперации мэппинга:\nmap_partitions: Применяет функцию к каждой части данных параллельно. Быстро и эффективно.\ndf.map_partitions(custom_function, meta=(\'column\', \'type\'))\n2. apply: Применяет функцию к строкам или столбцам, медленнее, чем map_partitions\ndf["col"].apply(lambda x: x * 2, meta=(\'col\', \'int\'))\n3. Ограничения мэппинга: Требует сериализации функций, метаданных (meta) и ограничен памятью.'},
 20: {'theory': "Dask.DataFrame поддерживает операции со скользящим окном через метод .rolling(), аналогично pandas. Это позволяет вычислять статистики по скользящему окну для больших данных, обрабатываемых по частям.\nimport dask.dataframe as dd\n# Создаем Dask DataFrame\ndata = {'value': range(10)}\ndf = dd.from_pandas(pd.DataFrame(data), npartitions=2)\n# Расчет среднего в скользящем окне размером 3\nrolling_mean = df.rolling(window=3).mean().compute()\nprint(rolling_mean)\nОсобенность: Dask оптимизирует операции, распределяя вычисления по разделенным фрагментам данных."},
 21: {'theory': 'При работе с DataFrame некоторые вычисления могут проделываться более одного раза. Для большинства операций\xa0dask.dataframe\xa0сохранияет промежуточные результаты так, что они могут повторно использоваться. Но для этого эти промежуточные результаты должны рассматриваться в рамках одного процесса вычислений, запущенного вызовом\xa0compute\xa0(или его неявным аналогом).\nРассмотрим пример рассчета суммы и среднего значения для одного столбца\xa0dask.dataframe.\nДля расчета среднего значения используется суммирование по столбцу, что и составляет основную трудоемкость операции. (не случайно выполнение каждой из функций занимает примерно одно время). Использование промежуточных результатов суммирования для вычисления среднего значения могло бы существенно его ускорить. Для этого обе функции нужно рассчитать в рамках одного вызова функции\xa0compute.\nИспользование\xa0dask.compute\xa0для одновременного рассчета двух функций сократило время рассчета в 2 раза. Это произошло из-за того, что графы зависимостей задач для вычисления каждой функции были объеденины, что позволило однократно выполнять вычисления встречающиеся в каждом из графов.\nВ частности однократно выполнялись операции:\nзагрузка данных из файла (функция\xa0read_csv)\nфильтарция (df[df.amount > 0])\nчасть операций в свертках (функции\xa0sum,\xa0count)'},
 22: {'theory': "Структура и специфика\nDask.Bag — это структура данных для параллельной обработки неструктурированных данных (например, JSON, строки, списки). Реализована как разделенные партиции, каждая из которых обрабатывается независимо. Все вычисления ленивые, выполняются при вызове .compute().\nПрименение\nИспользуется для фильтрации, мэппинга и преобразования больших объемов данных, не помещающихся в память.\nПроцедура создания\nimport dask.bag as db\nimport json\n# Из списка\nbag1 = db.from_sequence([1, 2, 3, 4, 5], npartitions=2)\n# Из текстовых файлов\nbag2 = db.read_text('data/*.txt')\n# Из JSON-файлов\nbag3 = db.read_text('data/*.json').map(json.loads)\n# Пример мэппинга\nresult = bag1.map(lambda x: x * 2)\nprint(result.compute())"},
 23: {'theory': 'Принципы обработки данных через map, filter и reduce разделяют обработку данных на этапы, которые могут быть выполнены параллельно. Эти методы используются для обработки больших объемов данных.\nMap: применяет заданную функцию к каждому элементу входных данных.\nFilter: отбирает элементы, удовлетворяющие условию (фильтрует данные).\nReduce: сводит список значений к одному с помощью комбинаторной функции.\ndask.bag — это структура данных для параллельной обработки несхематизированных или полу-структурированных данных\nРазделение данных на разделы (partitions) - входные данные разбиваются на несколько разделов.\nВычислительный граф  - dask строит граф задач, где каждая операция (например, map или filter) добавляет новый слой вычислений, но вычисления происходят только при вызове .compute().\nФункции Map/Filter выполняются для каждого элемента данных независимо т. к. не требуют синхронизации\nФункция Reduce выполняется в два этапа: редукция внутри каждого раздела и слияние результатов всех разделов.'},
 24: {'theory': 'Dask.Bag предоставляет удобный API для обработки больших объемов неструктурированных данных, поддерживая функции мэппинга, фильтрации и преобразования.\nМэппинг (map): применяет функцию к каждому элементу.\nФильтрация (filter): оставляет элементы, удовлетворяющие условию.\nПреобразование (map_partitions, fold): работает с партициями или объединяет результаты.\nimport dask.bag as db\n# Создаем Dask.Bag из JSON-строк\ndata = db.from_sequence([\n{"name": "Alice", "age": 25},\n{"name": "Bob", "age": 30},\n{"name": "Charlie", "age": 35}\n])\n# Map: извлекаем имена\nnames = data.map(lambda x: x["name"])\n# Filter: выбираем людей старше 30\nfiltered = data.filter(lambda x: x["age"] > 30)\n# Преобразование: суммируем возраст\ntotal_age = data.map(lambda x: x["age"]).fold(lambda x, y: x + y)\n# Вывод результатов\nprint("Names:", names.compute())          # [\'Alice\', \'Bob\', \'Charlie\']\nprint("Filtered:", filtered.compute())    # [{\'name\': \'Charlie\', \'age\': 35}]\nprint("Total Age:", total_age.compute())  # 90\nОсобенности:\nПодходит для обработки JSON, логов или файлов.\nРаспределяет данные по партициям для масштабируемости.'},
 25: {'theory': 'Группировки и свертки. Эти функции используются для агрегирования данных или их преобразования в удобные для анализа формы.\nbag.groupby(key=None) - группирует элементы Bag по ключу.\nВозвращает Bag, где каждый элемент — это пара (ключ, список_группы). Группировка производится в каждом разделе отдельно, а затем результаты объединяются.\nbag.foldby(key, binop, combine=None) - группирует элементы по ключу (key). Применяет функцию бинарной операции (binop) внутри групп. Объединяет результаты с помощью функции combine.\nbag.reduce(binop) - Сворачивает элементы Bag в одно значение, используя бинарную операцию (binop). Далее результат объединяется с помощью bag.combine().\nОбщая концепция:\nЛокальная - сначала обработка в каждом разделе.\nГлобальная - затем объединение результатов из партиций.'},
 26: {'theory': 'Apache Hadoop — это фреймворк для распределенной обработки и хранения больших объемов данных. Он позволяет обрабатывать данные на кластерах из множества узлов, обеспечивая масштабируемость и отказоустойчивость. Основные принципы работы Apache Hadoop включают:\n1. Модель хранения и обработки данных:\n• Hadoop Distributed File System (HDFS): Это распределенная файловая система, которая разбивает файлы на блоки (обычно размером 128 МБ или 256 МБ) и хранит их на различных узлах кластера. HDFS обеспечивает высокую доступность и отказоустойчивость, так как каждый блок дублируется на нескольких узлах.\n• MapReduce: Это парадигма программирования, которая позволяет обрабатывать большие объемы данных, разбивая задачи на две стадии: Map (отображение) и Reduce (сокращение). На этапе Map данные обрабатываются параллельно на разных узлах, а на этапе Reduce результаты сводятся в итоговый вывод.\n2. Масштабируемость: Hadoop позволяет легко добавлять новые узлы в кластер без необходимости изменения существующей инфраструктуры. Это позволяет обрабатывать растущие объемы данных.\n3. Отказоустойчивость: Благодаря дублированию данных в HDFS, если один из узлов выходит из строя, система может продолжить работу, используя копии данных с других узлов.\n4. Гибкость: Hadoop поддерживает различные форматы данных (структурированные, полуструктурированные и неструктурированные), что делает его подходящим для работы с разными типами информации.'},
 27: {'theory': 'Apache Spark — это фреймворк для распределенной обработки данных, который обеспечивает высокую производительность и масштабируемость. Основные принципы его работы включают:\nРаспределенная архитектура: Spark использует мастер-агентную модель, где драйвер управляет выполнением задач на рабочих узлах (исполнителях).\nRDD (Resilient Distributed Dataset): Spark работает с устойчивыми распределенными наборами данных, которые являются неизменяемыми и могут восстанавливаться при сбое.\nЛенивая и оптимизированная обработка: Spark строит граф задач (DAG) и откладывает их выполнение до вызова действия, оптимизируя операции перед выполнением.\nОбработка данных в памяти: Spark выполняет вычисления в оперативной памяти, что ускоряет обработку по сравнению с диском.\nМодульная структура: Spark состоит из нескольких модулей, включая Spark Core, Spark SQL, Spark Streaming, MLlib и GraphX.\nПоддержка различных API: Spark предоставляет API для работы с данными на разных уровнях абстракции, включая RDD, DataFrame и Dataset.\nУправление ошибками и устойчивость: Spark восстанавливает потерянные данные и задачи, повторяя необходимые операции.\nПреимущества Spark\nВысокая производительность: Spark в 100 раз быстрее MapReduce для определенных задач.\nГибкость: Поддержка пакетной, потоковой и интерактивной обработки данных.\nУдобство: Высокоуровневые API на Python, Scala, Java, R и SQL'},
 28: {'theory': 'Faiss (Facebook AI Similarity Search) — библиотека для быстрого поиска ближайших соседей (NNS) по векторным данным. Она разработана Facebook AI Research и оптимизирована для работы с большими объемами данных на CPU и GPU.\nКак устроена:\nПоддерживает различные структуры данных для поиска, включая:\nFlat Index: Полный перебор для точного поиска.\nIVF (Inverted File Index): Кластеризация данных для ускорения поиска.\nHNSW (Hierarchical Navigable Small World): Графовая структура для эффективного поиска в больших наборах данных.\nИспользует CUDA для значительного ускорения вычислений.\nОснована на BLAS (Basic Linear Algebra Subprograms) для высокой производительности.\nСценарии использования:\nРекомендательные системы\nОбработка изображений: Поиск изображений в галереях, работа с эмбеддингами.\nИспользуется с текстовыми эмбеддингами для поиска похожих документов.\nСравнение лиц, отпечатков пальцев.\nРазделение данных в высокоразмерных пространствах.\nKiller features (ключевые фичи) Faiss:\nОптимизация для CPU/GPU.\nМасштабируемость: Эффективно обрабатывает миллиарды векторов.\nПоддерживает как точный, так и приближенный поиск.\nБесплатная и широко используемая библиотека с большим сообществом.'},
 29: {'theory': 'Redis — это база данных, размещаемая в оперативной памяти, которая хранит данные в формате «ключ — значение». Она относится к NoSQL системам и используется для хранения данных, кэширования, и как инструмент для передачи сообщений.\nСценарии использования Redis:\nКэширование: Быстрый доступ к данным, снижение нагрузки на БД.\nХранилище сессий: Сохранение данных пользователей (токены, сессии).\nОчереди сообщений: Реализация задач и потоков.\nPub/Sub: Системы уведомлений и чаты в реальном времени.\nАналитика: Подсчет просмотров, кликов (HyperLogLog, Sorted Set).\nГеолокация: Хранение координат, поиск ближайших объектов.\nОграничение запросов: Rate Limiting для API.\nСложные структуры данных: Hashes, Sets, Sorted Sets для структурированной\nimport redis\n# Подключение к Redis\nr = redis.Redis(host=\'localhost\', port=6379, decode_responses=True)\n# Установка значения с TTL\nr.set("user:123", "Alice", ex=3600)  # Данные пользователя с ID 123\n# Получение значения\nprint(r.get("user:123"))  # "Alice"\nRedis применяется в сценариях, где требуется высокая производительность и низкая задержка.'},
 2.1: {'code' : '\nimport pandas as pd\ndf = pd.read_csv(\'titanic.csv\')\n# print(df.head())\n\nprint("Было", df[\'Age\'].isna().sum())\n\n# Группировка по классу и полу, расчет среднего возраста\nage_means = df.groupby([\'Pclass\', \'Sex\'])[\'Age\'].mean()\n\n# Функция для заполнения пропусков\ndef fill_age(row):\n    if pd.isna(row[\'Age\']) and not pd.isna(row[\'Pclass\']) and not pd.isna(row[\'Sex\']):\n        return age_means[row[\'Pclass\'], row[\'Sex\']]\n    return row[\'Age\']\n\n# Применение функции к датафрейму\ndf[\'Age\'] = df.apply(fill_age, axis=1)\n\nprint("Итог", df[\'Age\'].isna().sum())\n'},
 2.2: {'code' : "\nfrom collections import Counter\nimport re\nimport pymorphy2\n\nfile_path = '/content/AKareninf (1).TXT'\nwith open(file_path, 'r', encoding='utf-8') as file:\n    text = file.read()\n\ncharacter_name = input() # Старик доёбывал, нужно чтобы он мог ввести что хочет от руки\n\n# Инициализация лемматизатора\nmorph = pymorphy2.MorphAnalyzer()\n\n# Токенизация текста\ntokens = re.findall(r'\x08[А-Яа-яЁё]+\x08', text)\n\n# Лемматизация и поиск форм имени\nname_forms = [word for word in tokens if morph.parse(word)[0].normal_form == character_name.lower()]\n\n# Подсчет частоты каждой формы\nform_frequencies = Counter(name_forms)\n\nform_frequencies_sorted = dict(sorted(form_frequencies.items(), key=lambda x: x[1], reverse=True))\nprint(form_frequencies_sorted)\n"},
 2.3: {'code' : '\nimport numpy as np\nimport pandas as pd\n\narr = np.random.randint(0, 51, size=(10, 4))\n\nprint(arr)\n\n# Преобразование массива в одномерный\narr_1d = arr.flatten()\n\n# Подсчет частоты каждого числа\ncounts = np.bincount(arr_1d)\n\n# Нахождение самого частотного числа\nmost_frequent_value = np.argmax(counts)\n\n# Нахождение расположения всех самых частотных чисел в исходном массиве\nlocations = np.where(arr == most_frequent_value)\n\nprint(f"Самое частое число: {most_frequent_value}")\nprint(f"Расположение самых частотных чисел:\n{locations}")\n\n# Преобразование массива в DataFrame Pandas\ndf = pd.DataFrame(arr)\n\n# Подсчет количества самых частотных чисел в каждой строке\nrow_counts = df.apply(lambda row: row.value_counts().get(most_frequent_value, 0), axis=1)\n\n# Нахождение 3 строк с наибольшим количеством самых частотных чисел\ntop_3_rows = row_counts.nlargest(3).index\n\n# Создание нового массива (3, 4)\nresult_arr = df.loc[top_3_rows].values\n\nprint(f"Массив (3, 4) с 3 строками, содержащими наибольшее количество самых частотных значений:\n{result_arr}")\n'},
 2.4: {'code' : '\nimport numpy as np\n\nA = np.random.randint(0, 101, (30, 4))\nnorms = np.linalg.norm(A, axis=1)\n\nA[np.argsort(norms)[-5:]]\n\narr = np.random.randint(0, 100, (30, 4))\nnorms = np.linalg.norm(arr, axis=1)\nindices = np.argsort(norms)[::-1][:5]\nresult = arr[indices]\n\nresult\n'},
 2.5: {'code' : '\nimport  sqlite3\n\n# Подключаемся к БД\ndb_path = \'/content/Chinook_Sqlite.sqlite\'\nconn = sqlite3.connect(db_path)\ncursor = conn.cursor()\n\n# Первая функция: выводим все альбомы по артисту\ndef get_albums_by_artist(artist_name):\n    query = """\n    SELECT Album.Title\n    FROM Album\n    JOIN Artist ON Album.ArtistId = Artist.ArtistId\n    WHERE Artist.Name = ?\n    """\n    cursor.execute(query, (artist_name,))\n    return cursor.fetchall()\n\n# Вторая функция: выводим количество треков в альбоме и суммарную продолжительность (в мс)\ndef get_album_details(artist_name, album_title):\n    query = """\n    SELECT COUNT(Track.TrackId) AS TrackCount, SUM(Track.Milliseconds) AS TotalDuration\n    FROM Track\n    JOIN Album ON Track.AlbumId = Album.AlbumId\n    JOIN Artist ON Album.ArtistId = Artist.ArtistId\n    WHERE Artist.Name = ? AND Album.Title = ?\n    """\n    cursor.execute(query, (artist_name, album_title))\n    return cursor.fetchone()\n\n# Пример\nartist_name_example = "AC/DC"\nalbum_title_example = "For Those About To Rock We Salute You"\n\nalbums = get_albums_by_artist(artist_name_example)\nalbum_details = get_album_details(artist_name_example, album_title_example)\n\nconn.close()\n\nalbums, album_details\n'},
 2.6: {'code' : '\nimport numpy as np\n\n# Исходный массив A\nA = np.array([\n    [8, 4, 15, 6, 9],\n    [3, 16, 13, 8, 10],\n    [0, 9, 18, 17, 5],\n    [16, 2, 6, 0, 10],\n    [18, 13, 9, 17, 8]\n])\n\n# Минимальные элементы по строкам\nrow_mins = np.min(A, axis=1).reshape(-1, 1)  # Вектор-столбец минимальных элементов строк\n\n# Минимальные элементы по столбцам\ncol_mins = np.min(A, axis=0).reshape(1, -1)  # Вектор-строка минимальных элементов столбцов\n\n# Построение массива B\nB = row_mins + col_mins\n\n# Вывод результата\nprint("Исходный массив A:")\nprint(A)\nprint("\nМассив B:")\nprint(B)\n\n'},
 2.7: {'code' : '\nimport numpy as np\n\ndef one_hot_encoding(arr):\n    unique_values = np.unique(arr)\n    n_classes = len(unique_values)\n    one_hot = np.zeros((arr.size, n_classes))\n    one_hot[np.arange(arr.size), arr - np.min(arr)] = 1\n    return one_hot\n\n# Пример использования\narr = np.array([2, 3, 2, 2, 2, 1])\nresult = one_hot_encoding(arr)\nprint(result)\n'},
 3.1: {'code' : '\nimport os\nimport zipfile\nimport re\nimport dask.bag as db\n\n# Распакуем архив\nwith zipfile.ZipFile("all_k.zip", "r") as zip_ref:\n    zip_ref.extractall("all_k")\n\n# Получаем список всех текстовых файлов\nbase_dir = "all_k"\nfile_paths = [os.path.join(root, file)\n              for root, _, files in os.walk(base_dir) for file in files if file.endswith(".txt")]\n\n# Регулярные выражения для типов предложений\nquestion_re = r"\\?"\nexclamatory_re = r"!($|[^?])"\nnarrative_re = r"(?<![а-яА-Я])\\.(?!\\w)"  # Исключает точки в сокращениях\n\n# Функция подсчета предложений в одном файле\ndef count_sentences(file_path):\n    counts = {"question": 0, "exclamatory": 0, "narrative": 0}\n    try:\n        with open(file_path, "r", encoding="utf-8", errors="replace") as file:\n            text = file.read()\n\n            # Убираем сокращения перед подсчетом повествовательных предложений\n            text = re.sub(r"\x08[а-яА-Я]+\\.(к|г|т|д|л|м|ж)\\.", "", text)\n\n            counts["question"] += len(re.findall(question_re, text))\n            counts["exclamatory"] += len(re.findall(exclamatory_re, text))\n            counts["narrative"] += len(re.findall(narrative_re, text))\n    except Exception as e:\n        print(f"Ошибка при обработке файла {file_path}: {e}")\n    return counts\n\n\n# Преобразуем пути к файлам в Dask-бэг\nbag = db.from_sequence(file_paths)\n\n# Подсчитываем предложения в каждом файле параллельно\nresult = bag.map(count_sentences).compute()\n\n# Суммируем результаты по всем файлам\nfinal_counts = {"question": 0, "exclamatory": 0, "narrative": 0}\nfor counts in result:\n    for key in counts:\n        final_counts[key] += counts[key]\n\nprint("Количество вопросительных предложений:", final_counts["question"])\nprint("Количество побудительных предложений:", final_counts["exclamatory"])\nprint("Количество повествовательных предложений:", final_counts["narrative"])\n'},
 3.2: {'code' : "\nimport math\nfrom multiprocessing import Pool\nimport time\n\n# Функция для вычисления синуса произведения чисел\ndef check_pair(pair):\n    a, b = pair\n    result = math.sin(a * b)\n    if result > 0.99999999:\n        return (a, b, result)\n    return None\n\n# Последовательное решение\ndef sequential_solution():\n    result = []\n    for i in range(1, 3001):\n        for j in range(1, 3001):\n            res = check_pair((i, j))\n            if res:\n                result.append(res)\n    return result\n\n# Параллельное решение\ndef parallel_solution():\n    with Pool(processes=2) as pool:\n        result = pool.map(check_pair, [(i, j) for i in range(1, 3001) for j in range(1, 3001)])\n    return [x for x in result if x is not None]\n\n# Измерение времени выполнения последовательного решения\nstart_time = time.time()\nsequential_result = sequential_solution()\nsequential_time = time.time() - start_time\n\n# Измерение времени выполнения параллельного решения\nstart_time = time.time()\nparallel_result = parallel_solution()\nparallel_time = time.time() - start_time\n\nprint('Время работы последовательной обработки:', sequential_time)\nprint('Время работы параллельной обработки:', parallel_time)\n\nparallel_result\n"},
 3.3: {'code' : '\nimport dask.array as da\nimport h5py\n\ndata  = h5py.File(\'/content/rnd_data.hdf5\', \'r\') # Открываем HDF5 файл\narr = data[\'data_set_1\'] # Получаем набор данных\n\n# Создаем dask array из NumPy массива, загруженного h5py\n# chunks=(1000, 1000) - для разделения на блоки\ndask_arr = da.from_array(arr, chunks=(1000, 1000))\n\n# Вычисление среднего значения по всему массиву\nmean_value = dask_arr.mean().compute()  # Вычисляем среднее\n\n# Подсчет количества строк, удовлетворяющих условию\nresult = da.sum(da.sum(dask_arr > mean_value, axis=1) > 600).compute()  # Подсчет\n\n# Вывод результата\nprint(f"Количество строк, в которых более 600 значений, превышающих среднее значение: {result}")\n'},
 3.4: {'code' : "\nimport dask\nfrom dask import delayed\n\n# Загружаем данные из файла\nfile_path = '/content/movies.txt'\nwith open(file_path, 'r', encoding='utf-8') as file:\n    movie_titles = file.readlines()\n\n# Функция для выделения слов из заголовков\ndef extract_words(title):\n    words = title.strip().split()\n    if len(words) >= 3:\n        first_word = words[0]\n        last_word = words[-1] if words[-1] != first_word else None\n        middle_words = words[1:-1]\n        return first_word, last_word, middle_words\n    elif len(words) == 2:\n        return words[0], words[-1], []\n    else:\n        return words[0], None, []\n\n# Функция для поиска двух самых длинных слов в списке\n@delayed\ndef find_longest_words(word_list, n=2):\n    return sorted(word_list, key=len, reverse=True)[:n]\n\n# Обработка данных с использованием dask.delayed\n@delayed\ndef process_group(group, titles):\n    words = []\n    for title in titles:\n        extracted = extract_words(title)\n        if group == 'first':\n            words.append(extracted[0])\n        elif group == 'last' and extracted[1]:\n            words.append(extracted[1])\n        elif group == 'middle':\n            words.extend(extracted[2])\n    return find_longest_words(words)\n\ntypes = ['first', 'last', 'middle']\ntask = delayed(lambda x: x)(movie_titles)\n\n# Поиск самых длинных слов и группировка их при помощи dask.delayed\n@delayed\ndef get_words(task, types):\n    results = []\n    for i in types:\n        results.append(process_group(i, task).compute())\n    return dask.compute(results)\n\nget_words(task, types).compute()[0]\n"},
 3.5: {'code' : '\nimport dask.dataframe as dd\n\n# Загрузка данных\n# Предполагаем, что файлы находятся по пути \'data/accounts.*.csv\'\ndf = dd.read_csv(\'data/accounts.*.csv\')\n\n# Фильтруем значения столбца \'amount\' в диапазоне [1000, 1500]\nfiltered_df = df[(df[\'amount\'] >= 1000) & (df[\'amount\'] <= 1500)]\n\n# Считаем количество таких значений для каждого \'id\'\ncount_by_id = filtered_df.groupby(\'id\').size()\n\n# Находим id с максимальным количеством\nresult = count_by_id.idxmax().compute()\nmax_count = count_by_id.max().compute()\n\n# Вывод результата\nprint(f"ID с наибольшим количеством значений в диапазоне [1000, 1500]: {result}")\nprint(f"Количество таких значений: {max_count}")\n'},
 3.6: {'code' : '\nimport os\nimport re\nimport zipfile\nfrom dask import delayed, compute\n\n# разархивация\nzip_path = "all_k.zip"\nunzip_dir = "unzipped_files"\n\nwith zipfile.ZipFile(zip_path, \'r\') as zip_ref:\n    zip_ref.extractall(unzip_dir)\n\n# подсчет строк-реплик в одном файле\ndef count_dialogs_in_file(file_path):\n    dialog_pattern = re.compile(r"^\\s*-\\s*.+")  # новая строка, пробельные символы, тире, пробельные символы, любой символ\n    count = 0\n    try:\n        with open(file_path, \'r\', encoding=\'utf-8\') as file:\n            for line in file:\n                if dialog_pattern.match(line):\n                    count += 1\n    except Exception as e:\n        print(f"Ошибка обработки {file_path}: {e}")\n    return count\n\n# список файлов\ntext_files = [\n    os.path.join(root, file)\n    for root, _, files in os.walk(unzip_dir)\n    for file in files if file.endswith(\'.txt\')\n]\n\n# распараллеливание\ndelayed_tasks = [delayed(count_dialogs_in_file)(file_path) for file_path in text_files]\ntotal_dialogs = compute(*delayed_tasks)\n\n\nprint("Общее количество реплик:", sum(total_dialogs))\n'}}

themes = '''
к этим только теория:
1. Большие данные – определение и причины возникновения задач обработки больших данных
2. Специфика современного аппаратного обеспечения для обработки больших данных и проблема масштабируемости параллельных вычислений
3. Выбор типичных средств обработки данных, адекватных различным объемам данных; принцип обработки данных на базе операций map / filter / reduce
4. Многопроцессорные архитектуры с общей и разделяемой памятью – специфика и сравнение
5. Подходы к декомпозиции крупных вычислительных задач на подзадачи для параллельного исполнения
6. Модели параллельного программирования и их сочетаемость с архитектурами параллельных вычислительных систем
7. Профилирование реализации алгоритмов на Python, принципы решения задачи оптимизации производительности алгоритма
8. Проблема Global Interpreter Lock в Python и способы обхода ее ограничений
9. Модуль multiprocessing – назначение и основные возможности, API multiprocessing.Pool
10. Различия между потоками и процессами, различие между различными планировщиками в Dask
11. Граф зависимостей задач – суть структуры данных, ее построение и использование в Dask
12. Три ключевых структуры данных Dask: их специфика и принцип выбора структуры данных при решении задач
13. Dask.Array – структура данных, специфика реализации и применения, процедура создания
14. Dask.Array – поддерживаемые операции и отличия от NumPy ndarray
15. Распараллеливание алгоритмов с помощью dask.delayed – принцип и примеры использования
16. Дополнительные параметры декоратора dask.delayed – назначение и примеры использования
17. Использование dask.delayed для объектов и операции над объектами dask.delayed, включая ограничения их использования
18. Dask.DataFrame - структура данных, специфика реализации и применения, процедура создания Dask.DataFrame
19. Ограничения использования Dask.DataFrame и операции мэппинга в Dask.DataFrame
20. Поддержка Dask.DataFrame операций работающих со скользящим окном
21. Совместное использование промежуточных результатов в Dask: принцип работы и примеры использования
22. Dask.Bag - структура данных, специфика реализации и применения, процедура создания DaskBag
23. Организация вычислений с помощью Map / Filter / Reduce : общий принцип и специфика параллельной реализации обработки данных в Dask.Bag
24. API Dask.Bag – функции мэппинга, фильтрации и преобразования
25. API Dask.Bag – функции группировки и свертки
26. Принципы работы Apache Hadoop.
27. Принципы работы Apache Spark.
28. Сценарии использования Faiss.
29. Сценарии использования Redis.
к этим только практика:
2.1 Заменить все пропущенные числовые значения
2.2 Найти частоту склонения имени
2.3 Найти самое частое число в массиве
2.4 Евклидовая норма
2.5 sqlite альбомы
2.6 Матрицы минимальный элемент строка столбец
2.7 one-hot-encoding
3.1 Виды предложений
3.2 sin > 0.9999
3.3 Строк, в которых более n значений, превышающих среднее значение
3.4 Самое длинное первое, среднее и последнее слово
3.5 csv значения между
3.6 прямая речь
'''
m_to_dict = {0 : 'theory', 1 : 'code'}
api_keys = ['uQwjntCIJ9omN9z8jLTV1VOUvYlbaDIv',
            'tgPxn1rFujtuJe1H0j4PQTSjbLthvjyO',
            'Z0XHjxUHj6QXJblxACLxDhJoQAUreqt4',
            'iOSObqKAYAliBWRglH4gpZb7JN0KF91j',
            'E42w9TME0Ykm1WMsVwdzS6DxV9q2Xhgx',
            'uQmDdzM1nrw3cbmksP1BWhRnjOKGLWi1',
            'AhRiKjuHaXm4WccJ0BDI0NRBJ1X9RxSE',
            'mtddNQsqAMbL5GNHroRNRsSOwOr8vusH',
            'tpxt5xsU7jetD1x9u9r0IiKxqwajlTXO'
            ]
model = "mistral-large-latest"



def info():
    '''
    Добавляет в буфер обмена список тем, по которым потом обращаться при помощи функции get(n, m), где n - номер темы, m = 0 => теория, m = 1 => практика
    '''
    pyperclip.copy(themes)

def info_cl():
    '''
    Создает класс, в документации которого список тем, по которым потом обращаться при помощи функции get(n, m), где n - номер темы, m = 0 => теория, m = 1 => практика
    '''
    class sol():
        __doc__ = themes
        
    return sol()

def get(n, m : int):
    '''
    Добавляет в буфер обмена ответ по теме (n - номер темы; m = 0 => теория, m = 1 => практика)
    '''
    if 0 < n < len(questions) + 1:
        if -1 < m < 2:
            pyperclip.copy(questions[n][m_to_dict[m]])
        else:
            pyperclip.copy('Неправильный выбор типа задания')
    else:
        pyperclip.copy('Неправильный выбор номера темы')


def get_cl(n, m):
    '''
    Создает объект класса, в документации (shift + tab) которого лежит ответ по теме (n - номер темы; m = 0 => теория, m = 1 => практика)
    '''
    class sol:
        def __init__(self, n, m):
            self.n = n
            self.m = m
            self.doc = questions[self.n][m_to_dict[self.m]] 

        @property
        def __doc__(self):
            return self.doc  

    return sol(n, m)

def m_get(message, n):
    '''
    message: запрос, n = 0 => теория, n = 1 => практика
    '''
    taskk = {0 : ' В твоем ответе не должно быть никакого кода, только теория', 1 : " В твоем ответе не должно быть ничего, кроме кода, решающего задачу."}
    # Выбираем случайный API ключ из списка
    api_key = random.choice(api_keys)

    # Инициализируем клиент Mistral AI
    client = Mistral(api_key=api_key)

    # Отправляем запрос
    try:
        chat_response = client.chat.complete(
            model=model,
            messages=[
                {
                    "role": "user",
                    "content": message + taskk[n],
                },
            ]
        )
        # Получаем ответ
        response_content = chat_response.choices[0].message.content
    except:
        response_content = 'неправильный выбор типа задания'

    # Копируем ответ в буфер обмена
    pyperclip.copy(response_content)

class m_get_cl:
    def __init__(self, message, n):
        '''
        message: запрос, n = 0 => теория, n = 1 => практика
        '''
        taskk = {0 : ' В твоем ответе не должно быть никакого кода, только теория', 1 : " В твоем ответе не должно быть ничего, кроме кода, решающего задачу."}
        # Выбираем случайный API ключ из списка
        api_key = random.choice(api_keys)
    
        # Инициализируем клиент Mistral AI
        client = Mistral(api_key=api_key)
    
        # Отправляем запрос
        try:
            chat_response = client.chat.complete(
                model=model,
                messages=[
                    {
                        "role": "user",
                        "content": message + taskk[n],
                    },
                ]
            )
            response_content = chat_response.choices[0].message.content
        except:
            response_content = 'неправильный выбор типа задания'
    

        self.answer = response_content

    @property
    def __doc__(self):
        return self.answer
