Speaking to the RB109d Interview.

george hockett
Launch School
14 min readMay 24, 2023

--

Given that the RB109d interview study guide problems have the same format, this article is a “top-down” strategy for more rote practice of a PEDAC process. More or less, the following is what I attempted.

Some of the first things you learn about in programming are loops and their use for iteration. What is a Loop? How is it used to iterate? If you don’t know and want to know, don’t read this article, do read this https://launchschool.com/books/ruby. If you are a well-seasoned programmer, take the TL;DR route or prepare to be bored out of your gourd. However, if you are in Launch School studying to take the Ruby 119/109d interview assessment, read on. Hopefully, boredom (but not to the point of frustration!) sets in with the following. Boredom was a sign that I was ready to schedule my interview assessment.

The Familiar

The fact that arrays and strings have indexed elements determines the kind of tool we need to look within those objects. One of those tools is iteration. Simple iteration allows us to look but not see the usefulness of elements. Nor their connection to other data. Seeing the relationships of indexed elements requires the lens of a pattern or the filter of a conditional. The familiar, Array#each, Array#select, Array#map, and whatever else we can shop around for in Ruby-Docs, enable us to apply a lens or filter. Having passed the written exam, you know this. I know this. But this is not good enough for the interview. Making my way through the interview study guide, it was clear that I did not have the syntactical proficiency for a PEDAC process to describe how to iterate through the examples in the Watch Others Code videos.

The instruction material for the written exam and practice problems are excellent at teaching you how to work with and maintain your sense of location when working with nested data. Working with nested data such as arrays within arrays, arrays with hashes, arrays with strings, and on and on, we learn it is critically important to know what scope a line of code has and what the current line of code is returning. But much of the Ruby 109d interview study guide practice is an examination of a flat array or a string. So, haven’t we learned everything we need of Ruby syntax to pass the 119/109d interview? The interview is just a PEDAC process to demonstrate what we already know, right? For me, kind of. While slow, I could show I had the know-how to do the Watch Others Code examples. But what else could there be?

Also aliased as: slice

Slicing Strings

Slicing Arrays

Strings and arrays use the same syntax to return a substring or subarray of themselves. Again, I understood string[range], array[range], and string[start, length], array[start, length], but I didn’t have the kind of mastery needed for an interview exam. I needed a lot of time to use a [range] or [start, length] syntax. For example:

ary = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]

p ary[0..4] == ["a", "b", "c", "d", "e"] #=> true
p ary[0, 5] == ["a", "b", "c", "d", "e"] #=> true

# and

str = 'abcdefghij'

p str[0..4] == 'abcde' #=> true
p str[0, 5] == 'abcde' #=> true

On its own, the above is very simple. But it gets more complicated when trying to develop a PEDAC while explaining it to an observer and being timed. Try it on your own. Explain out loud, then type what you said out loud to describe the code snippet. Make sure to include what each line of code will be doing. Describe why the return values are the same, why the syntax is different, and what the differences are all before coding out the snippet.

Does what you said and wrote describe and explain what you want to explain? How long does it take?

An advantage I found with practicing this syntax is the identical use with strings or arrays. A rote syntax process takes meat off your plate as you chew into a problem. Using the same syntax formula, whether you are working with strings or arrays, as we will see later, you become far less dependent on having to know any single iterative method from the Ruby-Docs library. Being able to focus more on the process of PEDAC saves time in the interview. And if your interview problem is similar to the study guide practice problems, you have practiced how to get going. Knowing how to start under the pressure of an interview can be daunting. Having this gave me a big boost of confidence going into the interview exam.

Range Objects

Get to know them

If a standard method is handy in the exam and you can easily describe it, go for it! Also, the Ruby Style Guide advocates using functional code when possible, “avoiding mutation when it makes sense.” That’s a caveat.

Some interviewees in the Watch Others Code videos use range objects to iterate through an array or string. Something like this:

