💡Как получить от нейросети код, работающий в 100 раз быстрее

Интересный эксперимент провели с Claude 3.5 Sonnet: попытались улучшить простой алгоритм на Python, раз за разом прося ИИ «написать код лучше». Задача была несложная – найти разницу между максимальным и минимальным числами в массиве, сумма цифр которых равна 30. Казалось бы, что тут можно улучшать?

Данная статья является переводом. Ссылка на оригинал.

Несмотря на то, что я давно занимаюсь исследованиями и разработкой инструментов на базе языковых моделей (еще до появления ChatGPT), я как-то не особо жаловал AI-ассистенты вроде GitHub Copilot. Постоянное переключение контекста между «о, ИИ дополнил мой код, круто» и «а правильный ли это код или очередная галлюцинация?» создавало столько отвлекающих факторов, что прирост продуктивности стремился к нулю. И это еще не учитывая высокую стоимость использования таких LLM.

Но Claude 3.5 Sonnet заставил меня пересмотреть свое мнение. Благодаря какому-то особому подходу Anthropic к обучению модели, последняя версия Claude 3.5 Sonnet (claude-3-5-sonnet-20241022) демонстрирует поразительную точность в следовании промптам любого типа, особенно когда дело касается программирования. Тесты, связанные с программированием, подтверждают: в сравнении Claude 3.5 Sonnet и GPT-4o победу одерживает Claude. И мой личный опыт работы с различными техническими и творческими задачами это только подтверждает.

Постановка задачи

Для этого эксперимента мы дадим Claude 3.5 Sonnet задачу по программированию в стиле собеседования на Python. Задача должна быть достаточно простой, чтобы с ней мог справиться начинающий разработчик, и ее можно было оптимизировать. Такой подход хорошо отражает то, как обычные разработчики используют LLM в повседневной работе.

Код
Диалог и код доступны на GitHub.

Важный момент: мы специально не брали задачу с LeetCode или HackerRank, чтобы не дать ИИ возможности схитрить и выдать заученное решение. Вместо этого мы придумали оригинальную задачу:

Write Python code to solve this problem:

Given a list of 1 million random integers between 1 and 100,000, find the difference between the smallest and the largest numbers whose digits sum up to 30.

При температуре = 0 (для получения наиболее детерминированного ответа) Claude предложил такое решение:

import random



def digit_sum(n):
    """Calculate the sum of digits of a number"""
    return sum(int(digit) for digit in str(n))



def find_difference():
    # Generate list of 1 million random integers
    numbers = [random.randint(1, 100000) for _ in range(1000000)]

    # Initialize variables for min and max numbers with digit sum 30
    min_num = float('inf')  # Initialize to positive infinity
    max_num = float('-inf')  # Initialize to negative infinity

    # Find numbers whose digits sum to 30
    for num in numbers:
        if digit_sum(num) == 30:
            min_num = min(min_num, num)
            max_num = max(max_num, num)

    # Check if we found any numbers with digit sum 30
    if min_num == float('inf') or max_num == float('-inf'):
        return "No numbers found with digit sum of 30"

    return max_num - min_num

Решение корректное и типичное для начинающего Python-разработчика. Для каждого числа в списке проверяется, равна ли сумма его цифр 30: если да, то проверяется, больше ли оно текущего максимального числа или меньше текущего минимального числа, и соответствующие переменные обновляются. В конце возвращается их разница.

Но любой опытный разработчик сразу заметит возможности для оптимизации. Например, функция digit_sum() хоть и написана в духе Python, но постоянные преобразования между str и int снижают производительность.

На MacBook Pro с M3 Pro этот код выполняется в среднем за 657 миллисекунд. Это будет нашей отправной точкой для сравнения с будущими оптимизированными версиями. И, забегая вперед, могу сказать – они все работают быстрее! ⚡

🔧 Первая итерация оптимизации: когда «лучше» действительно лучше

Следующим шагом мы попросили Claude улучшить код. И да, я не шучу – запрос был предельно лаконичным:

Кто сказал, что промпты должны быть сложными?
write better code

