Arrays and hashes are common data types used to store information. The main difference between an array and a hash is the manner in which data is stored. Arrays, represented by square brackets, contain elements which are indexed beginning at 0. So if an array contained three elements, the indexes for those elements would be array[0], array[1] and array[2]. Hashes, represented by curly brackets, store information in key/value pairs. Instead of checking array[0] to get the value at a particular index, you would check hash[key] to get the value at that index.
Nested Arrays
A nested array is exactly as it sounds – an array with one or more arrays nested inside of it. Nested arrays are useful when storing things in multiple categories. There can be multiple levels of nesting.
Two levels:
nested_array = [[“hello”, “world”], [“love”, “ruby”]]
Three levels:
more_nested_array = [[“hello”, [“world”, “new york”]], [“love”, “ruby”]]
Accessing Values in a Nested Array
If we want to access the first value in nested_array, we would do so in the same manner as a regular array: nested_array[0]. This would return the first element in nested_array, which is also an array: [“hello”, “world”]. If we want to dip into the inner arrays, we need to add an extra index for the inner array. For example, nested_array[0][1] would return the second element of the first nested array: “world”.
Looping Through A Nested Array
If we were to iterate through the outer array in more_nested_array, only the first layer inside the array would be included in the loop. For example:
more_nested_array.each do |element| element << "test" end
Produces the following output:
[[“hello”, [“world”, “new york”], “test”], [“love”, “ruby”, “test”]]
As you can see, our loop iterated through the first layer of more_nested_array and added the string “test” to each element. Since the first layer in more_nested_array consisted of two arrays, it added “test” as an additional element of each nested array.
If we want to iterate through the inner elements, we have to add an inner loop:
more_nested_array.each do |element| element.each do |inner_element| inner_element << "test" end end
The output for the above code is quite different:
[[“hellotest”, [“world”, “new york”, “test”]], [“lovetest”, “rubytest”]]
As you can see, this looped through the outer array and then looped through the second level of the array and added the string “test” to each element at the second level, which consisted of three strings and an array. It skipped the third layer (i.e. it did not append “test” to “world” or “new york”) because we only included two nested loops. The number of nested loops we need will correspond to the number of layers we wish to access. If we wanted to loop through just the third layer, we would use a third nested loop:
more_nested_array.each do |element| element.each do |inner_element| if inner_element.is_a?(Array) inner_element.each do |third_layer_element| third_layer_element << "test" end end end end
This appended the string “test” only to the third layer elements:
[[“hello”, [“worldtest”, “new yorktest”]], [“love”, “ruby”]]
With nested loops, you can control exactly which elements of a nested array you’re accessing. If we wanted to add “test” to the end of every string at every level, we could do the following:
more_nested_array.each do |element| element.each do |inner_element| if inner_element.is_a?(Array) inner_element.each do |third_layer_element| third_layer_element << "test" end else inner_element << "test" end end end
Notice this skips the first layer and modifies only the second and third level, or inner_element and third_layer_element, to add “test” to each string inside more_nested_array:
[[“hellotest”, [“worldtest”, “new yorktest”]], [“lovetest”, “rubytest”]]
Nested Hashes
Nested hashes can be a bit more complicated. As stated above, a hash is a container that holds key and value pairs, noted with a hash rocket =>. A simple example of this is a dictionary. Dictionary would be the hash, each word would be a key, and the definition would be its corresponding value. The syntax looks something like this:
dictionary = { :hello => "greeting", :apple => "fruit" }
In programming, we rarely use such a simple example. Often, we’ll encounter nested hashes, which also contain arrays. For example, say we wanted to store our holiday supplies by holiday and by season, we may end up with something that looks more like this:
holiday_supplies = { "winter" => { "Christmas" => ["lights", "tree"], "New Years" => "champagne glasses" }, "summer" => { "July Fourth" => ["BBQ", "flags"] }, "spring" => { "Memorial Day" => "BBQ" }, "fall" => { "Labor Day" => "hot dogs" } }
As you can see, there is a hash called holiday_supplies, and four nested hashes representing the seasons. There are also two key/value pairs in the above example: (1) season => {holiday => supplies}, and (2) holiday => supplies.
Accessing Keys and Values in Nested Hashes
To access information in a standard hash, we could check the hash[key] to get the value at that key. In the dictionary example, if we entered dictionary[:hello] we would get the value “greeting”.
Accessing keys and values in a nested hash works the same way. If we wanted to know the value of winter (or what holidays/supplies to anticipate), we would enter holiday_supplies[“winter”], except instead of returning a single value, this returns another key/value pair, since “winter” is also a hash:
{“Christmas”=>[“lights”, “tree”], “New Years”=>”champagne_glasses”}
If we wanted to find out which supplies we need for a specific holiday, things change a bit. Instead of entering one key, we need to enter two keys:
holiday_supplies[“winter”][“Christmas”]
This dives another level into the hash and returns the value of the second key/value pair, holiday => supplies: [“lights”, “tree”].
Looping Through a Nested Hash
The best way to understand a nested loop is this: the first iteration loops through the outer hash and its key/value pair, and the second iteration loops through the inner hash and its key/value pair. Just like we had to add a second level to our above index to access the innermost values in the hash, we likewise need to add a second loop to access and manipulate the innermost values.
Say we wanted to print each of the supplies we need for all seasons to prepare for the upcoming year, we would want to use nested loops:
supplies = [] holiday_supplies.each do |key, value| value.each do |key2, value2| supplies << value2 end end puts supplies.flatten.uniq
This would give us a nice list each supply and would remove duplicates from the list:
lights tree champagne_glasses BBQ flags
If we used a single loop, we would not get the same output:
supplies = [] holiday_supplies.each do |key, value| supplies << value end puts supplies.flatten.uniq
This prints for us the values of the outer hash only:
{"christmas"=>["lights", "tree"], "new_years"=>"champagne_glasses"} {"july_fourth"=>["BBQ", "flags"]} {"memorial_day"=>"BBQ"} {"labor_day"=>"hot dogs"}
This isn’t as useful, as “BBQ” is listed twice, forcing us to do extra work to remove it from the list. If we’re particularly forgetful (or literal as computers are), we may purchase multiple BBQs. We probably don’t want to purchase multiple BBQs, so having a clean list which eliminates duplicates is important. Inner values; inner loop.
Another example for the use of nested loops would be for comparison purposes. Say we have a tweet that is over 140 characters and we need to shorten some of the words to make it fit. We have a list of common words and their abbreviations in a hash, and we need to figure out how to loop through the tweet and replace any of the matching words.
Here is our hash:
replacements = { "hello" => "hi", "to" => "2", "two" => "2", "too" => "2", "for" => "4", "four" => "4", "be" => "b", "you" => "u", "at" => "@", "and" => "&" }
Although the tweet itself is a string, we can .split the tweet and create an array containing each word in the tweet. Since we’re accessing two different data types (an array and a hash), we want to use nested loops:
tweet_array = tweet.split(" ") tweet_array.map do |word| replacements.each do |key, value| word = value if word.downcase == key end word end.join(" ")
The above code iterates through the tweet array and for each word in the array it iterates through the hash and replaces any word in the tweet array if the word matches a hash key. In other words, if a word in the tweet matches a keyword in the hash, it is replaced by that keyword’s value, or the word’s shortened version.
- PM Career Story - April 28, 2022
- How to Transition into Product Management - December 26, 2017
- What I’ve Learned in My First Few Months as a Product Manager - October 14, 2015
Shrikant says
Thanks a ton for this article.
I am having some issues with my nested hashes. COuld you please guide me?
I have hash of common_id as follows:
{ “signin” =>
“element1” =>
{ “ios_id => “i1”, “android_id” => “a1” }
“element2” =>
{ “ios_id => “i2”, “android_id” => “a2” }
}
trying to build a hash here on runtime, which will put all the related ids as a value to elements.
ids[“signin”][“elment1”] => “i1”
How can I convert the ids hash to get above result.
Francisco says
Pretty useful information. Just ran into a hash of arrays and this post helped me decide the way to iterate it. Thanks.
JOSE says
Hey really helpful and clear – thanks a lot!
Hilliard M Scott says
Thanks for this article, great explanations!.
I haven’t made it through the whole article, but I think your holiday_supplies example should have ‘christmas’ capitalized ‘Christmas’
Koren Leslie Cohen says
Thanks for the feedback – I’ve updated.
Hilliard M Scott says
In your single loop holiday_supplies.each needs a ‘do’ statement
and there are only two BBQ’s in the list instead of three, because “Labor Day” => “hot dogs”
And I guess the ‘tweet’ string is missing..
Koren Leslie Cohen says
Thanks for catching that. Not sure where the third BBQ came from!
Majid says
for the tweet, if you put hello workd in the beginning , it does not convert it to hi.
DailyWalk365 says
This has to be one of the best and most easy to follow explanation of nested arrays and hashes I’ve come across. Following your examples and using them in https://repl.it/ I was able to better understand how they work and how to iterate through them. Also, thanks for leaving suggested reading at the bottom. That was very thoughtful. To us new programmers this is a very helpful article.
Thank you
Daniel toma says
Are there situations in which you prefer to use .collect in lieu of .each ?
CASEY C HANNAN says
This is the best explanation / example i’ve found! Thanks for this.