This is originally intended to be a further exploration attempt in one of the exercise problems in Launch School. Since I wanted to test my knowledge, I took the challenge and wrote an original solution. I am also training my fluency in explaining a code line-by-line, which is encouraged by Launch School. Thus, I created a detailed analysis. Before we dive into the analysis, let's do a quick summary of what this program does.
This bannerizer program takes a string as input, and outputs it within a box. For example:
print_in_box('')
+--+
| |
| |
| |
+--+
print_in_box('The quick brown fox jumps over the lazy dog')
+---------------------------------------------+
| |
| The quick brown fox jumps over the lazy dog |
| |
+---------------------------------------------+
The examples above look simple and straightforward. Put simply, a program that has ten or less lines of code can run the above strings without coming across any misalignments in the output. So far we only dealt with rather short text inputs, but what about a longer string that exceeds our console width? How do we wrap lines around so that they don't overflow or cause any misalignment issues? Take a look at the example below:
MAX_WIDTH = 76 # specifies the maximum content width in number of characters
print_in_box('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate vestibulum nisi. Nam maximus hendrerit eros non mattis. Fusce a pretium elit. Nulla ullamcorper turpis orci, eu accumsan tellus euismod suscipit. Pellentesque convallis dolor dolor.
Nam vitae lectus mauris. Duis posuere quis massa quis auctor. Phasellus porta fermentum lacus ac convallis. Nam id orci sit amet metus ornare sodales quis ac dolor.
Donec auctor commodo ligula, id luctus ligula egestas at. Donec euismod ut tellus non scelerisque. Nulla ut elit leo. Aliquam molestie in tortor ac congue. Fusce eget blandit velit.')
+------------------------------------------------------------------------------+
| |
| Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate ves |
| tibulum nisi. Nam maximus hendrerit eros non mattis. Fusce a pretium elit. N |
| ulla ullamcorper turpis orci, eu accumsan tellus euismod suscipit. Pellentes |
| que convallis dolor dolor. |
| |
| Nam vitae lectus mauris. Duis posuere quis massa quis auctor. Phasellus port |
| a fermentum lacus ac convallis. Nam id orci sit amet metus ornare sodales qu |
| is ac dolor. |
| |
| Donec auctor commodo ligula, id luctus ligula egestas at. Donec euismod ut t |
| ellus non scelerisque. Nulla ut elit leo. Aliquam molestie in tortor ac cong |
| ue. Fusce eget blandit velit. |
| |
+------------------------------------------------------------------------------+
The above example demonstrates the edge cases that needs to be considered if:
- Our input string contains multiple paragraphs separated by one or more line breaks
- We need to expand/ shrink our box width as desired
I intend to walk you through a step-by-step process addressing the above edge cases based on my current knowledge of understanding. The remaining portion of this article will explain my thought process and why I chose to write my code the way I wrote.
As I have spent much time writing this post, it may not be perfect. So feel free to comment below if you notice any errors, or need additional clarification on certain parts of the analysis.
MAX_WIDTH = 76
First, we specify the constant variable MAX_WIDTH
. This will be the maximum character width of the text string at any given line. We set this to be 76 (total box width of 80 minus 2 characters of padding on each side)
We then create our main method print_in_box
that takes in a text input as an argument. The text input can be a simple line of string or multiple paragraphs. We then initialize a method local variable content_width
to determine the width of our box:
def print_in_box(input_string)
content_width = [input_string.size, MAX_WIDTH].min
end
The content_width
resizes based on the character size of the input string, up to a width of 76 characters.
Before moving on to draw the box and output the string, we need to split the input text into multiple paragraphs, and subsequently into individual lines before formatting them into a wrapped-string output. We create a sub-method wrapped_output
. This sub-method aims to return an array of paragraphs containing appropriate line breaks to fulfil our wrapping criteria.
def wrapped_output(input_string, content_width)
paragraphs = input_string.split("\n")
end
We create an array paragraphs
within this method. We want paragraphs
to contain each paragraph as elements. We can do this by calling String#split
on the input text specifying \n
as the delimiter.
To simply demonstrate this implementation:
input_string = 'Sample first paragraph.
Sample last paragraph...'
input_string.split("\n")
# => ["Sample first paragraph.", "", "Sample last paragraph..."]
By calling String#split
on a string with two paragraphs, we have 3 elements returned in the array: the first paragraph, an empty string and the last paragraph. The empty string will be used to create an entire new line for line break output.
Next, we need to think about transforming each paragraph in paragraphs
into individual lines of strings. We can achieve this by calling Array#map
on paragraphs
:
def wrapped_output(input_string, content_width)
paragraphs = input_string.split("\n")
paragraphs.map do |paragraph|
number_of_lines = (paragraph.size / MAX_WIDTH) + 1
formatted_text = '' # initialize empty string
# code that perform splitting of paragraphs into individual lines
formatted_text # return formatted string
end
end
Each paragraph or empty string is passed into the map
block and in turn assigned to the local variable paragraph
. One way to transform each paragraph
into individual lines of strings is to add \n
s into the paragraph
string whenever we need a line break. We can put the formatted paragraph into a new variable formatted_text
before returning it as a block return value.
To determine how many lines we have in each paragraph
, we use the formula (paragraph.size / MAX_WIDTH) + 1
. This formula determines how many 76-character lines can occur in a single paragraph. It then adds 1
to account for the last line which, in many cases is less than 76 characters. For example, a 254-character paragraph will have 3 full lines and 1 additional line with spaces at the end.
This is perhaps the trickiest part of the problem. Essentially we want to perform a line-wrap and adding '|' on each end of each line. In our Array#map
block we will add a loop specifying i
as the counter.
paragraphs.map do |paragraph|
formatted_text = ''
number_of_lines = (paragraph.size / MAX_WIDTH) + 1
for i in (1..number_of_lines)
current_line = paragraph[MAX_WIDTH * (i - 1), MAX_WIDTH]
formatted_text += "| " + current_line.ljust(content_width) + " |\n"
end
formatted_text
end
The trick here is to extract 76 characters at a time from each paragraph
to form a full line. We can achieve this by using the formula paragraph[MAX_WIDTH * (i - 1), MAX_WIDTH]
.
The question here is perhaps, how do we start with extracting the first 76 characters of each paragraph
? And how can we continuously extract the next 76 characters at subsequent iterations?
The easiest approach here is perhaps calling String#[]
(element reference) on paragraph
. At the first iteration, the first 76 characters of paragraph
string is simply paragraph[0, 76]
, it means that we return 76
characters starting from index 0
(first character) of paragraph.
An example table below shows how each line is being extracted:
We use an example string with 254 characters:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate vestibulum nisi. Nam maximus hendrerit eros non mattis. Fusce a pretium elit. Nulla ullamcorper turpis orci, eu accumsan tellus euismod suscipit. Pellentesque convallis dolor dolor.'
Line Number(i ) |
String Index Position | Formula paragraph[MAX_WIDTH * (i - 1), MAX_WIDTH] |
current_line .size |
p current_line |
---|---|---|---|---|
1 | 0 to 75 | paragraph[0, 76] |
76 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate ves' |
2 | 76 to 151 | paragraph[76, 76] |
76 | 'tibulum nisi. Nam maximus hendrerit eros non mattis. Fusce a pretium elit. N' |
3 | 152 to 227 | paragraph[152, 76] |
76 | 'ulla ullamcorper turpis orci, eu accumsan tellus euismod suscipit. Pellentes' |
4 | 228 to 253 | paragraph[228, 76] |
26 | 'que convallis dolor dolor.' |
Note: MAX_WIDTH = 76
Formatting the current_line
is relatively simple, however, we need to consider when the current_line
has less than 76 characters especially in the last iteration, or when the current_line
is an empty string ''
If we add the lines "|" on both ends without considering this, the "|" at the right end will be misaligned.
To fix this, we call String#ljust
on current_line
, passing in the content_width
as the argument. That way, the last line will expand and match our designated maximum box width, while keeping its alignment to the left. An empty string will simply become an entire new line in our output. Note: every current_line that has an empty string '' will be converted to a full line with 76-character spaces as its content.
Lastly, we add the lines "|" to both ends of current_line
while adding a newline character \n
at the end. Each current_line
is then concatenated to the string variable formatted_text
formatted_text
will be our output paragraph string. This will also be the block return value of map
, which will be used to return a new array containing all of the formatted paragraph strings.
Now our wrapped_output
method returns an array of paragraphs and within it contains \n
s and |
s at designated line breaks. We will now write the remaining code to our main method:
def print_in_box(input_string)
content_width = [input_string.size, MAX_WIDTH].min
horizontal_rule = "+-#{'-' * content_width}-+"
top_bottom_padding = "| #{' ' * content_width} |"
puts horizontal_rule
puts top_bottom_padding
puts wrapped_output(input_string, content_width)
puts top_bottom_padding
puts horizontal_rule
end
In our main method print_in_box
, we 'draw' the horizontal lines and padding and call puts
on them.
We will also call puts
instead of other printing methods to our wrapped_output
transformed array.
Since we have already took care of the formatting and wrapping isseus, puts
will correctly print out all the elements in our transformed paragraphs
array.
As a feature of this program, we can also change the maximum width of our box as desired.
We can do this by changing the MAX_WIDTH
constant variable to our desired content width.
The change to a MAX_WIDTH from 76 to 50 correctly outputs our box:
`MAX_WIDTH = 50`
print_in_box('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate vestibulum nisi. Nam maximus hendrerit eros non mattis. Fusce a pretium elit. Nulla ullamcorper turpis orci, eu accumsan tellus euismod suscipit. Pellentesque convallis dolor dolor.
Nam vitae lectus mauris. Duis posuere quis massa quis auctor. Phasellus porta fermentum lacus ac convallis. Nam id orci sit amet metus ornare sodales quis ac dolor.
Donec auctor commodo ligula, id luctus ligula egestas at. Donec euismod ut tellus non scelerisque. Nulla ut elit leo. Aliquam molestie in tortor ac congue. Fusce eget blandit velit.')
+----------------------------------------------------+
| |
| Lorem ipsum dolor sit amet, consectetur adipiscing |
| elit. Donec vulputate vestibulum nisi. Nam maximu |
| s hendrerit eros non mattis. Fusce a pretium elit. |
| Nulla ullamcorper turpis orci, eu accumsan tellus |
| euismod suscipit. Pellentesque convallis dolor do |
| lor. |
| |
| Nam vitae lectus mauris. Duis posuere quis massa q |
| uis auctor. Phasellus porta fermentum lacus ac con |
| vallis. Nam id orci sit amet metus ornare sodales |
| quis ac dolor. |
| |
| Donec auctor commodo ligula, id luctus ligula eges |
| tas at. Donec euismod ut tellus non scelerisque. N |
| ulla ut elit leo. Aliquam molestie in tortor ac co |
| ngue. Fusce eget blandit velit. |
| |
+----------------------------------------------------+