I'm supposed to create a method in ruby that will take in a structured,multi-dimensioned array, such as:
my_arr = [
[ [['Armando', 'P'], ['Dave', 'S']], [['Richard', 'R'], ['Michael', 'S']] ],
[ [['Allen', 'S'], ['Omer', 'P']], [['David E.', 'R'], ['Richard X.', 'P']] ]
]
This array is supposed to represent a tournament of Rock, paper & scissors, the number of players will always be 2^n and no repetitions (of players) are made.
The code I wrote is as follows:
class WrongNumberOfPlayersError < StandardError ; end
class NoSuchStrategyError < StandardError ; end
def rps_game_winner(game)
raise WrongNumberOfPlayersError unless game.length == 2
valid = ["r","p","s"]
a1=[(game[0][1]).downcase]
a2=[(game[1][1]).downcase]
raise NoSuchStrategyError unless (valid & a1) && (valid & a2)
return (game[0]) if a1 === a2
case (a1[0])
when "r"
case (a2[0])
when "p"
return (game[1])
else
return (game[0])
end
when "p"
case (a2[0])
when "s"
return (game[1])
else
return (game[0])
end
when "s"
case (a2[0])
when "r"
return (game[1])
else
return (game[0])
end
end
end
def rps_tournament_winner(tournament)
if tournament[0][0].is_a?(Array)
rps_tournament_winner(tournament[0])
elsif tournament[1][0].is_a?(Array)
rps_tournament_winner(tournament[1])
else
rps_game_winner(tournament)
end
end
So my problem is that given the use of array i mentioned earlier being passed to rps_tournament_winner Dave always wins instead of Richard and i haven't been able to figure out where i went wrong.
Ty for reading the wall of text/code :)
One thing I noticed is that your use of 'valid' doesn't do anything to check if your input is actually valid. If you're trying to check that a1 and a2 are either "r" "p" or "s" you should use a regular expression:
valid = /[rps]/ # matches either "r" "p" or "s"
raise NoSuchStrategyError unless (a1 =~ valid) && (a2 =~ valid)
Your array of players is nested very deep. You'll make your life simpler by thinning it out:
my_arr = [['Armando', 'P'], ['Dave', 'S'], ['Richard', 'R'], ['Michael', 'S'],
['Allen', 'S'], ['Omer', 'P'], ['David E.', 'R'], ['Richard X.', 'P']]
You can make it easier to read and maintain by breaking your program into parts. For example, create a method for determining a win:
# This is incomplete as it doesn't deal with ties. I'll let you do that part
def win(first, second)
if (first == "p" && second == "r") || (first == "r" && second == "s") || (first == "s" && second == "p")
return true
else
return false
end
end
Now it's easier to write and understand the game itself, using the above method:
def rps_game_winner(player1, player2)
first = player1[1].downcase
second = player2[1].downcase
win(first, second) ? player1 : player2 # ternary operator returns the winner
end
You now have a method to put all this into play (sort of the main logic), and we'll use recursion here:
def rps_tournament_winner(player_list)
round_winners = [] # place to hold the winners for each round
if player_list.size == 1 # final condition to stop the recursion
puts "The winner is #{player_list[0][0]}."
else
player_list.each_slice(2) do |l1, l2| # take pairs from your list to play each other
round_winners << rps_game_winner(l1, l2)
end
rps_tournament_winner(round_winners) # use recursion to play the winners against each other
end
end
# put it into play
puts test_array(my_arr)
That's it. The winner is Richard and it'll always be Richard because the play is deterministic.
While this will run, you should know that I left out some important things like dealing with ties and odd numbers of players. The program will not work right under these conditions. But I'll leave it up to you to solve those parts.
EDIT: Modifying the original array:
new_array = []
my_arr.flatten.each_slice(2) do |name, pick|
new_array << [name, pick]
end
Your recursion only goes down tournament[0] before ending never tournament[1].
You need to play both tournament[0] and tournament[1] then play them against each other.
Something like. ( I leave it to you write the ruby code)
rps_game_winner( [rps_tournament_winner(tournament[0], rps_tournament_winner(tournament[1]] )
It's very hard to debug your code.
But I think that it is fundamentally flawed.
When you call rps_tournament_winner you pass in
[
[ [['Armando', 'P'], ['Dave', 'S']], [['Richard', 'R'], ['Michael', 'S']] ],
[ [['Allen', 'S'], ['Omer', 'P']], [['David E.', 'R'], ['Richard X.', 'P']] ]
]
It asks tournament[0][0].is_a?(Array)
[['Armando', 'P'], ['Dave', 'S']], [['Richard', 'R'], ['Michael', 'S']]
Yes it is.
It calls rps_tournament_winner(tournament[0])
tournament = [ [['Armando', 'P'], ['Dave', 'S']], [['Richard', 'R'], ['Michael', 'S']] ]
It asks tournament[0][0].is_a?(Array)
['Armando', 'P']
Yes it is.
it calls rps_tournament_winner(tournament[0])
tournament = [['Armando', 'P'], ['Dave', 'S']]
It asks tournament[0][0].is_a?(Array)
'Armando'
No its not.
It calls rps_game_winner(tournament)
And in the game Dave beats Armando!
I don't think this is the functionality you intended.
I suggest you rewrite this, trying to keep things simple.
Related
I am having trouble using || ("or").
This is the first time I select using the "or" feature and I have been trying to select the words that are greater than 6 characters long OR start with an "e". I tried everything but I keep getting just one feature or an "and". This is the code so far
def strange_words(words)
selected_words = []
i = 0
while i < words.length
word = words[i]
if word.length < 6
selected_words << word
end
i += 1
end
return selected_words
end
print strange_words(["taco", "eggs", "we", "eatihhg", "for", "dinner"])
puts
print strange_words(["keep", "coding"])
Using the || operator is the same as writing multiple if statements. Let's use a silly example to demonstrate it. Say you wanted to determine if a word started with the letter 'e'. Well there are a few forms of 'e'. There is the lowercase e and the upppercase E. You want to check for both forms so you could do something like this:
def starts_with_e?(string)
result = false
if string[0] == 'e'
result = true
end
if string[0] == 'E'
result = true
end
result
end
Notice however that you're doing the same actions after checking for the condition. This means you could simplify this code using the OR/|| operator, like such:
def starts_with_e?(string)
result = false
if string[0] == 'e' || string[0] == 'E'
result = true
end
end
For your specific question, you can do the following:
def strange_words(words)
words.select { |word| word.length < 6 || word[0] == 'e' }
end
When you run with your example, it gives you this output:
> strange_words(["taco", "eggs", "we", "eatihhg", "for", "dinner"])
=> ["taco", "eggs", "we", "eatihhg", "for"]
This is still not good code. You'll want to protect the methods from bad input.
I want the user to type anything, but it should include two strings like "look" and "right", if both are included in the sentence then the program will go to the next line.
I am new to ruby, just getting myself familiar with the language.
I have tried && and || but it doesnt work
puts " in which direction do you want to look"
input = gets.chomp.to_s
if input.include? == "look" && "right"
puts " There is a dead end on the right"
elsif input.include? "look" && "left"
puts "there is a narrow way , going towards the woods"
else
puts "i dont understand what you are trying to say"
end
Instead of input.include? "look" && "right", you need to compare the statements one by one.
if input.include?("look") && input.include?("right")
puts " There is a dead end on the right"
elsif input.include?("look") && input.include?("left")
puts "there is a narrow way , going towards the woods"
else
puts "i dont understand what you are trying to say"
end
Logically, input.include? "look" would either return true or false, and input.include? "right" would also return true or false. Both statements need to be true in order for it to work!
I'd suggest to define a method which handles the input and returns the direction to be use in the if/then/else:
def get_direction(input)
keywords = ['look', 'left', 'right']
directions = {101 => :right, 110 => :left}
directions.default = :none
input = input.scan(/\w+/).map(&:downcase).uniq
key = keywords.map { |k| input.include? k }.map{ |e| e ? 1 : 0 }.join().to_i
directions[key]
end
These are few examples of method calls:
get_direction("Yes. Look at your right now! Right now! Look!") #=> :right
get_direction("Yes. Look at your left now!") #=> :left
get_direction("Look right. Left them there!") #=> :none
get_direction("Stay right here!") #=> :none
Inside the method you can find a String#scan for splitting the input into words. Also, there is a .map(&:downcase) to assure case insensitivity, using Array#map. It uses a Hash to select the output.
Add p to print out this line to understand how it is working:
p key = keywords.map { |k| input.include? k }.map{ |e| e ? 1 : 0 }.join().to_i
Now you can use the method this way:
direction = get_direction(gets.chomp.to_s)
if direction == :right
p 'right'
elsif direction == :left
p 'left'
else
p 'none'
end
Is there a way to store the output of a method in a variable in Ruby? For example, if I wanted to store the return value of display_results in a variable to be evaluated by win_counter is that possible? I want win_counter to increment every time display_results has a winner.
win_combos = [
['rock', 'scissors'],
['paper', 'rock'],
['scissors', 'paper'],
['rock', 'lizard'],
['lizard', 'spock'],
['spock', 'scissors'],
['scissors', 'lizard'],
['lizard', 'paper'],
['paper', 'spock'],
['spock', 'rock']
]
scores = { player: 0, computer: 0 }
def display_results(first, second, combos = [])
combos.each do |combo|
if combo == [first, second]
puts "Player won!"
break
elsif combo == [second, first]
puts "Computer won!"
break
end
end
if first == second
puts "It's a tie!"
end
end
def win_counter(player, computer, scores)
if display_results(player, computer)
scores[:player] = scores[:player] + 1
elsif display_results(computer, player)
scores[:computer] = scores[:computer] + 1
end
puts "players wins: #{scores[:player]}; computer wins: #{scores[:computer]}"
end
display_results('spock', 'rock', win_combos)
win_counter('spock', 'rock', scores)
All you need to do is set a variable equal to the method call. You could use:
if display_results(player, computer) == "Player won!"
However, it might be more useful to split the logic for printing the winner away from the logic for the actual game. In ruby methods that return booleans are stylized with a '?' i.e empty? display_results should only display the results of the game, you shouldn't use an if statement on it.
I have a hash here:
VALID_CHOICES = {
'r' => 'rock',
'p' => 'paper',
'sc' => 'scissors',
'l' => 'lizard',
'sp' => 'spock'
}
And a method which basically compares here:
def win?(first, second)
(first == 'sc' && second == 'p') ||
(first == 'p' && second == 'r') ||
(first == 'r' && second == 'l') ||
(first == 'l' && second == 'sp') ||
(first == 'sp' && second == 'sc') ||
(first == 'sc' && second == 'l') ||
(first == 'l' && second == 'p') ||
(first == 'p' && second == 'sp') ||
(first == 'sp' && second == 'r') ||
(first == 'r' && second == 'sc')
end
How can I rewrite my method in very short concise code that means exactly the same thing? Any idea? Is it possible to do it using hashes?
You should define clear rules for what each token can win:
WINS = {
'r' => %w{l sc},
'p' => %w{r sp},
'sc' => %w{p l},
'l' => %w{p sp},
'sp' => %w{r sc}
}
Now you can determine wins using a simple lookup:
def win?(first, second)
WINS[first].include?(second)
end
While there may be several 'clever' ways to avoid an explicit structure like WINS, explicit rules are much more understandable - and therefore, more maintainable. Conciseness in code is considered a positive attribute where it improves the readability of the code. Conciseness to the extreme that causes the code to be difficult to understand is not something to strive for.
In addition to user2864740's comment and Cary Swoveland's explanation, you could also use a hash to map "winning pairs" to their respective verb:
WINS = {
%w[scissors paper] => 'cuts',
%w[paper rock] => 'covers',
%w[rock lizard] => 'crushes',
%w[lizard spock] => 'poisons',
%w[spock scissors] => 'smashes',
%w[scissors lizard] => 'decapitates',
%w[lizard paper] => 'eats',
%w[paper spock] => 'disproves',
%w[spock rock] => 'vaporizes',
%w[rock scissors] => 'crushes'
}
It returns the corresponding verb if the key's first item beats the second:
WINS[['paper', 'rock']] #=> "covers"
and nil if it doesn't:
WINS[['rock', 'paper']] #=> nil
In your method:
def win?(first, second)
WINS.has_key?([first, second])
end
Or to check both sides:
if WINS.has_key?([first, second])
# first wins
elsif WINS.has_key?([second, first])
# second wins
else
# tie
end
Or more verbose:
def result(first, second)
if verb = WINS[[first, second]]
"first wins: #{first} #{verb} #{second}"
elsif verb = WINS[[second, first]]
"second wins: #{second} #{verb} #{first}"
else
"tie"
end
end
result('rock', 'scissors')
#=> "first wins: rock crushes scissors"
result('spock', 'lizard')
#=> "second wins: lizard poisons spock"
result('paper', 'paper')
#=> "tie"
Of course, you can also use your abbreviations (sc, p, r, l, sp) instead of whole words.
Ruby has a fairly powerful case..when..else construct for when you need to match criteria against a single variable. What is the "canonical" way to match criteria against multiple variables without simply nesting case statements?
Wrapping multiple variables in an array (like [x, y]) and matching against it isn't equivalent, because Ruby won't apply the magical case === operator to the elements of the array; the operator is only applied to the array itself.
I'm going to go ahead and respond with a community-wiki answer with a (defeated) stab at this question.
You need to use an if..elsif..else, and ensure that the variables you want to match against appear on the right-hand side of the === operator (which is what case essentially does).
For example, if you want to match x and y against some criteria:
if (SomeType === x) && (1..10 === y)
some_value
elsif (:some_symbol === x) && (11..20 === y)
some_other_value
end
This is a simplistic way to add ===:
class Array
def ===(other)
return false if (other.size != self.size)
other_dup = other.dup
all? do |e|
e === other_dup.shift
end
end
end
[
['foo', 3],
%w[ foo bar ],
%w[ one ],
[]
].each do |ary|
ary_type = case ary
when [String, Fixnum] then "[String, Fixnum]"
when [String, String] then "[String, String]"
when [String] then "[String]"
else
"no match"
end
puts ary_type
end
# >> [String, Fixnum]
# >> [String, String]
# >> [String]
# >> no match
If this pattern is common enough in your code to warrant economical expression, you can do it yourself:
class BiPartite
attr_reader :x, :y
def self.[](x, y)
BiPartite.new(x, y)
end
def initialize(x, y)
#x, #y = x, y
end
def ===(other)
x === other.x && y === other.y
end
end
....
case BiPartite[x, y]
when BiPartite[SomeType, 1..10]
puts "some_value"
when BiPartite[:some_symbol, 11..20]
puts "some_other_value"
end
Since Ruby's when keyword supports a comma separated list of values, you could use the splat * operator. This is, of course, assuming that you're referring to a set of discrete values that are in or could become an array.
The splat operator converts a list of arguments into an array, as frequently seen in
def method_missing(method, *args, &block)
Less well known is the fact that it also performs the inverse operation - turning an array into a list of arguments.
So in this case, you could do something like
passing_grades = ['b','c']
case grade
when 'a'
puts 'great job!'
when *passing_grades
puts 'you passed'
else
puts 'you failed'
end
My not battled tested solution is to call .map(&:class) and then is the array in the when statements
def foo(a, b)
case [a,b].map(&:class)
when [Integer, Integer]
puts "Integer"
when [String, String]
puts "String"
else
puts "otherwise"
end
end