ary = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
(0...ary.size).each do |idx|
p ary[idx]
end

Output:
"a"
"b"
"c"
"d"
"e"
"f"
"g"
"h"
"i"
"j"
=> 0...10

str = 'abcdefghij'
(0...str.size).each do |idx|
p str[idx]
end

Output:
"a"
"b"
"c"
"d"
"e"
"f"
"g"
"h"
"i"
"j"
=> 0...10

At this point, it is a little more work to use than Array#each or decomposing the string to an array of characters and then using Array#each. Also, notice that the return value is a range object, in this case, 0…10.

Recreating Array#select or Array#map behavior is straightforward:

#select only the vowels
ary = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
selected = []
(0...ary.size).each do |idx|
if ary[idx] =~ /[aeiou]/
selected << ary[idx]
end
end

p selected #=> ["a", "e", "i"]

#transform the vowels to their ASCII order value
ary = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
transformed = []
(0...ary.size).each do |idx|
if ary[idx] =~ /[aeiou]/
transformed << ary[idx].ord
else
transformed << ary[idx]
end
end

p transformed #=> [97, "b", "c", "d", 101, "f", "g", "h", 105, "j"]

Not as elegant as the original Array#select and Array#map, but done with ease.

But here is where things start to get nifty. We are beginning to think and work in terms of behavior. Remember how slicing syntax is the same for both strings and arrays? What if we want to select from a string object or transform the characters of a string object? Which method is it? There is at least one. I don’t know. Wait…do I? Hold on, is it String#delete or String#gsub? No, those aren’t it…wait, oh yeah!! I don’t need to remember any of those because I can do that with a range object!

#select only the vowels
str = "abcdefghij"
selected_str = ""
(0...str.size).each do |idx|
if str[idx] =~ /[aeiou]/
selected_str << str[idx]
end
end

p selected_str #=> "aei"

#transform the vowels to their ASCII order value
str = "abcdefghij"
transformed_str = ""
(0...str.size).each do |idx|
if str[idx] =~ /[aeiou]/
transformed_str << str[idx].ord.to_s
else
transformed_str << str[idx]
end
end

p transformed_str #=> "97bcd101fgh105j"

While plenty of standard methods exist to modify a string and minor changes are needed to our code from a paragraph earlier, the thinking and the syntax are identical to get the same behavior for an array or a string. That is very useful.

Deleterious Behavior

Here is Array#map!’s destructive behavior and its string sibling:

#destructive transformation of the vowels to their ASCII order value

#array
ary = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
ary_id = ary.object_id

(0...ary.size).each do |idx|
if ary[idx] =~ /[aeiou]/
ary[idx] = ary[idx].ord
end
end

p ary #=> [97, "b", "c", "d", 101, "f", "g", "h", 105, "j"]
p ary_id == ary.object_id #=> true

#string
str = "abcdefghij"
str_id = str.object_id

(0...str.size).each do |idx|
if str[idx] =~ /[aeiou]/
str[idx] = str[idx].ord.to_s
end
end

p str #=> "97bcd101fgh105j"
p str_id == str.object_id #=> true

Array#select! and a selective string bomb:

#mutative selection of vowels

#array
ary = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
ary_id = ary.object_id

selected = []
(0...ary.size).each do |idx|
if ary[idx] =~ /[aeiou]/
selected << ary[idx]
end
end

ary[0..-1] = selected

p ary #=> ["a", "e", "i"]
p ary_id == ary.object_id #=> true

#string
str = "abcdefghij"
str_id = str.object_id

selected = ''
(0...str.size).each do |idx|
if str[idx] =~ /[aeiou]/
selected << str[idx]
end
end

str[0..-1] = selected

p str #=> "aei"
p str_id == str.object_id #=> true

Did you catch that? By re-assigning the full range of the original object’s indexes to the value of the selected elements, we get the behavior of Array#select!. It may not be surprising or noteworthy to some, having worked through the Ruby Collections Basics chapter, and it works for both [range] and [start, length] syntax:

