Sitemap
Marilyn Roxie

Marilyn Roxie

Student at Launch School.

Visualizing Ruby Collections and Methods in RB110

12 min readNov 3, 2024

--

Press enter or click to view image in full size
Photo by Mona Eendra on Unsplash

In Launch School's RB110, we learn more about working with collections like arrays and hashes and treating strings like collections. Even though strings aren't actual collections and are one object rather than containing multiple objects, characters can be accessed by index, and strings have access to methods that enable iteration and manipulation, e.g. chars returning a string as an array of characters.

In RB110, we encounter nested collections and combinations of arrays and hashes at various levels. Arrays with subarrays, hashes with hashes as values, arrays with hashes as elements, and more. Any Ruby object can be a hash key, value, or array element.

Collections and their methods can get complicated quickly!

I was having difficulty visualizing the state of a collection, such as whether it had nested levels and whether multiple methods were being used on return values of inner blocks or being chained together. My goals have been to:

  • be able to better picture the state of a collection and elements in it on the way to the desired return value
  • decrease the chances of feeling of being surprised by the return value
  • examine the code and understand exactly where things went wrong and correct it if there is an error or I am surprised by the return value

To help keep track of the value of the collection and collection method return values, I've been exploring tools useful for visualizing collections, as well as the methods to iterate, transform, and select that you can use on them.

In this article, I'll cover console output with p, pp, pry, and the Ruby gemamazing_print, utilizing the AI assistant Claude's capacity to create diagrams, and Obsidian's Canvas feature and Excalidraw plugin.

P and Pretty Printing

Press enter or click to view image in full size
Photo by Debby Hudson on Unsplash

Sometimes, all you need is one or morep methods to understand and start training yourself to understand what will happen in every iteration, transformation, or selection by seeing its output in the console. You may want to review the difference between puts, print, and p if you're not sure when to use which method.

Take a look at this example of a multidimensional array:

arr = [[5, 10, 15], ['yes', 'no', 'perhaps']]

arr.map do |sub_arr|
sub_arr.select do |element|
element.is_a?(Integer) && element >= 10 || element.is_a?(String) && element.length < 5
end
end

If you're having trouble visualizing what is happening at every level - p should be your go-to method. I've added additional comments myself here to clarify what we would see in the console output:

arr = [[5, 10, 15], ['yes', 'no', 'perhaps']]

arr.map do |sub_arr|
p sub_arr
sub_arr.select do |element|
p element
p element.is_a?(Integer) && element >= 10 || element.is_a?(String) && element.length < 5
end
end

# The first subarray, referenced by sub_arr:
# [5, 10, 15]

# Every element in each subarray is checked for
# whether it is greater than 10 if it's an integer
# or whether it is less than 5 characters if its a string

# 5
# false

# 10
# true

# 15
# true

# The second subarray, referenced by sub_arr:
# ["yes", "no", "perhaps"]

# "yes"
# true

# "no"
# true

# "perhaps"
# false

# The return value is an array containing the elements
# that select filtered from each subarray:
# => [[10, 15], ["yes", "no"]]

You can also use string interpolation to add your own context to clarify the output further. Note that depending on the code, you may need to add, e.g., the condition check again to avoid altering the expected return value.

arr = [[5, 10, 15], ['yes', 'no', 'perhaps']]

arr.map do |sub_arr|
p "The current subarray is #{sub_arr}."
sub_arr.select do |element|
p "The current element is #{element}."
p "The evaluation is #{element.is_a?(Integer) && element >= 10 || element.is_a?(String) && element.length < 5}."
element.is_a?(Integer) && element >= 10 || element.is_a?(String) && element.length < 5
end
end

# "The current subarray is [5, 10, 15]."
# "The current element is 5."
# "The evaluation is false."
# and so on...

While not as amazing, as you'll see in the next section about Amazing Print, you can also look intopp (Ruby docs). Since Ruby 2.5, pp has been included by default; before that, it was a gem that needed require pp.

Let's compare thep output with pp:

p fruits = [
{ apple: 'a', banana: 7 },
[{ grape: 8, orange: 9 }, :orange],
:banana,
{ 10 => :kiwi, 20 => :mango }
]

# [{:apple=>"a", :banana=>7}, [{:grape=>8, :orange=>9}, :orange], :banana, {10=>:kiwi, 20=>:mango}]

pp fruits = [
{ apple: 'a', banana: 7 },
[{ grape: 8, orange: 9 }, :orange],
:banana,
{ 10 => :kiwi, 20 => :mango }
]

