Один из многих рекурсивных вызовов функции нашел правильный результат, но не может «сказать» другим. Есть ли лучшее решение, чем этот уродливый обходной путь?

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

Размещая операторы print в различных точках функции, я обнаружил, что проблема заключалась в том, что после рекурсивного вызова, который фактически нашел возвращенный элемент, другие, которые не нашли элемент, также возвращались, и, очевидно, позже по времени, чем тот, который нашел Это. Это означало, что окончательный результат сбрасывался на значение «неудача» из значения «успех», если только значение «успех» не было последним, с чем приходилось сталкиваться.

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

Нет никакого способа узнать, что рекурсивный вызов который (если есть) найдет элемент заранее, и как только один из них найдет его, он не сможет «общаться» с другими!

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

ПЕРВАЯ ПОПЫТКА

# ITERATIVE/RECURSIVE SEQUENCE TRAVERSER (First Attempt)
# Works on 'result1' but not on 'result2'

# Searches for 'item' in sequence (list or tuple) S, and returns a tuple
# containing the indices (in order of increasing depth) at which the item
# can be found, plus the depth in S at which 'item' was found.

# If the item is *not* found, returns a tuple containing an empty list and -1

def traverse(S, item, indices=[], atDepth=0):
    # If the sequence is empty, return the 'item not found' result
    if not S:
        return ([], -1)
    
    else:
        # For each element in the sequence (breadth-first)
        for i in range(len(S)):
            # Success condition base case:  found the item!
            if S[i] == item:
                return (indices + [i], atDepth)
            
            # Recursive step (depth-first):  enter nested sequence
            # and repeat procedure from beginning
            elif type(S[i]) in (list, tuple):
                return traverse(S[i], item, indices + [i], atDepth + 1) 
            
        # Fail condition base case:  searched the entire length
        # and depth of the sequence and didn't find the item, so
        # return the 'item not found' result
        else:
            print("We looked everywhere but didn't find " + str(item) + " in " + str(S) + ".")
            return ([], -1)


L = [0, 1, 2, [3, (4, 5, [6, 6.25, 6.5, 6.75, 7])], [[8, ()]], (([9], ), 10)]

result1 = traverse(L, 7)
result2 = traverse(L, 9)

print("-------------------------------------------")
print(result1)
print("-------------------------------------------")
print(result2)

ВТОРАЯ ПОПЫТКА

# ITERATIVE/RECURSIVE SEQUENCE TRAVERSER (Second Attempt)
# Does not work on either test case

# Searches for 'item' in sequence (list or tuple) S, and returns a tuple
# containing the indices (in order of increasing depth) at which the item
# can be found, plus the depth in S at which 'item' was found.

# If the item is *not* found, returns a tuple containing an empty list and -1

def traverse(S, item, indices=[], atDepth=0, returnValue=None):
    # If the sequence is empty, return the 'item not found' result
    if not S:
        print("Sequence S is empty.")
        return ([], -1)
    
    # ---  ATTEMPTED FIX:
    # If the item is found before the end of S is reached,
    # do not perform additional searches.  In addition to being
    # inefficient, doing extra steps would cause incorrect false
    # negatives for the item being in S.
    # ---  DOES NOT WORK:  the underlying issue is that the multiple recursive
    # calls generated at the same time can't communicate with each other,
    # so the others don't 'know' if one of them already found the item.
    elif returnValue:
        print("Inside 'elif' statement!")
        return returnValue
    
    else:
        # For each element in the sequence (breadth-first)
        for i in range(len(S)):
            # Success condition base case:  found the item!
            if S[i] == item:
                # Return the depth and index at that depth of the item
                print("---  Found item " + str(item) + " at index path " + str(indices) + " in current sequence")
                returnValue2 = (indices + [i], atDepth)
                print("---  Item " + str(item) + " is at index path " + str(returnValue2) + " in S, SHOULD RETURN")
                #return returnValue2  # THIS DIDN'T FIX THE PROBLEM
                #break                # NEITHER DID THIS
            
            # Recursive step (depth-first):  enter nested sequence
            # and repeat procedure from beginning
            elif type(S[i]) in (list, tuple):
                # CANNOT USE 'return' BEFORE RECURSIVE CALL, as it would cause any items
                # in the outer sequence which come after the first occurrence of a nested
                # sequence to be missed (i.e. the item could exist in S, but if it is
                # after the first nested sequence, it won't be found)
                traverse(S[i], item, indices + [i], atDepth + 1, returnValue)  # CAN'T USE 'returnValue2' HERE (out of scope);
                                                                               # so parameter can't be updated in 'if' condition
            
        # Fail condition base case:  searched the entire length
        # and depth of the sequence and didn't find the item, so
        # return the 'item not found' result
        else:
            print("We looked everywhere but didn't find " + str(item) + " in " + str(S) + ".")
            return ([], -1)


