Лучшие практики рекурсивных функций; Кто они такие? - PullRequest
0 голосов
/ 06 октября 2009

Какие существуют другие независимые от языка способы конструирования рекурсивных функций, кроме типичных:

if (counter < 1) 
    return output;
else
   callSelf(); 

Существуют ли другие методы? Всякий раз, когда я смотрю примеры, я всегда вижу версию кода выше.

Спасибо! :)

Ответы [ 8 ]

6 голосов
/ 06 октября 2009

Вот и все.

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

Tail-recursion - это просто метод оптимизации, который компилятор / интерпретатор может использовать для повышения производительности. По сути, если ваша рекурсивная функция следует строгому формату (после рекурсивного вызова ничего не происходит, обычно это означает, что рекурсивный вызов всегда связан с return), рекурсивную функцию можно оптимизировать и переписать как цикл for.

5 голосов
/ 06 октября 2009

Какой именно у вас вопрос? Просто пробую некоторые ответы; -)

Существует много типов рекурсии:

  • "Стандартная" рекурсия

    let rec sum = function
        | [] -> 0
        | x::x' -> x + sum x'
    
  • Хвостовая рекурсия

    let rec sum acc = function
        | [] -> acc
        | x::x' -> sum (x + acc) x'
    
  • Взаимная рекурсия

     let rec f() = g()
     and g() = f()
    
  • Фиксированная точка рекурсия

    let factorial n = fix(fun rec n -> if n < 1 then 1 else n * rec (n - 1)) n
    

И список приложений рекурсии чрезвычайно богат. В функциональном программировании любой итерационный код (для циклов и т. Д.) Выражается через рекурсию!

Еще один хороший пример:

let rec sumTree = function
| End -> 0
| Node(val, right, left) = val + sumTree right + sumTree left

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

3 голосов
/ 06 октября 2009

Ну, вам нужно иметь какой-то способ узнать, когда прекратить повторение. Вот что у тебя за counter < 1, верно? Я часто удаляю / добавляю элементы в список, просматриваю деревья и выполняю математические функции во время повторения. В конечном итоге вам нужно знать, когда прекратить повторение, а когда нет, поэтому я не вижу других вариантов, которые нельзя свести к counter < 1.

function ProcessNode(node)
  //Do stuff
  while (node.Next != null)
    ProcessNode(node.Next);

function ManipulateList(list)
  //Do stuff, adding and removing items based on logic
  if (testCondition)
    return;
  else
    ManipulateList(list);
1 голос
/ 06 октября 2009

В ленивых языках программирования у вас может быть рекурсия, которая не определяет конечную точку. Результатом может быть бесконечная структура данных, но это нормально, если вы не пытаетесь использовать все это. Например, общий способ определения всего ряда Фибоначчи в Haskell заключается в следующем:

fibS = 1:1: zipWith (+) fibS (tail fibS)

Это переводится на следующий английский: ряд Фибоначчи равен 1, затем 1, за которым следует ряд, который является поэлементной суммой ряда Фибоначчи и ряда Фибоначчи без первого элемента.

Это звучит как рекурсивное определение, а не как рекурсивный вызов функции, но в Haskell нет большого различия - выше просто «нулевая функция» (та, которая не принимает аргументов).

Обычно, чтобы использовать это, вы просто использовали бы первые N элементов fibS. Фактически вы можете использовать все это (например, распечатать все), если вы довольны тем, что ваша программа работает вечно: -)

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

РЕДАКТИРОВАТЬ: Эти принципы, безусловно, могут применяться к другим языкам, если присутствует какой-то элемент «лени». Вот приведенная выше реализация fibS, портированной на Python с использованием генераторов:

def zipWith(func, iter1, iter2):
    while True:
        yield func(iter1.next(), iter2.next())

def tail(iter):
    iter.next()
    for x in iter:
        yield x

def fibS():
    yield 1
    yield 1
    for x in zipWith(lambda x,y: x + y, fibS(), tail(fibS())):
        yield x

# Test it by printing out the first n elements.
def take(n, iter):
    while n > 0:
        yield iter.next()
        n = n - 1

print list(take(10, fibS()))

Не ожидайте, что он будет таким же эффективным, как версия на Haskell! Вы можете сделать это более эффективным, используя некоторые хаки и глобальные объекты. Но обратите внимание на отсутствие явных условий расторжения.

1 голос
/ 06 октября 2009

Существует много вариантов, например:

foreach (child in parameter.GetChildren()) {
   callself(child)
}

или

switch (param.length) {
   case 1: return param[0];
   case 2: return param[0] + param[1];
   default: return callself(param.FirstHalf()) + callself(param.LastHalf());
}

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

1 голос
/ 06 октября 2009

У Google много информации о рекурсии . :)

0 голосов
/ 26 января 2011

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

Например, при работе со списками обычно используется сгиб. Многие функции, такие как конкатенация и добавление, легко описываются таким образом.

0 голосов
/ 06 октября 2009

Если вы хотите, чтобы ваша рекурсия прекратилась, вы должны поставить тест.

Но у вас могут быть разные вещи, например алгоритм Hanoi Tower (2 рекурсивных вызова):

Hanoi tower

int Hanoi( source, mid, destination, height ) {

if ( height == 1 ) { print source '->' destination }
else
   Hanoi ( source, destination, mid, height - 1 ) ; # move all upper tower from source to mid
   print source '->' destination;
   Hanoi ( mid , source, destination, height -1 ) ; # move all the upper tower from mid to destination

}
Hanoi ( "0", "1", "2", 8 );

Распечатает решение о перемещении 8 дисков от источника к месту назначения. См. http://en.wikipedia.org/wiki/Tower_of_Hanoi правила игры.

...