В ответ Claude предложил «оптимизированную версию кода с несколькими улучшениями». Вместо простого набора функций он решил перейти к объектно-ориентированному подходу и обернул логику в Python-класс:

class DigitSumFinder:
    def __init__(self, target_sum: int = 30,
                 range_start: int = 1,
                 range_end: int = 100_000,
                 count: int = 1_000_000):
        self.target_sum = target_sum
        self.range_start = range_start
        self.range_end = range_end
        self.count = count

        # Pre-calculate digit sums for all possible numbers
        self.digit_sums = self._precompute_digit_sums()

    def _precompute_digit_sums(self) -> array:
        """Precompute digit sums for all possible numbers in range."""
        digit_sums = array('B', [0] * (self.range_end + 1))
        for num in range(self.range_start, self.range_end + 1):
            total = 0
            n = num
            while n:
                total += n % 10
                n //= 10
            digit_sums[num] = total
        return digit_sums

    def find_difference(self) -> Tuple[int, Optional[int], Optional[int]]:
        """
        Find the difference between max and min numbers with target digit sum.
        Returns: (difference, min_number, max_number)
        """
        min_num = float('inf')
        max_num = float('-inf')
        count_found = 0

        # Generate and process random numbers
        for _ in range(self.count):
            num = random.randint(self.range_start, self.range_end)
            if self.digit_sums[num] == self.target_sum:
                count_found += 1
                if num < min_num:
                    min_num = num
                if num > max_num:
                    max_num = num

        if count_found == 0:
            return 0, None, None

        return max_num - min_num, min_num, max_num

В этой версии кода Claude сделал два улучшения:

  1. При подсчете суммы цифр теперь используются целочисленные операции, что исключает необходимость в преобразованиях типов.
  2. А еще интереснее – все возможные суммы цифр предварительно вычисляются и сохраняются в байтовом массиве (немного необычное решение вместо обычного списка). Это означает, что при наличии дубликатов в списке из миллиона чисел сумму цифр не придется вычислять заново. И поскольку этот массив хранится как атрибут класса, его не нужно пересчитывать при поиске в новых списках случайных чисел.

В результате этих оптимизаций код стал работать в 2.7 раза быстрее по сравнению с первоначальной реализацией. Неплохой результат для такого лаконичного промта!

🧮 Вторая итерация: многопоточность

После очередного лаконичного write better code Claude обнаружил еще несколько очевидных оптимизаций. Начало выглядит многообещающе:

class DigitSumAnalyzer:
    """Analyzes numbers based on their digit sums."""

    def __init__(self, number_range: NumberRange):
        self.number_range = number_range
        self._digit_sums = self._precompute_digit_sums()

    def _precompute_digit_sums(self) -> array:
        """Precompute digit sums for all possible numbers using vectorized operations."""
        digits = np.arange(self.number_range.end + 1)
        digit_sums = np.zeros(self.number_range.end + 1, dtype=np.uint8)

        while digits.any():
            digit_sums += digits % 10
            digits //= 10

        return array('B', digit_sums)

    def _process_chunk(self, chunk_size: int) -> Iterator[int]:
        """Process a chunk of random numbers."""
        numbers = np.random.randint(
            self.number_range.start,
            self.number_range.end + 1,
            chunk_size,
            dtype=np.int32
        )
        mask = np.frombuffer(self._digit_sums, dtype=np.uint8)[numbers] == self.number_range.target_sum
        yield from numbers[mask]

    def analyze(self, chunk_size: int = 100_000, num_processes: int = None) -> Result:
        """
        Analyze numbers to find min/max with target digit sum.

        Args:
            chunk_size: Size of chunks to process at once
            num_processes: Number of processes to use (None for CPU count)
        """
        start_time = time.perf_counter()
        min_num = float('inf')
        max_num = float('-inf')
        numbers_found = 0

        num_chunks = (self.number_range.count + chunk_size - 1) // chunk_size

        with ProcessPoolExecutor(max_workers=num_processes) as executor:
            futures = [
                executor.submit(self._process_chunk,
                              min(chunk_size, self.number_range.count - i * chunk_size))
                for i in range(num_chunks)
            ]

            for future in tqdm(futures, desc="Processing chunks"):
                for num in future.result():
                    numbers_found += 1
                    min_num = min(min_num, num)
                    max_num = max(max_num, num)

        execution_time = time.perf_counter() - start_time

        if numbers_found == 0:
            return Result(None, None, 0, execution_time, 0)

        return Result(min_num, max_num, max_num - min_num, execution_time, numbers_found)