#range syntax
str_1 = "Hello World"
p str_1.object_id == str_1[0..-1].object_id #=> false

ary_1 = ["G", "o", "o", "d" , "-", "b", "y", "e"]
p ary_1.object_id == ary_1[0..-1].object_id #=> false

#start, length syntax
str_2 = "Good-bye"
p str_2.object_id == str_2[0, str_1.size].object_id #=> false

ary_2 = ["H", "e", "l", "l", "o", " ", "W", "o", "r", "l", "d"]
p ary_2.object_id == ary_2[0, ary_2.size].object_id #=> false

At this point, I’m too naïve to say with authority why this works beyond what you will find in Launch School’s RB101 / RB110 courses. Does the fact that I find the destructive behavior of objects cool make me a nerd or a geek? I don’t know that either. Worrying whether I’m one or the other certainly makes me a dork. All right, moving on.

sub_not_one[0..3] == sub_not_one[0, 4]

Earlier, I described iteration as a device for employing a lens or filter to the elements of an indexed object. Array#each, Array#select and Array#map give us this. But these can’t examine anything other than single elements. Using a range object to examine longer subarrays or substrings is straightforward as well:

#Write a method that takes a string as an argument and outputs the number of
#substrings that are made of four alphabetic characters.

def four_long_sub(str)
result = 0
(0...str.length).each do |idx|
sub = str[idx, 4] #Just change the length to whatever fits in the object
result += 1 if sub == sub.delete('^a-zA-Z') && sub.size == 4
end
result
end
=begin
def four_long_sub(str)
result = 0
(0...str.length).each do |idx|
sub = str[idx..idx + 3]
result += 1 if sub == sub.delete('^a-zA-Z') && sub.size == 4
end
result
end
=end
p four_long_sub('abcd') == 1 #=> true
p four_long_sub('a xyz') == 0 #=> true
p four_long_sub("I cannot say that I don't disagree with you.") == 10 #=> Groucho Marx
p four_long_sub('floccinaucinihilipilification') == 26 #=> true

I included the second version of the four_long_sub method that uses a range syntax, sub = str[idx..idx + 3]. It shows the aesthetic difference. Not to say that [start, length syntax] always looks better, but in this case, [start, length] syntax makes it easier to describe and understand. Remember, the interview is your chance to impress. Minor differences add up to cleaner, more readable PEDAC and code. Subsequently, it gives the interviewer evidence of your ownership of the material.

Is it a tactic or a strategy?

Till now, the review of slicing syntax and my expansion on range objects for iteration has been more of a “bottom-up” approach. Since there tends to be a pattern to the kind of algorithms needed to complete the study guide and practice exercises, a specific way of practicing for the interview is helpful. What is next is not a panacea for the interview but my approach to practice for the RB109d interview. Shall we?

Loops within Loops

Working with nested data very often means using a loop within a loop. However, these loops are used to excavate the strata of nested data. Once at a level we want to work, the size of the slice (i.e. granularity) of the loop’s iteration is unchanging. Some library methods like Array#combination allow us to change that granularity. However, implementing a library method that will change granularity during iteration can be awkward.

Now we need to remove some ambiguity from the phrase “loop within a loop.” A loop within a loop for flat indexed objects needs to cycle the granularity of the iterations. So let’s call the inner loop a dependent iterator since the granularity of its iteration is reliant on what the outer loop is giving it. And let’s call the outer loop the independent iterator because its current iteration granularity is unchanging as the inner iterator progresses. Some of this will sound similar to those who have studied the scientific methodology of defining independent and dependent variables for experimental research. But, we are defining methodical researchers of the Launch School codex.

The new terminology is a tool to form a clearer mental model of the study guide and practice problems. But I don’t think using the above terminology when you practice and interview is wise. Simply stating “outer iterator” and “inner iterator” is clear enough because both you and the interviewer are looking at the same problem.

Building Iterations