L = [0, 1, 2, [3, (4, 5, [6, 6.25, 6.5, 6.75, 7])], [[8, ()]], (([9], ), 10)]

result1 = traverse(L, 7)
result2 = traverse(L, 9)

print("-------------------------------------------")
print(result1)
print("-------------------------------------------")
print(result2)

ТРЕТЬЯ И ПОСЛЕДНЯЯ ПОПЫТКА -- Работает, но не идеально!

# ITERATIVE/RECURSIVE SEQUENCE TRAVERSER (Third Attempt)
# This 'kludge' is ** HIDEOUSLY UGLY **, but it works!

# Searches for 'item' in sequence (list or tuple) S, and generates a tuple
# containing the indices (in order of increasing depth) at which the item
# can be found, plus the depth in S at which 'item' was found.

# If the item is *not* found, returns nothing (implicitly None)
# The results of calling the function are obtained via external global variables.

# This 3rd version of 'traverse' is thus actually a void function,
# and relies on altering the global state instead of producing an output.


# -----  WORKAROUND:  If the result is found, have the recursive call that found it
# send it to global scope and use this global variable as the final result of calling
# the 'traverse' function.

# Initialize the global variables to the "didn't find the item" result,
# so the result will still be correct if the item actually isn't in the sequence.
globalVars = {'result1': ([], -1), 'result2': ([], -1)}


def traverse(S, item, send_output_to_var, indices=[], atDepth=0):
    # If the sequence is empty, return *without* doing anything to the global variable.
    # It is already initialized to the "didn't find item" result.
    if not S:
        return
    
    else:
        # For each element in the sequence (breadth-first)
        for i in range(len(S)):
            # Success condition base case:  found the item!
            if S[i] == item:
                # Set the global variable to the index path of 'item' in 'S'.
                globalVars[send_output_to_var] = (indices + [i], atDepth)
                # No need to keep on doing unnecessary work!
                return
            
            # Recursive step (depth-first):  enter nested sequence
            # and repeat procedure from beginning
            elif type(S[i]) in (list, tuple):
                # Don't use 'return' before the recursive call, or it will miss items
                # in the outer sequence after a nested sequence is encountered.
                traverse(S[i], item, send_output_to_var, indices + [i], atDepth + 1) 
            
        # Fail condition base case:  searched the entire length
        # and depth of the sequence and didn't find the item.
        else:
            # Return *without* setting the global variable, as it is
            # already initialized to the "didn't find item" result.
            return


L = [0, 1, 2, [3, (4, 5, [6, 6.25, 6.5, 6.75, 7])], [[8, ()]], (([9], ), 10)]

traverse(L, 7, 'result1')
traverse(L, 9, 'result2')

print("-------------------------------------------")
print(globalVars['result1'])
print("-------------------------------------------")
print(globalVars['result2'])

Мне было интересно, если я что-то упустил, и на самом деле есть способ сделать эту работу без использования внешних переменных. Наилучшим возможным решением было бы каким-то образом «закрыть» все остальные рекурсивные вызовы, как только один из них вернет успешный результат, но я не верю, что это возможно (я бы хотел ошибаться в этом!). Или, может быть, какая-то «приоритетная очередь», которая задерживает return рекурсивного вызова «успешного» случая (если он существует) до тех пор, пока после не вернутся все рекурсивные вызовы «неудачного» случая?

Я посмотрел на этот похожий вопрос: Рекурсивно найти вложенный словарь, содержащий целевой ключ и значение но хотя принятый здесь ответ https://stackoverflow.com/a/59538362/18248018 от ggorlen решил проблему OP и даже упоминает то, что кажется именно этой проблемой («сопоставленный результат неправильно передается вверх по стеку вызовов»), он предназначен для выполнения конкретной задачи и не Я не предлагаю понимания, которое я ищу в более общем случае.