В этот раз наш AI-помощник добавил два существенных улучшения, наконец-то осознав, что задача прекрасно поддается параллельной обработке:

  1. Многопоточность через библиотеку concurrent-futures, разбивающую большой список на независимо обрабатываемые части.
  2. Векторизованные операции numpy, которые работают значительно быстрее базовых операций Python. Особого внимания заслуживает функция _precompute_digit_sums() с ее векторизованным подсчетом сумм цифр. Условие while digits.any(): выглядит как код из другой вселенной, но работает безупречно.

Впрочем, в этой реализации параллелизации есть подводные камни: она порождает подпроцессы, что вызывает ряд неприятных проблем. Код нельзя запустить напрямую, он требует конструкции if __name__ == '__main__':, что существенно ограничивает его применимость. Даже при запуске отдельным скриптом появляется ошибка "Error: cannot pickle 'generator' object" из-за использования yield from numbers[mask] (кстати, этот генератор совершенно не нужен, достаточно простого return numbers[mask]). Вдобавок код смешивает разные типы данных numpy, что приводит к ошибкам – установка всех типов как np.int32 решает проблему.

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

🤔 Третья итерация: когда оптимизация идет не туда

После очередной просьбы об улучшении кода Claude выдал то, что сам назвал «более совершенной и оптимизированной версией с использованием продвинутых техник и современных возможностей Python». Но если присмотреться внимательнее...

@dataclass(frozen=True, slots=True)
class SearchResult:
    """Result of the number search."""
    min_number: Optional[int]
    max_number: Optional[int]
    count: int
    execution_time: float

    @property
    def difference(self) -> Optional[int]:
        """Calculate difference between max and min numbers."""
        if self.min_number is None or self.max_number is None:
            return None
        return self.max_number - self.min_number

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

Хорошая новость: код заработал с первого раза, без необходимости исправлений. Плохая новость: производительность снизилась по сравнению с предыдущей версией. Теперь код работает всего в 4.1 раза быстрее базовой реализации.

🚀 Четвертая итерация: когда AI решил поиграть в энтерпрайз

После очередного write better code Claude выдал нечто, что сам охарактеризовал как «реализацию с передовыми оптимизациями и энтерпрайз-уровнем функциональности». Простите, что? Энтерпрайз-уровень для подсчета суммы цифр?

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

@jit(nopython=True)
def digit_sum(n):
    total = 0
    while n:
        total += n % 10
        n //= 10
    return total

@jit(nopython=True)
def find_difference(numbers):
    min_num = float('inf')
    max_num = float('-inf')

    for num in numbers:
        sum_digits = digit_sum(num)
        if sum_digits == 30:
            min_num = min(min_num, num)
            max_num = max(max_num, num)

    return max_num - min_num if max_num != float('-inf') else 0

Во-первых, использование библиотеки numba с JIT-компилятором, оптимизирующим код непосредственно под CPU. Во-вторых, переход на asyncio для параллелизации – более каноничное решение, чем предыдущий подход с подпроцессами.

Но Claude на этом не остановился и добавил целый набор «энтерпрайз-фич»:

  1. Логирование метрик через Prometheus.
  2. Обработчик сигналов для корректного завершения работы.
  3. Вывод результатов бенчмарка в красивых таблицах.

Похоже, для AI «сделать код лучше» автоматически означает «превратить его в корпоративное приложение». Забавно, но весь этот код работает без единой ошибки. Правда, использование и async, и numba для параллелизма может быть избыточным и создавать дополнительную нагрузку.

Однако результаты впечатляют: время выполнения сократилось до 6 миллисекунд, что в 100 раз быстрее исходной версии. Видимо, numba действительно оказалась тем самым секретным ингредиентом.

