Хвостовая рекурсия означает, что вы должны напрямую возвращать результат рекурсивного вызова без дальнейших манипуляций.
Очевидная рекурсия в map состоит в том, чтобы вычислить функцию для одного элемента списка, а затем использовать рекурсивный вызов для обработки остальной части списка. Однако вам нужно объединить результат обработки одного элемента с результатом обработки остального списка, что требует операции после рекурсивного вызова.
Очень распространенный способ избежать этого — поместить комбинацию внутри рекурсивного вызова; вы передаете обработанный элемент в качестве аргумента и делаете его частью ответственности map
за объединение.
def map(l, f):
if l == []:
return []
else:
return map(l[1:], f, f(l[0]))
Теперь это хвостовая рекурсия! Но это тоже явно неправильно. В хвостовом рекурсивном вызове мы передаем 3 аргумента, а map принимает только два аргумента. И тогда возникает вопрос, что нам делать с 3-м значением. В базовом случае (когда список пуст) это очевидно: вернуть список, содержащий переданную информацию. В рекурсивном случае мы вычисляем новое значение, и у нас есть этот дополнительный параметр, переданный сверху, и у нас есть рекурсивный вызов. Новое значение и дополнительный параметр должны быть свернуты вместе, чтобы быть переданными в рекурсивный вызов, чтобы рекурсивный вызов мог быть хвостовым рекурсивным. Все это предполагает следующее:
def map(l, f):
return map_acc(l, f, [])
def map_acc(l, f, a):
if l == []:
return a
else:
b = a + [f(l[0])]
return map_acc(l[1:], f, b)
Что можно выразить более кратко и на языке Python, как показали другие ответы, не прибегая к отдельной вспомогательной функции. Но это показывает общий способ превращения нехвостовых рекурсивных функций в хвостовые рекурсивные функции.
В приведенном выше примере a
называется аккумулятором. Общая идея состоит в том, чтобы переместить операции, которые вы обычно выполняете после рекурсивного вызова, в следующий рекурсивный вызов, завершив работу, которую внешние вызовы выполнили "на данный момент", и передав ее в аккумулятор.
Если map
можно рассматривать как означающее «вызывать f
для каждого элемента l
и возвращать список результатов», то map_acc
можно понимать как означающее «вызывать f
для каждого элемента l
, возвращая список результатов в сочетании с a
, список уже полученных результатов".
person
Ben
schedule
08.08.2011