Это должно быть рекурсивно? Просто интересно, допустимо ли итеративное решение

Freddy Mcloughlan 10.05.2022 06:30

@FreddyMcloughlan Итеративное решение было бы потрясающим! Я не думал о том, чтобы сделать это таким образом, и еще не пробовал такой подход, но если вы его написали, я был бы рад его увидеть!

Quack E. Duck 10.05.2022 06:34

Я вижу, как линейность итеративного подхода позволяет избежать проблемы с возвратом нескольких одновременных вызовов функций в непредсказуемое время. Было бы намного проще просто break выйти из цикла, когда будет получено правильное решение.

Quack E. Duck 10.05.2022 06:36

Так что, я думаю, это делает этот вопрос примером пресловутой «проблемы XY»? :D

Quack E. Duck 10.05.2022 06:37

Теперь, когда вы получили ответ, объясняющий, как исправить ваш код, я предлагаю вам опубликовать рабочую версию вашего кода по адресу codereview.stackexchange.com. Вы получите ответы, предлагающие множество небольших улучшений вашего кода.

Stef 10.05.2022 11:25

@Stef Спасибо за ссылку! Раньше я не писал на этом сайте, но слышал о нем и собирался как-нибудь проверить. Хотя я только что увидел, что новый ответ, опубликованный сегодня Келли Банди stackoverflow.com/a/72189848/18248018, описывает, как оптимизировать функцию вплоть до O (n), поэтому я не думаю, что ее можно будет улучшить дальше.

Quack E. Duck 10.05.2022 21:33

@FreddyMcloughlan вот итеративная версия. Поскольку вы упомянули об этом, я подумал, что было бы забавно написать его! Дизайн казался намного более сложным, хотя он делает то же самое, что и рекурсивная функция. stackoverflow.com/a/72194044/18248018 Я воспользуюсь советом Стефа и выложу его на сайт проверки кода, так как на самом деле это помедленнее, чем рекурсивная версия, поэтому я должен делать какие-то ненужные шаги где-то

Quack E. Duck 11.05.2022 02:02

@QuackE.Duck молодец, успешная реализация. Я благодарю вас за ваше подробное расследование, не теряйте любопытства, чувак :). Не все итеративное может быть рекурсивным, и наоборот. Я обнаружил, что обычно итеративный подход быстрее, чем рекурсивный подход (когда переписывается как можно ближе к дословному), но рекурсивный будет быстрее в некоторых ситуациях, подобных этой. И да, обмен код-ревью больше подходит для этих вопросов, но ответов придется ждать дольше. Но ответы будут гораздо ценнее.

Freddy Mcloughlan 11.05.2022 02:25

Это своего рода «мета» комментарий, но мне интересно, может ли быть улучшением отредактировать вопрос так, чтобы последние два абзаца до были примерами кода? Вернувшись к этому сообщению через несколько дней, я мог видеть, как кто-то, кто смотрит на него впервые, может пропустить текст в конце, где разъясняется мой фактический вопрос, и принять сообщение за «дамп кода». С другой стороны, я также не хочу «стены текста» и не хочу, чтобы примеры просто плавали в конце поста без контекста. Что думает сообщество?

Quack E. Duck 13.05.2022 23:01
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
9
96
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

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

Итак, вам нужно изменить:

return traverse(S[i], item, indices + [i], atDepth + 1) 

на что-то вроде:

t = traverse(S[i], item, indices + [i], atDepth + 1) 
if t != ([], -1):
    return t

Полный код:

def traverse(S, item, indices=[], atDepth=0):
    # If the sequence is empty, return the 'item not found' result
    if not S:
        return ([], -1)
    
    else:
        # For each element in the sequence (breadth-first)
        for i in range(len(S)):
            # Success condition base case:  found the item!
            if S[i] == item:
                return (indices + [i], atDepth)
            
            # Recursive step (depth-first):  enter nested sequence
            # and repeat procedure from beginning
            elif type(S[i]) in (list, tuple):
                t = traverse(S[i], item, indices + [i], atDepth + 1) 
                if t != ([], -1):
                    return t
            
        # Fail condition base case:  searched the entire length
        # and depth of the sequence and didn't find the item, so
        # return the 'item not found' result
        else:
            print("We looked everywhere but didn't find " + str(item) + " in " + str(S) + ".")
            return ([], -1)