Let’s say we need to find all the possible contiguous subarrays of an array or substrings of a given string. The slicing syntax built by the iterators could look like the following:

=begin

[Independent Iterator..Dependent Iterator]
[ Lower Index .. Upper Index ]

[0..0] [0..1] [0..2] [0..3] -> First index with increasing range.
[1..1] [1..2] [1..3] [1..4] -> Second index with increasing range.
[2..2] [2..3] [2..4] [2..5] -> Third index with increasing range.
...etc...

[Independent Iterator, Dependent Iterator]
[ Start Index , Length ]

[0, 1] [0, 2] [0, 3] [0, 4] -> First index start with increasing length.
[1, 1] [1, 2] [1, 3] [1, 4] -> Second index start with increasing length.
[2, 1] [2, 2] [2, 3] [2, 4] -> Third index start with increasing length.
...etc...

=end

As is shown, both [range] and [start, length] slicing can have the same behavior (slices returned) but need to be built out a little differently:

#copy; paste; execute!

def range_slice(obj)
(0...obj.size).each do |lower_idx| #independent iterator
(lower_idx...obj.size).each do |upper_idx| #dependent iterator
slice = obj[lower_idx..upper_idx]
p slice
end
end
end

range_slice("abcde")
range_slice(['a', 'b', 'c', 'd', 'e'])

def start_length_slice(obj)
(0...obj.size).each do |start_idx| #independent iterator
(1..obj.size).each do |length| #dependent iterator
slice = obj[start_idx, length]
next if length > slice.size #eliminates repeated slices
p slice
end
end
end

start_length_slice("abcde")
start_length_slice(['a', 'b', 'c', 'd', 'e'])

The other way to build the slicing syntax is by swapping what becomes the independent and dependent iterators:

=begin
[Dependent Iterator.. Independent Iterator]
[ Lower Index .. Upper Index ]

[0..0] [1..1] [2..2] [3..3] -> One element range at an increasing lower index.
[0..1] [1..2] [2..3] [3..4] -> Two element range at an increasing lower index.
[0..2] [1..3] [2..4] [3..5] -> Three element range at an increasing lower index.
...etc...

[Dependent Iterator, Independent Iterator]
[ Start Index , Length ]

[0, 1] [1, 1] [2, 1] [3, 1] -> One element length at an increasing start index.
[0, 2] [1, 2] [2, 2] [3, 2] -> Two element length at an increasing start index.
[0, 3] [1, 3] [2, 3] [3, 3] -> Three element length at an increasing start index.
...etc...
=end

Which results in this:

#copy; paste; execute!

def swapped_range_slice(obj)
(0...obj.size).each do |upper_idx| #independent iterator
(0...obj.size).each do |lower_idx| #dependent iterator
slice = obj[lower_idx..upper_idx + lower_idx]
next if upper_idx >= slice.size #eliminates repeated slices
p slice
end
end
end

swapped_range_slice("abcde")
swapped_range_slice(['a', 'b', 'c', 'd', 'e',])

def swapped_start_length_slice(obj)
(1..obj.size).each do |length| #independent iterator
(0...obj.size).each do |start_idx| #dependent iterator
slice = obj[start_idx, length]
next if length > slice.size #eliminates repeated slices
p slice
end
end
end

swapped_start_length_slice("abcde")
swapped_start_length_slice(['a', 'b', 'c', 'd', 'e'])

As you can see, all four versions are pretty close in the form of their respective syntax. But if you aren’t well-practiced with each, you will likely get lost under the pressure of an interview exam.

Are you getting bored yet?

PEDAC examples

To wrap up, I’ll leave you with example variations of how I used this slicing syntax in practicing PEDAC for the interview. I hope all this helps in your interview studies. Cheers.

=begin
Launch School Watch Others Code example
Part 3 video, First Problem

# The maximum sum subarray problem consists in finding the maximum
# sum of a contiguous subsequence in an array of intergers.

