Ruby ma oczywiście rekurencję, podobnie jak każdy inny język programowania wysokiego poziomu. Działa to dobrze, tak długo, jak głębokość rekurencji nie jest zbyt wysoka, ale jeśli tak, to złapiesz przepełnienie stosu:Jaki jest właściwy sposób na wdrożenie bardzo głębokiej rekursji w języku Ruby w sposób * czysty *?
#!/usr/bin/ruby2.0
def rec_naive(i)
return 1 if i==1
rec_naive(i-1) + i
end
puts rec_naive(10000) #(Stack size: ~9360)
#==> test.rb:3: stack level too deep (SystemStackError)
Najbardziej oczywiste rozwiązanie, które przychodzi do głowy to po prostu zwiększyć rozmiar stosu. Niestety, odpowiedzi na ten temat znalazłem, zmieniając stan systemu operacyjnego w taki czy inny sposób - majstrując przy użyciu kodu źródłowego tłumacza Ruby, ulimit
, kompilując flagi i tym podobne - co jest czystym Rubinem i, oczywiście, nie zawsze jest możliwe, szczególnie w bezpiecznym środowisku. Tak, mniej oczywistych rozwiązań przybywających do umysłu są przepisać funkcję przestępstwa w non-rekurencyjne sposób lub reimplement stos numerem:
# Recursion-free way
def rec_norecurse(i)
acc = 0
(1..i).each do |n|
acc += n
end
return acc
end
puts rec_norecurse(100)
# Reimplementing the call stack
StackFrame = Struct.new(:state, :args)
def rec_customstack(stack)
lastresult = nil
until stack.empty?
frame = stack.last
state, args = frame.state, frame.args
i = args[0]
case state
when :entrance_point
if i==1
#-- return 1 #--
lastresult = 1
stack.pop
#---------------
else
#-- rec(i-1) #--
stack.last.state = :returned_from_recursion
stack << StackFrame.new(:entrance_point, [i-1])
#---------------
end
when :returned_from_recursion
#-- return rec_result+i #--
lastresult = lastresult + i
stack.pop
#--------------------------
end
end
return lastresult
end
customstack = [StackFrame.new(:entrance_point, [100])]
puts rec_customstack(customstack)
jednak przepisanie nawet niezbyt złożonych funkcji w ten sposób może być żmudne zadanie , a wynikowy kod wydaje się być zbyt zagracony i zasłonięty w porównaniu do oryginału. Chciałem zaimplementować jakieś metaprogramowanie i napisać coś w rodzaju "opakowania", które mogłoby sprawić, że funkcja zawijana zachowa się właściwie z głęboką rekurencją, a jednocześnie będzie wystarczająco czysta, nawet jeśli nie będzie wyglądała dokładnie tak, jak nieopakowana. Zaimplementowałem rozwiązanie z Fibers, które początkowo wydawało się wystarczająco dobre, ale wtedy napotkałem pewne niespodziewane trudności (szczegóły: see the related question).
Poszukuję prawa i czystych - z najmniejszą ilością zakłóceń i zaciemnień, jak to tylko możliwe - sposób realizacji bardzo głębokich wywołań rekurencyjnych bez zbytniego obciążania wydajności.
Najszybszym i najczystszym sposobem jest '(1..n) .sum' dla Ruby 2.4 lub po prostu' n * (n + 1)/2'. Dlaczego w ogóle potrzebujesz rekursji? Dlaczego powinien być tak głęboki? –
@EricDuminil: * Ta funkcja * w szczególności jest tylko przykładem ilustrującym głęboką rekurencję. Zrobił by czynnik, numery fibbonacci ... no wiesz, każdy z tych klasycznych prostych przykładów, które są zwykle używane do demonstrowania zasad rekursji.W rzeczywistości jest przydatny do BFS, DFS, przeszukiwania drzewa i wykresów oraz wielu innych zadań, ale nie chciałem zaśmiecać kodu niepotrzebnymi szczegółami, ponieważ kod demonstracyjny powinien wyjaśnić problem tak szybko i wyraźnie jak to tylko możliwe. Stąd to. :) – DeFazer
Factorial eksplodowałby na długo przed osiągnięciem SystemStackError. Fibbonacci: Powinieneś używać buforowania. Czy macie konkretny, konkretny przykład, gdzie byłoby to potrzebne? –