Вывод для ваших двух тестовых случаев:

>>> traverse(L, 7)
([3, 1, 2, 4], 3)
>>> traverse(L, 9)
We looked everywhere but didn't find 9 in [6, 6.25, 6.5, 6.75, 7].
We looked everywhere but didn't find 9 in (4, 5, [6, 6.25, 6.5, 6.75, 7]).
We looked everywhere but didn't find 9 in [3, (4, 5, [6, 6.25, 6.5, 6.75, 7])].
We looked everywhere but didn't find 9 in [8, ()].
We looked everywhere but didn't find 9 in [[8, ()]].
([5, 0, 0, 0], 3)

Примечание, как указал @FreddyMcloughlan, atDepth — это просто длина возвращаемого списка минус 1. Таким образом, вы можете удалить этот параметр из вызова функции и просто использовать:


def traverse(S, item, indices=[]):
    # If the sequence is empty, return the 'item not found' result
    if not S:
        return ([], -1)
    
    else:
        # For each element in the sequence (breadth-first)
        for i in range(len(S)):
            # Success condition base case:  found the item!
            if S[i] == item:
                return (indices + [i], len(indices))
            
            # Recursive step (depth-first):  enter nested sequence
            # and repeat procedure from beginning
            elif type(S[i]) in (list, tuple):
                t = traverse(S[i], item, indices + [i]) 
                if t != ([], -1):
                    return t
            
        # Fail condition base case:  searched the entire length
        # and depth of the sequence and didn't find the item, so
        # return the 'item not found' result
        else:
            print("We looked everywhere but didn't find " + str(item) + " in " + str(S) + ".")
            return ([], -1)

Возможное улучшение; глубина len(lst)-1. Таким образом, вы можете удалить рекурсивный параметр и вычислить его в другом месте.

Freddy Mcloughlan 10.05.2022 06:54

@Nick Вау, я не ожидал такого простого решения! Я ценю, что вы предоставили такой подробный ответ на то, что оказалось тривиальной ошибкой. Хотя я считаю, что ваш пример кода — это первый раз, когда я встречаю «условный» рекурсивный вызов внутри функции! Кажется странным, что можно сгенерировать рекурсивный вызов, а затем выполнить дальнейшие действия с ним внутри той же функции.

Quack E. Duck 10.05.2022 06:56

@FreddyMcloughlan Спасибо за совет! На самом деле я считал, что список индексов полностью определяет все, что вам нужно знать, чтобы найти элемент, но все равно для удобства включал глубину отдельно. Конечно, если это единственное, что требует рекурсии после перезаписи функции, чтобы она была итеративной, определенно откажитесь от параметра глубины: D

Quack E. Duck 10.05.2022 06:58

@FreddyMcloughlan, ты прав, я отредактирую, чтобы подчеркнуть это.

Nick 10.05.2022 06:58