# Max Sequence [-2, 1, -3, 4, -1, 2, 1, -5, 4]
# -- Should be 6: [4, -1, 2, 1]
# Easy case is when the array is made up of only positive numbers
# and the maximum sum is the sum of the whole array. If the array
# is made up of only negative numbers, return 0 instead.

# Empty array is consisdered to have zero greatest sum. Note that
# the empty array is also a valid subarray.

Problem: Find the largest sum of a subarray taken from a given
array of integers. The smallest sum is 0 even if it is an empty
array or all negative integers.

The whole input can be considered a subarray.

Example:
[] -> 0, Empty array
[-1, -2] -> 0, all numbers are negative.
[11] -> 11, simply return the whole sum because numbers are positive.
[-2, 1, -3, 4, -1, 2, 1, -5, 4] -> 6 from the sub array [4, -1, 2, 1]

Data structure:
Input is an array
Output is an integer that is 0 or greater.

Iterate through all possible continguous subarrays
taken from the input array.

Algorithm:
->Name a default result of 0

->Outer Iterator will step through all the possible subarray lengths
of the input array.
->Inner iterator will step through each index on the input array.
->If a current subarray length is smaller than the current outer
iterator value
-> Skip to the next iteration
->If the current subarray's summed value is larger than the
current result value.
->Reassign the result value to the current subarray's summed
value

->return the current result value

Code:
=end
def max_sequence(ary)
greatest_sum = 0

(1..ary.length).each do |sub_length|
(0...ary.length).each do |start_idx|
sub = ary[start_idx, sub_length]
next if sub.size < sub_length
if sub.sum > greatest_sum
greatest_sum = sub.sum
end
end
end

greatest_sum
end

p max_sequence([]) == 0
p max_sequence([-2, 1, -3, 4, -1, 2, 1, -5, 4]) == 6
p max_sequence([11]) == 11
p max_sequence([-32]) == 0
p max_sequence([-2, 1, -7, 4, -10, 2, 1, 5, 4]) == 12
=begin
Launch School Watch Others Code example
Part 4 video, First Problem.

# Given 2 strings, your job is to find out if there is a substring
# that appears in both strings. You will return true if you find a
# substring that appears in both strings, or false if you do not.
# We only care about substrings that are longer than one letter long.

Problem restated:
Do two strings contain a substring that is the same within
both strings. The matching substring should be at least two
characters long. Single matching characters don't return true.

Any matching substring that is two characters long will qualify as
true.

Substrings characters should be in the same order and relative
position as they are in the inputs

Examples:
('Something', 'Fun') == false -> no matching subs
('Something', 'Home') == true -> 'om' matches
('BANANA', 'banana') == true -> whole string matches, case is not considered

Data Structure:
Input: two strings
Output: Boolean

string iteration -> Find all two character substrings

Algorithim:
->Name new strings of the inputs that are all lowercase.

->Outer iterator will step through each index of the first lower case input.
->Inner iterator will step through each index of the second lower case
input.
->Only evaluate substrings that are two characters long.
->If a substring from the first input is equivalent to a
substring from the second input.
->Return true

->Otherwise return false

Code:
=end
def substring_test(str1, str2)
down_1 = str1.downcase
down_2 = str2.downcase

(0...str1.size).step do |start_1|
(0...str2.size).step do |start_2|
next if down_2[start_2, 2].size < 2
return true if down_1[start_1, 2] == down_2[start_2, 2]
end
end
false
end

p substring_test('Something', 'Fun') == false
p substring_test('Something', 'Home') == true
p substring_test('Something', 'Fun') == false
p substring_test('Something', '') == false
p substring_test('', 'Something') == false
p substring_test('BANANA', 'banana') == true
p substring_test('test', 'lllt') == false
p substring_test('', '') == false
p substring_test('1234567', '541265') == true
p substring_test('supercalifragilisticexpialidocious', 'SoundOfItIsAtrociou') == true

--

--