Hip Hip Array!
It’s time to add another trick to our collection, and that’d be a collection!
Docs
What's an Array?
An Array is just a collection of stuff we want to keep together, kinda like a set of numbers:
2.4.2 :001 > numbers = [1, 2, 3]
=> [1, 2, 3]
Then to get them back out, we just reach in and grab one:
2.4.2 :002 > numbers[0]
=> 1
Wait, zero? Yep, computers learned to count differently, they start with 0 and go from there.
Maybe we have an array of some strings:
2.4.2 :003 > strings = ['strings', 'of', 'doom']
=> ["strings", "of", "doom"]
But why be picky? We could even mix and match!
2.4.2 :004 > array = ["string", 1, 1.0]
=> ["string", 1, 1.0]
Granted not many really do that at all, but it's nice to know we can get away with things.
No no no no no, not those type of things, shoo you.
Strung a Chord
Didn’t we say earlier that a String is just a collection of characters? What happens if we put that same accessor on the end of a String?
2.4.2 :005 > "foobarbaz"[0]
=> "f"
Aha! What if we want to get the first few characters though, or maybe the first few from an Array?
Home on the Range
A range is just a way to say give me the set of elements from a to b. Well, we could do literally that:
2.4.2 :006 > ("a".."c").to_a
=> ["a", "b", "c"]
Turns out we can do the same for Numbers:
2.4.2 :007 > (1..5).to_a
=> [1, 2, 3, 4, 5]
Think of a range as just eliding all the middle bits we don't want to type out. 1, 2, 3, 4, 5
becomes something like 1
to 5
, or in the case of a range, 1..5
.
Slice of Life
What happens if we go and stick a range into that accessor from earlier?
2.4.2 :008 > [1,2,3][0..1]
=> [1, 2]
It grabs the first (0
th) element and the second (1
st) element and gives us back a new array. We could make that 0
to 1
, to 5
, or even to 100
if we have a big enough collection to grab from. What happens if it's shorter than what we want? Well it still tries and just gives us back what it can, bless its heart:
2.4.2 :009 > [1,2,3][0..1000]
=> [1, 2, 3]
If you want a fun one, what happens if we use a negative number on it? Give it a try!
2.4.2 :010 > [1,2,3][-1]
=> 3
It looks starting at the end.
First Things First
Now if you're not fond of using ranges and think you'd just like to get the first or last few elements of an Array (it won't work on Strings), you're in luck!
If we want to get the first few elements, we just ask for the first n
elements. If we don't give it an argument it'll just give us back the first element of an array:
2.4.2 :011 > [1,2,3,4,5].first
=> 1
2.4.2 :012 > [1,2,3,4,5].first(3)
=> [1, 2, 3]
Same deal with last
, except it goes the opposite direction:
2.4.2 :013 > [1,2,3,4,5].last
=> 5
2.4.2 :014 > [1,2,3,4,5].last(3)
=> [3, 4, 5]
Feeling Included
What if we want to know if our array has something in it? Well we could just ask nicely:
2.4.2 :015 > [1,2,3].include?(1)
=> true
2.4.2 :016 > [1,2,3].include?(4)
=> false
Of course there are some faster ways to search through an Array, but that's a topic for later in Just the Sort when we have everything in order.
Free Samples!
Want to get a random element out of an Array? How about a few?
2.4.2 :017 > [1,2,3].sample
=> 3
2.4.2 :018 > [1,2,3].sample(2)
=> [3, 2]
Sample goes and grabs us a random element, or a few of them.
Pushy Pushy
Arrays aren't much good unless you can add and remove things from them, and in Ruby there are a few ways to do that, but first let's look at a few definitions:
Stack: Think of this like a Pez dispenser, you push new candies in the top, and to get one out you pop. The last item you put in is the first item you get out.
Queue: Ever been in a line for something, waiting? That's a queue. When you enter one you're enqueueing and when you finally get to the end you dequeue. So the first person in line is the first person out of line.
With that in mind, let's take a look at how you can add stuff to an Array:
2.4.2 :019 > array = [1,2,3]
=> [1, 2, 3]
2.4.2 :020 > array.push(4)
=> [1, 2, 3, 4]
2.4.2 :021 > array
=> [1, 2, 3, 4]
2.4.2 :022 > array.pop
=> 4
2.4.2 :023 > array
=> [1, 2, 3]
So we push
something onto the end, and we pop
it from the same end. Sound like a Stack? There's also shovel which you might see around some time:
2.4.2 :024 > array = [1,2,3]
=> [1, 2, 3]
2.4.2 :025 > array << 4
=> [1, 2, 3, 4]
2.4.2 :026 > array
=> [1, 2, 3, 4]
Well if they put Stack-like actions on it, there're probably Queue ones too right? Slightly different names, but yeah:
2.4.2 :027 > array = [2,3,4]
=> [2, 3, 4]
2.4.2 :028 > array.unshift(1)
=> [1, 2, 3, 4]
2.4.2 :029 > array
=> [1, 2, 3, 4]
2.4.2 :030 > array.shift
=> 1
2.4.2 :031 > array
=> [2, 3, 4]
Splatter Zone
In some Ruby you might see the Splat around: *
. So what's that do to an Array? Well it splats it!
2.4.2 :032 > old_array = [1, 2, 3]
=> [1, 2, 3]
2.4.2 :033 > new_array = [*old_array, 4, 5, 6]
=> [1, 2, 3, 4, 5, 6]
It takes that pesky second array and splats it on into another one like it never had brackets to begin with!
My Word!
Now it gets real pesky to keep on having to define an array of words like this:
2.4.2 :034 > words = ["a", "collection", "of", "some", "words"]
=> ["a", "collection", "of", "some", "words"]
You could go and make it a sentence and split it, but that's no fun at all. Ruby's got our back though:
2.4.2 :035 > words = %w(a collection of some words)
=> ["a", "collection", "of", "some", "words"]
Would you believe those are both the same thing? No? Good, probably not great to trust me right off, run it in your REPL!
%w
is just a whitespace delimited array. That just means that every time there's a space, tab, or newline it'll assume that's a new word.
Join the Fun
So we know how to split, but how do we put it back? I kinda want my sentence again. Ruby just calls this idea join
and it takes a delimiter too like split
:
2.4.2 :036 > words = %w(a collection of some words)
=> ["a", "collection", "of", "some", "words"]
2.4.2 :037 > words.join
=> "acollectionofsomewords"
...that's not quite right. Ah, right, delimiters:
2.4.2 :038 > words = %w(a collection of some words)
=> ["a", "collection", "of", "some", "words"]
2.4.2 :039 > words.join(" ")
=> "a collection of some words"
There we go! Now if we really wanted to we could make that a comma, a dash, or whatever really.
To Each Their Own
Now how do we get at each element in an array? Well this is where Ruby gets interesting:
2.4.2 :040 > [1,2,3].each { |n| puts n }
1
2
3
=> [1, 2, 3]
So if we read that, it's saying for each number we'll call it n
and we'll puts
it which is fancy talk for print with a new line afterwards.
That thing in the brackets? That's called a block, or in some other languages a function. Let's break it down:
Each element of the array goes into our block, and the block decides what to do with it. The thing to know about each is that we don't care about what the block returns, we normally use it for the side effects.
Once it's done, each
just returns the original Array.
We'll cover blocks where we care about output later in the section on Enumerable.
Let's take a look into the block:
So our Lemur friend here is just writing each number down on the screen, and we're just ignoring the output. If we did get the output though we'd be getting nil
As each number comes in it hits the |n|
and decides that in our block we'll call that number n
for now. Those symbols are known as pipes, and you can find it right above your enter key.
These are called block arguments, we're just giving a name to the value we pass in so we can deal with everything we get later.
Now this section will be particularly important for us to understand going into the next part, so make sure you understand what it's doing.
Scope
A block makes a new scope. That means if we go and create a variable inside of one we can't see it outside, but anything outside is fair game!
In this case a
will be defined in the context of that block, but anything outside of the block will have no idea what in the world a
is:
something.block_method { a = 5 }
If we define a variable before the block, the block will be able to change or update it:
a = 10
something.block_method { a = 5 }
In this case a
is equal to 5
because our block can see outside.
Be careful, this has been known to trip people up in the past.
There are ways around this, but that's most certainly a black magic bit of fun that has to deal with tweaking how Ruby defines what its current scope is.
There be dragons, so be careful.
Card Tricks
( Hoooo man this section could be fun, but I may well bring it out to Enumerable because map
and the shorthand is a bit dense for now )
Really, you could even think of an Array like a deck of cards:
deck = ['AS', 'AH', 'AD', 'AC', ..., 'JR']
Though we need an actual deck if we're going to play, so let's make one!
deck = %w[JR JB] + [*2..10, *%w[J Q K A]].product(%w[D S C H]).map(&:join)
Wait wait, was that a magic trick? That looked like magic.