# [{:apple=>"a", :banana=>7},
# [{:grape=>8, :orange=>9}, :orange],
# :banana,
# {10=>:kiwi, 20=>:mango}]

With thepry gem, we can also see color coding compared with the plain output of p:

require 'pry'

arr = [[5, 10, 15], ['yes', 'no', 'perhaps']]

Pry::ColorPrinter.pp(arr)
p arr
Press enter or click to view image in full size
require 'pry'

fruits = [
{ apple: 'a', banana: 7 },
[{ grape: 8, orange: 9 }, :orange],
:banana,
{ 10 => :kiwi, 20 => :mango }
]

Pry::ColorPrinter.pp(fruits)
p fruits

In the case of this nested hash, we can see a more organized output:

Press enter or click to view image in full size

Stepping through code with pry is also very helpful for more clearly understanding and improving visualization of what happens at each point in a program.

Since usingpry in this way is covered extensively via Launch School's material already, I won't detail that here. I wanted to remind everyone that pryis a great way to train your ability to picture what should happen at every point in your code. You can learn about installing pry and using it for debugging here:

pry is covered extensively in the Launch School Core Curriculum in RB101: Lesson 2 - Debugging and RB110: Lesson 3 - Debugging with Pry.

Amazing Print

Press enter or click to view image in full size
Photo by Robert Katzki on Unsplash

Amazing Print is forked from Awesome Print, which is no longer maintained. As of the time of writing, Amazing Print has been updated in the past year.

gem install amazing_print should work in your terminal, but check the repo for complete details.

You can then use Amazing Print by typing require "amazing_print" at the top of your Ruby file or when you begin an irb session. Though you can do many things with Amazing Print, like specify custom colors for different objects, I'll focus on visualizing collection levels out of the box here.

To see your code amazingly printed, you type amazing_print or simply ap before the name of the object you want to print. For example, the code in this Ruby file...:

require 'amazing_print'

fruits = [
{ apple: 'a', banana: 7 },
[{ grape: 8, orange: 9 }, :orange],
:banana,
{ 10 => :kiwi, 20 => :mango }
]

ap fruits

…results in this output with color coding seen in the terminal:

Amazing!

Untangling the levels of nesting going on here is much clearer now.

For example, we can see at a glance above that fruits[3][20] would allow us to reference the value:mango.

Let's have a look at an array of hashes representing Final Fantasy VII characters and their characteristics…:

require 'amazing_print'

ffvii_arr_of_hashes = [
{ name: 'Cloud', age: 21, details: { weapon: 'Buster Sword', affiliation: 'Avalanche' } },
{ name: 'Tifa', age: 20, details: { weapon: 'Gloves', affiliation: 'Avalanche' } },
{ name: 'Barret', age: 35, details: { weapon: 'Gun Arm', affiliation: 'Avalanche' } }
]

ap ffvii_arr_of_hashes
Press enter or click to view image in full size

And a hash with arrays as values:

require 'amazing_print'

ffvii_hash_arr_values = {
cloud: [21, 'Buster Sword', 'Avalanche'],
tifa: [20, 'Gloves', 'Avalanche'],
barret: [35, 'Gun Arm', 'Avalanche']
}

ap ffvii_hash_arr_values

Try Amazing Print out on any collection for a more easily digestible view of all the levels.

Claude Artifacts and Mermaid Diagrams

Press enter or click to view image in full size
Photo by delfi de la Rua on Unsplash

Claude can generate Artifacts of Mermaid diagrams based on your prompt specifications.

Claude is a large language model (LLM) AI assistant. This page at Descript explains both Artifacts and Mermaid well:

Artifacts are designed for you to modify, iterate on, and take ownership of. They’re typically intended for use outside the chat, such as in reports, emails, or presentations. They should be self-contained and understandable without additional context from the conversation.

Mermaid diagrams can visualize various processes, workflows, or relationships using flowcharts, Gantt charts, mind maps, and other visualizations.

My prompt was code I wrote myself…:

[['ah', 'ah', 'ah'], ['oh', 'oh'], ['eef', 'if', 'of']].map do |subarr|
subarr.map do |word|
word.reverse
end
end

…as well as the instruction: How would you visualize what's happening in the code?

Press enter or click to view image in full size
Claude desktop interface

Claude then outputs a Mermaid visualization of the code:

Press enter or click to view image in full size

The Artifact is available here for zooming and panning around. You can see this Artifact because I chose to publish it. Artifacts are unpublished by default, so don't worry if you try them out.

Get Marilyn Roxie’s stories in your inbox

Join Medium for free to get updates from this writer.

Accompanying Claude's description of what's going on in the code and visualization:

