Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stack level too deep in recursion for largest palindrome product question (Project Euler)

I'm trying to implement a recursive solution to the largest palindrome product problem

What I'm trying to do is start both numbers at 999 and iterate down to 100 for num1 and then restart num1 at 999 and iterate num2 down by 1.

The goal is basically to mimic a nested for-loop.

def largest_palindrome_prod(num1 = 999, num2 = 999, largest_so_far = 0)
  prod = num1 * num2
  largest_so_far = prod if prod > largest_so_far && check_pal(prod)

  if num2 == 100
    return largest_so_far
  elsif num1 == 100
    largest_palindrome_prod(num1 = 999, num2 -= 1, largest_so_far)
  else
    largest_palindrome_prod(num1 -= 1, num2, largest_so_far)
  end
end

#I know this function works, just here for reference
def check_pal(num)
  num = num.to_s if num.is_a? Integer
  if num.length < 2
    true
  else
    num[0] == num[-1] ? check_pal(num[1..-2]) : false
  end
end

rb:10:inlargest_palindrome_prod': stack level too deep`

I'm getting this error which is referring to the else statement in the largest_palindrome_prod function, but I can't figure out wast could be causing the stack error.

like image 240
user392500 Avatar asked Feb 26 '26 22:02

user392500


2 Answers

You don't have an infinite recursion bug. The stack is just running out of space because of the size of your input. To prove this, you can run your same function with the range of 2-digit numbers, instead of the 3-digit ones. It returns fine, which shows that there is no flaw with your logic.

How to get around this? Two options.

Option 1: You could simply not use recursion here (just use a regular nested loop instead)

Option 2: Keep your same code and enable tail call optimization:

# run_code.rb

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

require './palindrome_functions.rb'
puts largest_palindrome_prod
# => 906609 

Note, for a reason I don't fully understand, the tail call optimization must be enabled in a different file than the code being run. So if you simply moved the compile_option line to the palindrome_functions.rb file, it wouldn't work.

I cant really give you a full explanation of tail call optimization (look it up on Wikipedia) but from my understanding, its a heavy optimization for recursive functions that only works when the recursive call is at the end of the function body. Your function meets this criteria.

like image 171
max pleaner Avatar answered Feb 28 '26 13:02

max pleaner


@maxpleaner has answered your question and has shown how you can use recursion that avoids the stack level error. He also mentioned the option (which I expect he favours) of simply looping, rather than employing recursion. Below is one looping solution. The following method is used in the search1.

def check_ranges(range1, range2 = range1)
  range1.flat_map do |n|
    [n].product((range2.first..[n, range2.last].min).to_a)
  end.map { |x,y| x*y }.
      sort.
      reverse_each.
      find do |z|
        arr = z.digits
        arr == arr.reverse
      end              
end

Let's first find the largest palindrome of the product of two numbers between 960 and 999 (if there are any):

check_ranges(960..999)
  #=> nil 

There are none. Note that this calculation was very cheap, requiring the examination of only 40*40/2 #=> 800 products. Next, find the largest palindrome that is equal to the product of two numbers between 920 and 999.

check_ranges(920..999)
  #=> 888888

Success! Note that this method re-checks the 800 products we checked earlier. It makes more sense to examine only the cases represented by the following two calls to brute_force:

check_ranges(960..999, 920..959)
  #=> 888888 
check_ranges(920..959)
  #=> 861168 

The first call computes 40*40 #=> 1600 products; the second, 800 products.

Of course, we have not yet necessarily found the largest product that is a palindrome. We do, however, have a lower bound on the largest product, which we can use to advantage. Since

888888/999
  #=> 889

we infer that if the product of two numbers is larger than 888888, both of those numbers must be at least 889. We therefore need only check:

check_ranges(889..999, 889..919)
  #=> 906609 
check_ranges(889..919)
  #=> 824428 

We are finished. This tells us that 906609 is the largest product of two 3-digit numbers that is a palindrome.

The question does not ask what are the two numbers whose product is the largest palindrome, but we can easily find them:

(889..999).to_a.product((889..919).to_a).find { |x,y| x*y == 906609 }
  #=> [993, 913] 
993*913
  #=> 906609

Moreover, let:

a = (889..999).to_a.product((889..919).to_a).map { |x,y| x*y }.
      sort.
      reverse

Then:

a.index { |n| n == 906609 }
  #=> 84

tells us that only the largest 84 elements of this sorted group of 111*31 #=> 3441 products had to be examined before a palindrome (906609) was found.

All of this needs to be organized into a method. Though challenging for a newbie, it should be a good learning experience.

1. It would be useful to test which is faster, arr = z.digits; arr == arr.reverse or s = z.to_s; s == s.reverse.

like image 30
Cary Swoveland Avatar answered Feb 28 '26 12:02

Cary Swoveland



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!