2012-01-04 29 views
5

Có giải pháp nào cho các lỗi Stack Overflow trong các hàm đệ quy trong Ruby không?Có cách giải quyết nào cho các lỗi "mức xếp chồng quá sâu" trong các thói quen đệ quy không?

Say, ví dụ, tôi có khối này:

def countUpTo(current, final) 
    puts current 
    return nil if current == final 
    countUpTo(current+1, final) 
end 

nếu tôi gọi countUpTo(1, 10000), tôi nhận được một lỗi: stack level too deep (SystemStackError).

Nó dường như vỡ ở 8187. Có một số chức năng mà tôi có thể gọi cho Ruby để bỏ qua kích thước của ngăn xếp, hoặc một cách để tăng kích thước ngăn xếp tối đa?

+5

Đừng làm điều này. Nếu bạn cố ý dùng lại 10.000 lần, bạn đang làm điều đó một cách khủng khiếp và lạm dụng đệ quy. – meagar

+2

Việc triển khai Ruby không nhất thiết phải thực hiện xóa cuộc gọi đuôi, vì vậy bạn đang dựa vào việc sử dụng kích thước ngăn xếp C. Một khả năng là bạn có thể viết lại hàm của mình để lặp lại. – birryree

+0

Thứ nhất, kinh nghiệm của riêng tôi với Ruby là nó không đặc biệt tốt với đệ quy, trong đó nó tạo ra các lỗi như thế này khá dễ dàng và nó chậm (er hơn bạn muốn). Ngoài ra, để có được hiệu suất tốt hơn trong lĩnh vực này, bạn cần biên dịch Ruby với một tập hợp nhất định nhất định, nhưng tôi không thấy điều này giúp ích nhiều cho lắm. Nói cách khác, hãy viết hàm của bạn theo cách khác nhau bằng cách sử dụng các phương thức Ruby thông thường như 'times',' upto' etc. @meagar trừ khi bạn biết mục đích là gì, tôi không nghĩ bạn có thể khẳng định điều đó. Tôi viết các phương pháp trong Haskell mà recurse rằng số lần không có vấn đề và nó de rigeur. – iain

Trả lời

2

Nếu bạn đang sử dụng YARV (C dựa thực hiện của Ruby 1.9), bạn có thể nói với Ruby VM để biến tối ưu hóa cuộc gọi đuôi trên:

RubyVM::InstructionSequence.compile_option = { 
    :tailcall_optimization => true, 
    :trace_instruction => false 
} 

def countUpTo(current, final) 
    puts current 
    return nil if current == final 
    countUpTo(current+1, final) 
end 

countUpTo(1, 10_000) 
+0

Như tôi đã viết ở trên, tôi thấy điều này có ít tác động, nhưng YMMV. – iain

+0

Sau khi bạn thiết lập compile_option, bạn có thể sử dụng các phương thức 'new' hoặc' compile' trên chuỗi lệnh, ví dụ: 'RubyVM :: InstructionSequence.new ('def meth_name (b, c); d = b + c; puts" # {b} + # {c} = # {d} hiện đang thực hiện # {b + 1} + # {c} ... "; meth_name (b + 1, c) kết thúc '). eval'. Một cách chung chung hơn để làm điều đó được hiển thị [ở đây] (https://gist.github.com/rogerleite/5101751). –

+0

Hoặc nếu bạn không muốn đặt tùy chọn cho mọi thứ, bạn có thể chuyển các tùy chọn thành biên dịch/mới, ví dụ: 'RubyVM :: InstructionSequence.new ('def meth_name (b, c); d = b + c; đặt" # {b} + # {c} = # {d}. Bây giờ làm # {b + 1} + # {c} ... "; meth_name (b + 1, c) kết thúc ', nil, nil, nil, tailcall_optimization: true, trace_instruction: false) .eval'. Xem [:: RubyVM :: InstructionSequence] (http://www.ruby-doc.org/core-2.0.0/RubyVM/InstructionSequence.html) để biết thêm. –

2

Bạn có thể viết lại đoạn mã của bạn không phải là đệ quy:

# 'count_up_to' would be a more "Ruby" name ;-) 
def countUpTo(current, final) 
    (current..final).each { |i| puts i } 
end 

Tôi đánh giá cao mã của bạn có lẽ là một sự trừu tượng về những gì bạn đang thực sự cố gắng để làm, nhưng nó có thể giúp hình thành giải pháp của bạn nếu bạn nghĩ các cách khác để lặp lại thay vì đệ quy.

HTH

+0

Nói chung, đệ quy vô biên là xấu. Hầu hết đệ quy có thể được viết lại để lặp lại như Pavling đã hiển thị. – nmjohn

+0

Điều này xứng đáng với một downvote ?! }: - [ – Pavling

+0

@Pavling: Bởi vì bạn đang nói "không làm điều đó" (không làm đệ quy) mà không đưa ra lý do hợp lệ để không làm điều đó. –

2

Trong Ruby 2.0, bạn có thể xác định kích thước ngăn xếp (tính theo byte) sử dụng RUBY_THREAD_VM_STACK_SIZE và các biến môi trường khác.

Các vấn đề liên quan