Ruby recursion — ошибка слишком глубокого уровня стека

#ruby #recursion

#ruby #рекурсия

Вопрос:

Я решаю игру «камень, ножницы, бумага». Введите массив и рекурсивно выведите победителя. Вот мой код:

    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 amp;amp; 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. Я получил слишком глубокий уровень стека, потому что этот код выделен жирным шрифтом. Это потому, что турнир.длина меняется, поэтому я не должен помещать ее в рекурсию? Может ли кто-нибудь дать подробное объяснение того, как это произошло?

  2. Я искал ответ, и кто-то использовал приведенный ниже код и работал. Интересно, почему эта ссылка на турнир не вызовет такой же проблемы с рекурсией.

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

Спасибо за любую помощь!

пример ввода:

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

Ответ №1:

Ваш код не разрезает турнир пополам — tournament[tournament.length/2] не возвращает вторую половину массива — он только возвращает элемент в положение tournament.length/2 , вместо этого вы должны сделать:

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

Кроме того, вы не учитываете массив длины 1 , что приводит к бесконечной рекурсии.

Вот рабочая версия вашего кода:

 def tournament_winner(tournament)
  if tournament.length == 1
    tournament_winner(tournament[0])
  elsif tournament.length==2 amp;amp; 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
  

Ответ №2:

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

Например, вы могли бы использовать более объектно-ориентированный подход:

 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
  

Пример использования:

 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."