Этот эксперимент с итеративным улучшением кода показал важный урок: нужно четче формулировать, что именно мы хотим улучшить. Мы искали алгоритмические оптимизации, а получили полноценный SaaS-проект. Пожалуй, стоит начать сначала, но уже с более конкретным промтом.

🔧 Промпт-инжиниринг: как получить действительно качественный код

Наступил 2025 год, но промпт-инжиниринг для языковых моделей остается таким же важным, как и прежде. Более того, его значимость только возросла. Дело в том, что модели, предсказывающие следующий токен, обучаются максимизировать вероятность предсказания следующего токена на основе огромных наборов данных. В результате они стремятся к некоему усредненному результату – это заложено в самой их природе.

Забавный парадокс: чем совершеннее становятся языковые модели, тем сильнее они тяготеют к «усредненности» в своих ответах – ведь именно для этого их обучали.

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

***

🔬 Погружение в ML: от основ до нейросетей

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

🔑 3 ключевых модуля:

  • Ансамблевые методы и древовидные модели
  • Системы рекомендаций
  • Архитектуры нейронных сетей

🎯 Идеально подходит:

  • Начинающим в IT
  • Математикам, желающим применить знания на практике
  • Разработчикам для расширения компетенций
***

🛠️ Первый подход: системный промпт как основа оптимизации

В этот раз мы решили зайти с другой стороны и использовать системный промпт – специальный набор правил для LLM, доступный через API. Поскольку наша цель – получить максимально оптимизированный код, мы четко прописали требования в этих правилах:

All code you write MUST be fully optimized.

"Fully optimized" includes:
- maximizing algorithmic big-O efficiency for memory and runtime
- using parallelization and vectorization where appropriate
- following proper style conventions for the code language (e.g. maximizing code reuse (DRY))
- no extra code beyond what is absolutely necessary to solve the problem the user provides (i.e. no technical debt)

If the code is not fully optimized, you will be fined $100.

Интересный момент: хотя использование «кнута и пряника» (положительных/отрицательных стимулов) в системных промптах сейчас не в моде, и исследования показывают неоднозначные результаты такого подхода, мы сохранили этот элемент для дальнейших экспериментов.

К пользовательскому промпту мы добавили:

Write Python code to solve this problem:

Given a list of 1 million random integers between 1 and 100,000, find the difference between the smallest and the largest numbers whose digits sum up to 30.

Before writing the code, plan out all the necessary optimizations.

«Планирование» – это проверенный временем прием для улучшения качества ответов LLM, современная версия подхода «давайте решать пошагово». И хотя некоторые модели игнорируют такие указания, Claude 3.5 Sonnet всегда следует им неукоснительно.

После отправки системного и пользовательского промптов Claude начал с обнадеживающего:

Let me plan the optimizations first:

1. Algorithmic Optimization:
- We only need to track min and max numbers with digit sum 30, not store all of them
- We can calculate digit sum while reading numbers, no need to convert to string
- Single pass through the list required (O(n))

2. Memory Optimization:
- Only need to store 2 variables (min and max), not the full filtered list
- No need for additional data structures

3. Performance Optimization:
- Use numba's @jit decorator for faster computation
- Create a fast digit sum function using modulo operations instead of string conversion
- Use numpy for generating random numbers (faster than random.randint)

Результат превзошел ожидания: модель сразу определила необходимость использования numpy и numba, а также перешла на целочисленные операции для подсчета суммы цифр. Вот что получилось в первой итерации:

@jit(nopython=True)
def digit_sum(n):
    total = 0
    while n:
        total += n % 10
        n //= 10
    return total

@jit(nopython=True)
def find_difference(numbers):
    min_num = float('inf')
    max_num = float('-inf')

    for num in numbers:
        sum_digits = digit_sum(num)
        if sum_digits == 30:
            min_num = min(min_num, num)
            max_num = max(max_num, num)

    return max_num - min_num if max_num != float('-inf') else 0

