user2634156
user2634156

Reputation: 1751

Ruby recursion - stack level too deep error

I am solving the " rock, paper, scissor" game. Input an array and recursively output the winner. Here is my code:

   class RockPaperScissors

  # Exceptions this class can raise:
  class NoSuchStrategyError < StandardError;  end

  def self.winner(player1, player2)
    strategy = player1[1]+player2[1]
    raise NoSuchStrategyError.new("Strategy must be one of R,P,S") if strategy !~ /(R|P|S){2}/
    strategy  =~ /rs|sp|pr|rr|ss|pp/i ? player1 : player2
  end


  def self.tournament_winner(tournament)
    if tournament.length==2 && tournament.first[0].is_a?(String) 
         winner(tournament[0],tournament[1]) 
    else 
    #keep slice the array in half
        ***winner(tournament_winner(tournament[0,tournament.length/2]), tournament_winner(tournament[tournament.length/2]))***
    end
  end

end
  1. I got stack level too deep because that code in bold. Is it because the tournament.length is changing so I shouldn't put it inside the recursion? Could someone gives a detailed explanation about how that happened?

  2. I searched the answer and someone used the code below and worked. I wonder why that reference to tournament won't cause the same recursion issue.

    winner(tournament_winner(tournament[0]), tournament_winner(tournament[1]))
    

Thank you for any help!

sample input:

[
    [
        [ ["Armando", "P"], ["Dave", "S"] ],
        [ ["Richard", "R"],  ["Michael", "S"] ],
    ],
    [
        [ ["Allen", "S"], ["Omer", "P"] ],
        [ ["David E.", "R"], ["Richard X.", "P"] ]
    ]
]

Upvotes: 0

Views: 227

Answers (2)

coreyward
coreyward

Reputation: 80051

There are so many better ways of handling this than messy regexs, confusing abbreviations, and strings all over the place.

For example, you could go with a more object oriented approach:

module RockPaperScissors
  class NoSuchStrategyError < StandardError; end

  class Move
    include Comparable
    attr_reader :strategy

    def initialize(strategy)
      raise NoSuchStrategyError unless strategies.include?(strategy)
      @strategy = strategy
    end

    def <=>(opposing_move)
      if strengths[strategy] == opposing_move.strategy
        1
      elsif strengths[opposing_move.strategy] == strategy
        -1
      else
        0
      end
    end

  protected

    def strengths
      {
        rock: :scissors,
        scissors: :paper,
        paper: :rock
      }
    end

    def strategies
      strengths.keys
    end
  end

  class Player
    attr_reader :name, :move
    def initialize(name, move)
      @name, @move = name, move
    end
  end

  class Tournament
    def initialize(*players)
      @player_1, @player_2, _ = *players
    end

    def results
      p1move = @player_1.move
      p2move = @player_2.move

      if p1move > p2move
        "#{@player_1.name} wins."
      elsif p2move > p1move
        "#{@player_2.name} wins."
      else
        "Tie."
      end
    end
  end
end

Example usage:

rock = RockPaperScissors::Move.new(:rock)
paper = RockPaperScissors::Move.new(:paper)

player_1 = RockPaperScissors::Player.new('John Smith', rock)
player_2 = RockPaperScissors::Player.new('Corey', paper)

tournament = RockPaperScissors::Tournament.new(player_1, player_2)
tournament.results #=> "Corey wins."

Upvotes: 0

Uri Agassi
Uri Agassi

Reputation: 37409

Your code does not slice the tournament in half - tournament[tournament.length/2] does not return the second half of the array - it only returns the item in position tournament.length/2, instead you should do:

winner(tournament_winner(tournament[0...tournament.length/2]), tournament_winner(tournament[tournament.length/2..-1]))

Also, you do not consider an array of length 1, which results in an endless recursion.

Here is a working version of your code:

def tournament_winner(tournament)
  if tournament.length == 1
    tournament_winner(tournament[0])
  elsif tournament.length==2 && tournament.first[0].is_a?(String) 
    winner(tournament[0],tournament[1]) 
  else 
    winner(tournament_winner(tournament[0...tournament.length/2]), 
      tournament_winner(tournament[tournament.length/2..-1]))
  end
end

Upvotes: 1

Related Questions