To visualize what's happening in this code, we can create a step-by-step diagram that shows the transformation process. Here's how we could represent it:

This diagram visualizes the nested map operation:

1. We start with the original nested array at the top.

2. The outer map iterates over each subarray.

3. For each subarray, an inner map is applied.

4. The inner map reverses each word in the subarray.

5. The results of each inner map are collected into new subarrays.

6. Finally, the outer map collects these new subarrays into the final result.

This visualization helps to see how each level of nesting in the original array is processed, and how the transformations occur step-by-step. It clearly shows the input, the intermediate steps, and the final output, making it easier to understand the flow of data through the nested map operations.

Be careful with diagrams and descriptions; sometimes, Claude doesn't get things right. Understanding the concepts well enough to spot if something is wrong with the output is essential.

If you don't like the visualization, you can also click 'Retry' after getting the output or change your prompt somewhat to see if you like the results better. If Claude didn't do what you originally had in mind, ask for specific parts to be labeled in a certain way. For example, "Can you redo this and label what an array and a hash are?"

In this Artifact, I requested color coding. The labels are a little different from before as well:

Press enter or click to view image in full size

Here are some artifacts to visualize the data structure examples from earlier in this article too:

fruits (Artifact)

ffvii_arr_of_hashes (Artifact)

ffvii_hash_arr_values (Artifact)

Original subarray example (Artifact)

Just for fun, here are a couple of additional examples for inject a method I initially had trouble understanding. Seeing these visualizations helped a great deal.

numbersand inject (Artifact)

numbers = [1, 2, 3, 4, 5]

numbers.map { |n| n * 2 }.inject(:+)
Press enter or click to view image in full size

str and inject (Artifact):

str = "Hello World"
str.chars.select { |char| char =~ /[A-Z]/ }.map(&:ord).inject(:+)
Press enter or click to view image in full size

Obsidian Canvases

As I detailed in a previous article, Tools to Prep for RB109 Exams: Obsidian, Anki, and More, Obsidian is my markdown editor of choice. However, I ignored its Canvas feature until needing to explore collections more deeply.

If you haven't used Obsidian before, you can probably skip this section and the following one about Excalidraw. Nothing would stop you from creating your own diagram style in other software, on a whiteboard, or with pen and paper!

If you are interested in or already using Obsidian, I recommend this article for understanding more about using Canvases:

One gripe I had with Obsidian Canvas is the extremely small size of the text in the boxes. I use this CSS snippet to increase the font size:

.canvas-node-container p,
.canvas-node-container code,
.markdown-source-view.node-insert-event.mod-inside-iframe>.cm-editor>.cm-scroller {
font-size: 30px;
}

If you want to increase the size of the code block font in general, including outside of Canvases, you can visit here for more details.

You can adjust the height of canvas cards by double-clicking on the bottom of them.

Here is an example of a nested hash I visualized as an Obsidian canvas:

Press enter or click to view image in full size
Obsidian Canvas

Of course, it takes more time to lay out and label everything by hand in these ways rather than using the methods detailed in the sections above. However, if you are having trouble visualizing what is going on in a collection or with methods operating on one, taking the time to outline everything like this can help cement the concepts further, much like how just creating flashcards can help seal in information before you even begin to review them.

Obsidian Plugin Excalidraw

From the creator's description on the Obsidian forums:

Excalidraw is a very powerful yet simple to use sketching tool. It allows you to integrate quick sketches, user interface deigns, even simple charts, etc. into your notes in Obsidian.

You can try a minimal version of Excalidraw in-browser, but the version I'm referring to is an Obsidian community plugin.

Excalidraw is a powerful tool that I have only begun to use recently. Thankfully, there are many tutorial videos. I suggest watching these two first:

Here is the same diagram I did in Canvas, but this time in Excalidraw, which offers more flexibility for how to illustrate and connect elements:

Press enter or click to view image in full size

You can even bring Mermaid diagrams like the ones you create with Claude into Excalidraw.

In Claude, you can copy the text in the 'Code' box:

Press enter or click to view image in full size

Start a new drawing in Excalidraw, click the AI tool on the top right, then 'Mermaid to Excalidraw' and paste your code.

You can then edit fonts, change the size of boxes, and add any additional notes or diagrams you like. Here is how it was rendered by default; you can personalize the colors of the boxes, font, size of the boxes, and, of course, all the notes you may want to make around the diagram.

Excalidraw is such a robust tool that I recommend watching this playlist of videos to understand more about everything you can do with it.

I hope you found these techniques and tools helpful. Is there a visualization strategy that I missed that would be useful for Ruby collections and the methods that act on them? Let me know!

--

--