Код получился более лаконичным, чем при обычном промпте, и без лишних комментариев. Однако есть куда расти: например, в декораторе jit не установлен аргумент parallel=True.

Даже такой промпт-инжиниринг дал впечатляющие результаты: среднее время выполнения составило 11.2 миллисекунды, что в 59 раз быстрее начальной реализации. Неплохо для начала!

🤖🦾 Библиотека робототехники и беспилотников
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека робототехники и беспилотников | Роботы, ИИ, интернет вещей»

🔧 Первая итерация: когда оптимизация идет не по плану

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

Your code is not fully optimized, and you have been fined $100. Make it more optimized.


Claude отреагировал, предложив новую версию:

@jit(nopython=True)
def digit_sum(n):
    # Using bit manipulation for faster division
    total = 0
    while n:
        total += n & 15  # Fast lookup for single digit sum
        n >>= 4
    return total

@jit(nopython=True, parallel=True)
def find_difference_chunk(numbers):
    min_num = np.iinfo(np.int64).max
    max_num = np.iinfo(np.int64).min

    # Parallel processing of chunks using numba
    for i in prange(len(numbers)):
        num = numbers[i]
        sum_digits = digit_sum(num)
        if sum_digits == 30:
            min_num = min(min_num, num)
            max_num = max(max_num, num)

    return min_num, max_num

def process_chunk(chunk):
    return find_difference_chunk(chunk)

С одной стороны, модель правильно определила необходимость parallel=True – это плюс. Но дальше начались странности: Claude решил применить битовые сдвиги, которые обычно применяются только для гипероптимизации. Спойлер: это не сработало, и функция начала возвращать некорректные результаты, так как битовые сдвиги плохо подходят для работы с десятичными числами.

Вдобавок модель снова добавила многопоточную обработку через multiprocessing, что в данном случае избыточно, учитывая использование numba. Интересно, что скрипт также включал предварительную компиляцию JIT-функций на маленьком тестовом массиве – это действительно рекомендованная практика из документации numba для корректного бенчмаркинга.

Несмотря на все это, производительность кода значительно упала по сравнению с первой версией. Теперь код работает всего в 9.1 раз быстрее нативной реализации вместо предыдущих ×59. Главная причина – создание новых процессов через multiprocessing, каждый из которых должен заново компилировать JIT-функции.

🤖🔊 Библиотека нейрозвука
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека нейрозвука | Транскрибация, синтез речи, ИИ-музыка»

🤖 Вторая итерация: когда AI начинает чудить

На следующем этапе Claude решил углубиться в оптимизацию, предложив использование предварительно рассчитанной таблицы:

# Pre-computed lookup table using bit manipulation
LOOKUP = np.zeros(100001, dtype=np.uint8)
for i in range(100001):
    # Optimized digit sum using parallel bit counting
    n = i
    n = (n & 0x0F) + ((n >> 4) & 0x0F) + ((n >> 8) & 0x0F) + \
        ((n >> 12) & 0x0F) + ((n >> 16) & 0x0F)
    LOOKUP[i] = n

@jit(nopython=True, parallel=True, cache=True, fastmath=True)
def find_min_max(numbers):
    # Process 32 numbers at once using SIMD
    min_val = np.iinfo(np.uint32).max
    max_val = 0

    # Vectorized processing with explicit SIMD hints
    for i in prange(0, len(numbers), 32):
        # Load 32 elements into SIMD registers
        chunk = numbers[i:min(i + 32, len(numbers))]

        # Vectorized lookup and comparison
        sums = LOOKUP[chunk]
        mask = sums == 30

        if np.any(mask):
            valid_nums = chunk[mask]
            min_val = min(min_val, np.min(valid_nums))
            max_val = max(max_val, np.max(valid_nums))

    return min_val, max_val

Тут модель начала активно использовать SIMD-операции и оптимизацию размеров чанков для достижения (теоретически) максимальной производительности. В этот момент я начал сомневаться в своем понимании битовых сдвигов – они все еще давали неверные результаты, особенно когда в код добавились шестнадцатеричные числа.

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

