Skip to content

Instantly share code, notes, and snippets.

@meaganewaller
Last active December 6, 2020 00:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save meaganewaller/515e4c6e20a6d3d27cea909f45cda4df to your computer and use it in GitHub Desktop.
Save meaganewaller/515e4c6e20a6d3d27cea909f45cda4df to your computer and use it in GitHub Desktop.
Splat Operator Blog Post - April 15, 2013

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.

Method Definitions

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.

Multiple Variable Assignments

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

Flatten Arrays

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

Array Coercion

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

Calling Methods

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

Take Away

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment