If you don’t have a CS (Computer Science) degree you might feel like you are missing out on something…
Or you may feel like CS is too abstract to be useful…
Or that Ruby already does all the hard work for you…
Either way…
It’s helpful to understand the basics of data structures like hashes, stacks & queues.
In this post:
You’ll discover how to use stacks in Ruby.
A practical computer science concept that you can start putting into practice right now!
Understanding Stacks in Ruby
What is a stack in Ruby?
A stack is a data structure which you can use as a “to-do” list. You keep taking elements from the stack & processing them until the stack is empty.
Here’s is a visual representation.
Push 5 into an empty stack:
5
Push 3 into the stack:
3
5
Push 9 into the stack:
9
3
5
Take one item from the stack:
3
5
The big thing to notice here is that new items are added to the top of the stack. In a “Last-In First-Out” (LIFO) fashion. Meaning that when you take (pop) an item from the stack, it will be the last item that was pushed into it.
Another way to visualize how this works is by thinking about a stack of plates. If you put one plate on top of another you can’t take out the bottom plate without taking out the top plate first.
That’s all you are doing with a stack, pushing things to the top or poping things out. There is no indexing.
Let’s see some code examples on how this can be used.
How to Flatten an Array Using a Stack
A classic example of stacks is to use them to flatten an array. In other words, convert a multi-dimensional array into a one-dimensional array.
Here’s an example:
arr = [1,2,3,[4,5],6] arr.flatten
In Ruby we have the flatten method which does this for you. But what if you didn’t have this method? And how does this method work?
This is where the stack comes in!
Ruby implements the push & pop methods, so we can treat an array like a stack.
Note: Both
push
&<<
are the same method. I will be using<<
in my code examples.
The idea is to go over every element and check if it’s an array or something else. If it’s an array then we will push
the elements inside this array back into the stack.
What happens is that we keep removing array layers (like an onion) until there are none left. This will leave us with a flattened array.
Here’s is the code:
arr = [1,2,3,[4,5],6] flat = [] arr.each do |thing| if thing.is_a? Array thing.each { |i| arr << i } else flat << thing end end p flat # [1, 2, 3, 6, 4, 5]
You will notice there is no pop
method call in this code.
This is because I'm letting each
do the hard work of taking the items from the stack & feeding them to my algorithm. Also notice how the order of the elements is not maintained when doing things this way.
Another version using until
& empty?
:
until arr.empty? thing = arr.pop if thing.is_a? Array thing.each { |i| arr << i } else flat << thing end end p flat # [6, 5, 4, 3, 2, 1]
Since we are using pop
now, instead of letting each
do its thing, we are getting the flattened array in the correct order... But in reverse.
This reveals an interesting property of stacks:
Whatever list of things you put in, it will come back in the same order but in reverse.
Tip: The
Array#flatten
method takes an argument, which lets you define how many layers of nesting you would like to remove (by default all of them).
Solving the Balanced Parenthesis Problem
Here is another example & there is no equivalent Ruby method to do this for you!
It's also another classic problem in computer science...
The name is:
Matching balanced parenthesis.
The idea is that you are given a string & you need to validate if the parenthesis are valid.
For example, let's say you are writing a math evaluation program. You want to make sure the input is valid before processing it.
Example (Valid Input):
input = "1 + (4 + 6) * 2"
Example (Invalid Input):
input = "1 + (4 + 6 * 2"
You can use a stack to keep track of any parenthesis you have found in the input & then when you find a new parenthesis you check the top of the stack to make sure it matches the closing parenthesis.
If there is no match that means that you have invalid input.
Example:
PARENS = { "(" => ")", "{" => "}", "[" => "]" } OPENING_PARENS = PARENS.keys CLOSING_PARENS = PARENS.values def valid_parentheses(string) stack = [] string.each_char do |ch| if OPENING_PARENS.include?(ch) stack << ch elsif CLOSING_PARENS.include?(ch) ch == PARENS[stack.last] ? stack.pop : (return false) end end stack.empty? end p valid_parentheses("(){}[]") # true p valid_parentheses("[(])") # false
Another thing you will notice is that I ended this valid_parentheses
method with a stack.empty?
. That's to make sure we don't end with unclosed parenthesis.
If all the parenthesis are correctly closed then the stack should be empty 🙂
Example 3 (directions)
One more example to make sure you understand how to apply this.
In this case we are given a set of directions & we are told that we need to optimize it to save our traveler some time.
Here is an example set:
["NORTH", "SOUTH", "SOUTH", "EAST", "WEST", "NORTH", "WEST"]
You will notice that if you go north then south you will end up in the same place (assuming it's the same distance both directions). That's what we need to optimize & we can do that using a stack.
Example:
input = ["NORTH", "SOUTH", "SOUTH", "EAST", "WEST", "NORTH", "WEST"] directions = [] opposites = { "NORTH" => "SOUTH", "SOUTH" => "NORTH", "EAST" => "WEST", "WEST" => "EAST" } input.each do |dir| opposites[dir] == directions.last ? directions.pop : directions << dir end p directions
Conclusion
I hope you learned something new & that you start looking for ways to use stacks to solve programming challenges.
Don't forget to share this post so more people can read this!
Very nice! Thanks!
Thanks for reading Ben! 🙂
Informative. I think there is a typo if you can fix.
in Array#flatten example should be
Thanks for your comment Snehit. I think it should be
arr
, because I’m usingarr
as the stack & what I’m doing is pushing the array items on top of the stack. Sorry if I wasn’t clear enough in the post!If that is the case when you pop out 1,2,3, [4,5] you have 6 at the top of the stack. Then [4,5] is being an Array, you push those elements back on the stack then
arr
should be like [5, 4, 6]. And flat array should be [ 1, 2, 3, 5, 4, 6]. Let me know if I am missing anything.In that example
arr
would become& the end result would be
If that value of
arr
looks weird is becauseeach
is not actually removing any items, it just keeps track of the current index & keeps going as long as the index is less than the array length (in other words: until it reaches the end of the array).I added another example to the post using
until
that results in[6, 5, 4, 3, 2, 1]
(same order but in reverse, that’s normal for a stack).Hope that helps make things more clear 🙂
Thanks Jesus,
This makes sense.
Thank you, I’ve enjoyed reading. Please keep posting more articles like this.
I will while people keep sharing & reading my articles 🙂
Nice post
Nice post. Which syntax highlighting css have you used? Those are really nice!
I’m using the “Crayon Syntax Highlighter” wordpress plugin with the “Obsidian” theme 🙂