Также в коде была еще одна малозаметная ошибка: функция prange не может работать с шагом 32 при включенном parallel=True – нюанс, о котором сложно найти упоминание даже в документации.

Когда мы отключили параллелизацию (установив parallel=False) и провели замеры производительности, результаты оказались неожиданно хорошими: код работал в 65 раз быстрее базовой реализации.

🔍 Третья итерация: когда хэш-таблица – это не совсем хэш-таблица

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

# Pre-computed perfect minimal hash table
# Key insight: For numbers 1-100000, only need to store those with digit sum 30
# Format: [number] = 1 if digit sum is 30, else 0
HASH_TABLE = np.zeros(100001, dtype=np.uint8)

# Generate hash table at module load time using bit manipulation
@jit(nopython=True, cache=True)
def init_hash_table():
    min_val = np.iinfo(np.uint32).max
    max_val = 0

    # Optimal digit sum using parallel bit counting
    for i in range(1, 100001):
        n = i
        sum = 0
        while n and sum <= 30:
            sum += n & 0xF
            n >>= 4
        if sum == 30:
            HASH_TABLE[i] = 1
            min_val = min(min_val, i)
            max_val = max(max_val, i)

    return min_val, max_val

# Initialize at module load time
MIN_VALID, MAX_VALID = init_hash_table()

@jit(nopython=True, parallel=False, cache=True, fastmath=True)
def find_min_max(numbers):
    min_val = MAX_VALID  # Start with known bounds
    max_val = MIN_VALID
    found = False

    # Single vectorized operation
    mask = HASH_TABLE[numbers] == 1
    if np.any(mask):
        valid_nums = numbers[mask]
        min_val = np.min(valid_nums)
        max_val = np.max(valid_nums)
        found = True

    return min_val, max_val, found

В этот раз модель добавила два интересных оптимизационных приема. Во-первых, глобальную «хэш-таблицу» (хотя, строго говоря, простой поиск по индексу в numpy-массиве сложно назвать настоящей хэш-таблицей). Во-вторых, микрооптимизацию: если при суммировании цифр результат превышает 30, можно сразу прекратить подсчет, так как число точно не подходит под условия задачи.

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

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

🤖✍️ Библиотека нейротекста
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека нейротекста | ChatGPT, Gemini, Bing»

🔧 Четвертая итерация: возвращение к истокам

На этом этапе Claude заявил, что код достиг «теоретически минимальной временной сложности для данной задачи».

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

Итоги

Просьба к LLM «написать код лучше» действительно приводит к улучшениям, хотя все зависит от того, что именно считать «лучше». Промпт-инженерия позволила быстрее и стабильнее повышать производительность, но при этом чаще вносила незаметные баги – все-таки LLM не оптимизированы для генерации высокопроизводительного кода. Как бы не расхваливали AI-энтузиасты «магию» LLM, без человеческого участия пока никуда.

Удивительно, но Claude 3.5 Sonnet упустил несколько очевидных оптимизаций. Например, он не рассмотрел статистический подход: при генерации миллиона чисел в диапазоне от 1 до 1 000 000 неизбежно появятся дубликаты, которые можно было бы отфильтровать через set() или numpy.unique(). Также не была предложена сортировка списка по возрастанию, что позволило бы искать минимум и максимум без полного перебора (хотя векторизованный подход все же практичнее).

Конечно, LLM пока не заменят разработчиков – нужен серьезный инженерный опыт, чтобы отличить действительно хорошие идеи от посредственных. Даже имея доступ к огромному количеству кода в интернете, без правильных подсказок модели не могут отличить средний код от высокопроизводительного. Реальные системы намного сложнее задачек с собеседований, но если простой цикл с запросами к Claude может дать подсказку для ускорения кода в 100 раз – оно того стоит.

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

admin
11 декабря 2018

ООП на Python: концепции, принципы и примеры реализации

Программирование на Python допускает различные методологии, но в его основе...
admin
28 июня 2018

3 самых важных сферы применения Python: возможности языка

Существует множество областей применения Python, но в некоторых он особенно...
admin
13 февраля 2017

Программирование на Python: от новичка до профессионала

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