Today was the start of my second week as an apprentice. I spent my first weekend as an apprentice barely opening my laptop, it was a much needed break and I felt very refreshed and ready to come back to work. I finally finished off my RSpec file for my board class, and now am trying to get start on my game_spec file and game file.
I spent a good portion of my day doing Ruby koans, and was pleasantly surprised to discover that I was able to complete exercises 1-15 without hardly any assistance. I learned something new in Ruby that actually made me excited, it’s kind of cool being excited about a programming language and what it can do. I’m just really happy that things are finally starting to click. I was stumped on a test that just wouldn’t pass, and I couldn’t figure out why not. I spent a little bit of time working over the weekend (read: very limited), and found that my tests that weren’t passing were almost always due to a simple spelling error. This is what the test on my board_spec.rb that was giving me some trouble looked like:
it 'should return true when a winning solution exists' do
@board.solutions.each do |num|
make_mark(num, 'x')
@board.winning_solutions?('x').should == true
make_mark(num, :blank)
@board.winning_solutions?('x').should == false
end
end
Here is my board.rb file
class Board
attr_reader :squares, :size, :solutions
def initialize(size)
@size = size
@squares = Array.new(size, :blank)
end
def make_mark(index, mark)
@squares[index] = mark
end
def squares_with_marks(mark)
occupied_squares = []
@squares.each_with_index do |x, index|
occupied_squares << index if x == mark
end
occupied_squares
end
def win_solutions
@solutions = [
[0,1,2],[3,4,5],[6,7,8],
[0,3,6],[1,4,7],[2,5,8],
[0,4,8],[2,4,6]
]
end
def winning_solutions?(mark)
has_solution = false
occupied_squares = squares_with_marks(mark)
[*@solutions].each do |solution|
has_solution |= (solution - occupied_squares).empty?
end
has_solution
end
end
(If you took more than a cursory glance, you probably already notice I have a splat operator in here, I ran into this error twice). However, upon running the rest this error kept happening:
Run options: include {:focus=>true}
All examples were filtered out; ignoring {:focus=>true}
F.........
Failures:
1) Board should return true when a winning solution exists Failure/Error: @board.solutions.each do |num|
NoMethodError:
undefined method `each' for nil:NilClass
# ./spec/board_spec.rb:55:in `block (2 levels) in <top (required)>' Finished in 0.00373 seconds 10 examples, 1 failure Failed examples: rspec ./spec/board_spec.rb:54
# Board should return true when a winning solution exists
I was stumped for a bit, and then I took to google for my solution. It turns out that I needed to use the splat operator. I was listening to a Ruby podcast on the way home from work last week and learned about the splat operator and said outloud: “Woah, that’s cool.” but didn’t think to use it here.
However, upon searching my error, it seemed I needed the splat operator to remedy my problem. I fixed my problem by simply doing this:
it 'should return true when a winning solution exists' do
[*@board.solutions].each do |num|
make_mark(num, 'x')
@board.winning_solutions?('x').should == true
make_mark(num, :blank)
@board.winning_solutions?('x').should == false
end
end
If you don’t know about the splat operator, which is simply the asterisk (*
) symbol, it has some cool effects.
I am going to demonstrate what I learned about the splat operator today, because I learn a lot better, and retain
information more when I explain how to do something.
You can use splat in a method definition to gather up remaining arguments
def say(message, *people)
people.each{ |i| puts "#{i}: #{message}" }
end
say("Let's slay some vampires!", "Buffy", "Willow", "Xander")
# Buffy: Let's slay some vampires!
# Willow: Let's slay some vampires!
# Xander: Let's slay some vampires!
(As you can tell, I’ve got Buffy the Vampire Slayer on my brain) However, let’s break this down: message gets the first argument, then *people will get however many arguments you pass into say.
Ruby is smart enough to match up arguments as best possible and pass all additional arguments into the splat operator. What if you assign more than one parameter a splat operator?
def say(*messages, *people)
people.each do |i|
messages.each { |j| puts "#{i}: #{j}" }
end
end
say("Hello!", "What's happening?", "Buffy", "Xander")
However, it returns this error:
/rb:1: syntax error, unexpected tSTAR (SyntaxError)
def say(*messages, *people)
/rb:5: syntax error, unexpected keyword_end, expecting
$end
So, Ruby seems to be smart enough to not allow me to do this, which would be not so smart on my part . The splat operator is basically just syntactic sugar. It allows you to pass in additional parameters without having to put those parameters in an array.
Ruby lets you do multiple variable assignment without a splat operator.
i, j = 50, 75
i, j = j,
i puts "#{i}, #{j}"
# 50, 75
But, sometimes the splat operator helps in some circumstances
names = ["Buffy", "Willow", "Xander", "Angel", "Oz"]
first, second = names
puts "#{first}, #{second}"
# Buffy, Willow
Uh-oh, we’ve lost the last three variables, in steps the splat operator!
names = ["Buffy", "Willow", "Xander", "Angel", "Oz"]
first, *second = names
puts "#{first}, #{second}"
#Buffy, ["Willow", "Xander", "Angel", "Oz"]
So far we’ve only seen the splat operator create an array out of variables, but it can also do the opposite:
names = ["Buffy", "Willow"]
more_names = [*names, "Xander", "Angel", "Oz"]
print more_names
["Buffy", "Willow", "Xander", "Angel", "Oz"]
Sometimes it can be useful to create arrays out of other variables
one_through_five = *1..5
print one_through_five
[1, 2, 3, 4, 5]
The code above is bit neater to use than the alternative: (1..5).to_a
. However, just because you can use something doesn’t mean you should. You can do ridiculous things with the splat operator:
say = *"Hello"
print say
# ["Hello"]
The splat operator doesn’t have to only be used to define a method, you can use it to call one.
people = ["Buffy", "Willow", "Xander"]
say "Hi there!", *people
# Buffy: Hi there!
# Willow: Hi there!
# Xander: Hi there!
Above, the splat operator converted the array into method arguments. However, it doesn’t have to be used with methods that take a variable number of arguments you can use it in other ways
def add(i, j)
i + j
end
pair = [6, 10]
add *pair
# 16
The splat operator is a neat little trick, but it’s rarely used outside of method definitions and method calls. With that being said, I would be cautious to use it outside of either, you don’t want to make the code too complicated to only save a few characters.