'Ruby: Accessing array's in arrays

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

That is a tournament that needs to be solved by the following code:

def self.winner(player1, player2)
  p1c = player1.last.downcase
  p2c = player2.last.downcase
  unless RPS.include?(p1c) && RPS.include?(p2c)
    raise NoSuchStrategyError, "Strategy must be one of R,P,S"
  end
  if p1c!=p2c 
    case p1c
      when "r" 
        p2c=="s" ? player1 : player2
      when "p" 
        p2c=="r" ? player1 : player2
      when "s" 
        p2c=="p" ? player1 : player2
    end
   else 
     player1
  end  
end

Now the base case is, of course,

def self.tournament_winner(tournament)
  if tournament.size == 2
    self.winner(tournament[0], tournament[1])
  else
    #WORK HERE
  end
end

But in the else how can I get the first array set being the array containing "Armando" and "Dave" check who wins then continue, it needs to be used on arbitrary sized arrays. is there a way to go through the elements and filter the wins; i.e., on the first instance of recursion it should return:

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

I'm sorry if my wording's bad. I've been trying to solve this for a while and its getting late and I lack sense.



Solution 1:[1]

That base function is not correct. First, let us assume that the tourney array is well-formed with always pairs of either players or arrays. What you want to do is if tournament[0] is a simple player definition then player 1 is tournament[0]. However, if tournament[0] is an array of players, then player 1 is the winner of tournament[0] - this is your recursion. Repeat the same logic for player 2, and return the winner of player 1 versus player 2.

Now, the question becomes "How do I determine if tournament[0] is a simple array or not?" Lets do this the simple way with the interactive shell (I prefer pry over the default irb), using your second example:

[3] pry(main)> tourney.class
=> Array
[4] pry(main)> tourney[0].class
=> Array
[5] pry(main)> tourney[0][0].class
=> Array
[6] pry(main)> tourney[0][0][0].class
=> String

So, we can test the .class method to check if it is an array, leading us to this solution:

def self.tournament_winner(tournament)
  if tournament[0][0].class == Array
    player1 = self.tournament_winner(tournament[0])
  else
    player1 = tournament[0]
  end

  if tournament[1][0].class == Array
    player2 = tournament_winner(tournament[1])
  else
    player2 = tournament[1]
  end

  self.winner(player1, player2)
end

Note that I need to use tournament[0][0] to stop us recursing one step too deep. Actually, I don't like that, so lets try it another way, using flatten as suggested above. We have reached the bottom of the tree if the flattened array is the same size as the unflattened version:

def self.tournament_winner(tournament)
  if tournament[0].flatten(1).size != tournament[0].size
    player1 = self.tournament_winner(tournament[0])
  else
    player1 = tournament[0]
  end

  if tournament[1].flatten(1).size != tournament[1].size
    player2 = tournament_winner(tournament[1])
  else
    player2 = tournament[1]
  end

  self.winner(player1, player2)
end

I use flatten(1) as a minor optimisation. Now:

[7] pry(main)> puts "And the winner is #{tournament_winner(tourney)[0]}!"
=> And the winner is Richard!

Further Ruby-fication of the function above might be

def self.tournament_winner(tournament)
  player1 = if tournament[0].flatten(1).size != tournament[0].size
    self.tournament_winner(tournament[0])
  else
    tournament[0]
  end
  
  player2 = if tournament[1].flatten(1).size != tournament[1].size
    tournament_winner(tournament[1])
  else
    tournament[1]
  end

  self.winner(player1, player2)
end

Or getting DRYer:

def self.get_competitor(branch)
  if branch.flatten(1).size != branch.size
    self.tournament_winner(branch)
  else
    branch
  end
end

def self.tournament_winner(tournament)
  player1 = self.get_competitor(tournament[0])
  player2 = self.get_competitor(tournament[1])

  self.winner(player1, player2)
end

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Chris