@QuackE.Duck, сам вызов не является условным, он просто возвращает значение из вызова. Вы заметите, что то же самое происходит в ответе, на который вы ссылаетесь (хотя он немного запутан использованием :=; в этом случае результат recursive_lookup возвращается только в том случае, если он не "ложный"

Nick 10.05.2022 07:05

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

Стек может быть глобальной переменной или передаваться в качестве аргумента.

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

Quack E. Duck 10.05.2022 21:45

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

Quack E. Duck 10.05.2022 21:47

У вас есть пример кода для вашего решения?

Quack E. Duck 10.05.2022 21:47

@QuackE.Duck: мне противно.

Yves Daoust 10.05.2022 22:43

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

def traverse(S, item):
    for i, element in enumerate(S):
        if element == item:
            return [i]
        if type(element) in (list, tuple):
            if indices := traverse(element, item):
                indices.insert(0, i)
                return indices

Результаты ваших двух тестов (Попробуйте онлайн!):

[3, 1, 2, 4]
[5, 0, 0, 0]

Я не передаю список, вместо этого строю список в обратном направлении только от местоположения элемента (или, если его нигде нет, то я вообще не строю список). Это намного меньше работы, хотя вставка в 0 делает его квадратичным, хотя и намного быстрее, чем копирование срезов. Если вы хотите, чтобы она была линейной, вы можете обернуть эту рекурсивную функцию в другую функцию и иметь рекурсивную функцию добавить для индексов и иметь функцию-оболочку обратный для списка, прежде чем возвращать его исходному внешнему вызывающему объекту. Функция-обертка также позволит вам превратить результат «непустой список или None» во что-то другое (например, пару с глубиной), если хотите.

Это намного элегантнее и эффективнее, чем мой оригинал, но я не думаю, что когда-либо придумал бы это самостоятельно. enumerate надо будет поискать. Этот оператор условного присваивания := выглядит так же, как синтаксис if let / if var из Swift, который используется для развертывания опций, но я не знал, что вы можете сделать это в Python. +1 за отличный ответ!

Quack E. Duck 10.05.2022 21:42

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

# Iterative-only version of the same function from the question

def traverse(S, item):
    indices = []
    sequence = S
    depth = 0

    # By definition, if the sequence is empty, you won't find any items in it.
    # Return the 'item not found' result.
    if not S:
        return ([], -1)

    else:
        # You have to start somewhere :D
        indices.append(0)

        while depth > -1:
            # Go back up one level once the end of a nested sequence is reached.
            while indices[depth] == len(sequence):
                depth -= 1
                
                # If the depth ever gets below 0, that means that we've scanned
                # the entire length of the outermost sequence and not found the
                # item.  Return the 'failed to find item' result.
                if depth == -1:
                    return ([], -1)

                # Remove the entry corresponding to index in the innermost
                # nested sequence we just exited
                indices.pop()

                # Reset 'sequence' using the outermost sequence S and the indices
                # computed so far, to get back to the sequence which contained
                # the previous one
                sequence = S
                for i in range(len(indices) - 1):
                    sequence = sequence[indices[i]]

                indices[depth] += 1
                # And return to the top of the outer 'while' loop to re-check
                # whether to go down another level
                continue
                
            # Success:  found the item at depth 'depth', in sequence 'sequence',
            # at index 'indices[depth]` inside that sequence.  Return 'indices'
            # and 'depth'.
            if sequence[indices[depth]] == item:
                return (indices, depth)
            
            # Recursion replacement:  enter the nested subsequence and increase the depth
            # as long as the nested subsequence is not empty.  If it is empty, treat it
            # as if it were a non-sequence type.
            elif (type(sequence[indices[depth]]) in (list, tuple)) and (sequence[indices[depth]]):
                sequence = sequence[indices[depth]]
                depth += 1
                indices.append(0)
                        
            # The item being compared is not a sequence type and isn't equal to 'item', so increment
            # the index without increasing the depth
            else:
                indices[depth] += 1
                
        # If all of S has been searched and item was not found,
        # return the 'failed to find item' result
        return ([], -1)
                            

L = [0, 1, 2, [3, (4, 5, [6, 6.25, 6.5, 6.75, 7]), 7.5], [[8, ()]], (([9], ), 10)]

# Demonstrating the function's use:  search for numbers from 0 to 10 in list L,
# in increments of 0.5
for i in range(21):
    outputString = "Looking for " + (str(i/2) if (i/2 % 1) else str(int(i/2))) + " in L: "
    indices, depth = traverse(L, i/2)
    if indices:
         print(outputString + "found at depth " + str(depth) + " by index path " + str(indices))
    else:
        print(outputString + "Not found.")

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

L = [0, 1, 2, [3, (4, 5, [6, 6.25, 6.5, 6.75, 7]), 7.5], [[8, ()]], (([9], ), 10)]

startTime = time.time()

for i in range(1000):
    for j in range(21):
        traverse(L, j/2)

finishedAt = time.time()
print("[WHICH VERSION] took " + str(finishedAt - startTime) + " seconds")

и получил такие результаты:

Для исходной рекурсивной функции с примененным исправлением Ника: Original recursive version took 0.14863014221191406 seconds

Для рекурсивной версии Келли: Quadratic recursive version took 0.11344289779663086 seconds

Для итеративной версии: Iterative version took 0.1890261173248291 seconds

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

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

Рекомендации: Оригинал с исправлением ошибки: см. ответ Ника здесь: https://stackoverflow.com/a/72180834/18248018

Рекурсивная версия Келли: https://stackoverflow.com/a/72189848/18248018

Итерационная версия: см. выше

Другие вопросы по теме