Skip to content

Instantly share code, notes, and snippets.

@stacietaylorcima
Last active November 29, 2022 13:14
Show Gist options
  • Save stacietaylorcima/40c8da20a111fe5e489da0d6e343f651 to your computer and use it in GitHub Desktop.
Save stacietaylorcima/40c8da20a111fe5e489da0d6e343f651 to your computer and use it in GitHub Desktop.
Learn to use loops to iterate over Array objects and execute a block of code multiple times.

Ruby: Loops

Concept Description
Iteration To traverse or visit every element in an Array object
each The each method can be called on an Array object to iterate over the elements it contains
each_with_index Allows for an additional variable to be used in the pipe operators that will represent the index of the current element
map Can be used to iterate over every element in an Array while applying a method to each instance
Loop constructs A loop construct allows for iteration of an object for a given number of times or while a condition is met

Objectives

  • Understand the concept of iteration.
  • Apply the each method to iterate over an Array.

Loop: Loops allow a programmer to run a block of code a known or unknown number of times. They allow us to write less code. .

each

The each method allows us to loop through objects of the Array and Hash classes.

toy_chest = [
  "TMNT Figurines",
  "Tonka Truck",
  "Fort Legoredo",
  "Baseball",
  "Baseball Glove",
  "Motherboard"
]

toy_chest.each do |toy|
  p "I remember my #{toy}!"
end
  • toy is a stand-in for every element in the collection we are iterating over.

    • It's critical to use good variable names that represent what we are dealing with in order to improve the readability of the code.
  • next Method:

    • Imagine that we are emptying out our toy_chest to give the toys away for charity. We really love our Fort Legoredo Lego set and don't want to give it up.
    • We can use the next method to skip that toy.
toy_chest = [
  "TMNT Figurines",
  "Tonka Truck",
  "Fort Legoredo",
  "Baseball",
  "Baseball Glove",
  "Motherboard"
]

toy_chest.each do |toy|
  if toy == "Fort Legoredo"
    next
  else
    p "Removing #{toy}"
  end
end

# could also be written as follows:

toy_chest.each do |toy|
  next if toy == "Fort Legoredo" #inline next
  p "Removing #{toy}"
end

=begin
both return:

Removing TMNT Figurines
Removing Tonka Truck
Removing Baseball
Removing Baseball Glove
Removing Motherboard

=end
  • break Statement:
    • Allows us to break out of the loop and escape the block.
    • Let's do a quick search for an umbrella inside our closet.
    • We want to search the closet and as soon as we find the umbrella, we want to stop searching.
closet = [
  "Red hat",
  "White hat",
  "Black hat",
  "Umbrella",
  "Running shoes",
  "Red clown shoes"
]

closet.each do |item|
  p "Grabbing the #{item}"
  if item == "Umbrella"
    break
  else
    p "Not the #{item}"
  end
end

# could also be written as follows:

closet.each do |item|
  p "Grabbing the #{item}"
  break if item == "Umbrella" #inline break
  p "Not the #{item}"
end

=begin
both return:

Grabbing the Red hat
Not the Red hat
Grabbing the White hat
Not the White hat
Grabbing the Black hat
Not the Black hat
Grabbing the Umbrella

=end
  • Note: like the return statement, nothing below a break or next statement will be executed.

each_with_index

_Sometimes it's useful to know what position an element occupies in the Array. each_with_index allows us find o that information. _

  • Instead of one argument in, it takes two.
    • The first argument will be the current element.
    • The second argument will be the index.
numbers = [1,2,3]

numbers.each_with_index do |number, index|
  p "element #{number} is at index #{index}"
end

=begin

"element 1 is at index 0"
"element 2 is at index 1"
"element 3 is at index 2"

=end

map

If we want to transform the elements in the collection, we can use the map method.

bag = ["popcorn", "frozen pizza", "coffee", "donuts", 5]

bag.map do |item|
  next if item.is_a? Integer
  "Gluten-free #{item}"
end
  • In the case above, we are skipping the current iteration if the item is an integer.

  • The map method will iterate over all the elements in the bag and add the words "Gluten-free" before each item.

  • The program will return a new array with the gluten-free items but the bag collection remains unchanged.

  • map!

    • We can map it in place by using the mutator version of map called map!.
    • At first, map! might seem more complicated, but it’s really a simpler way to do an each to produce an Array.
    • For instance, the code above can be written like this:
bag = ["popcorn", "frozen pizza", "coffee", "donuts", 5]

result = []
bag.each do |item|
  next if item.is_a? Integer
  result << "Gluten-free #{item}"
end
# result is now the array of strings with a prefix of “Gluten-free”

for

The for loop construct works almost exactly like the each loop. The main difference is regarding scope. The variable for a for loop must be outside the scope of the call, while the variable of an each loop (the variable between the pipes) is thrown away after the block executes.

  • Let's take a look at the format used in a for loop:
#using the reserved word do
for variable in collection do
  #execute this block for each
end

#using a newline
for variable in collection
  #execute this block for each
end

#using a semicolon
for variable in collection ;
  #execute this block for each
end
  • A for loop begins with the reserved word for.
  • It's followed by a variable name that will be our stand-in for each element we'll be visiting in our loop.
  • in is another reserved word. It tells our for loop what object we want to iterate over.
  • That keyword is followed by the collection we want to iterate over.
  • Lastly, on that same line, an optional do or ; tells our loop that the loop body will begin after it.
    • Inside the loop, our code will execute for each item in the collection.
  • The loop is then closed with another reserved word, end.

Example: Let's take a look at some code that uses a for loop to iterate over an Array.

  • If the current element is a umber, it will print the result of squaring that number.
  • If the current element is a string, it will print "Can't square strings."
  • For anything else, it will print "Don't know what to do with that."
potential_numbers = [1, 2, 3, "word", 4, true, Hash.new, 5, [4], "another word"]

for element in potential_numbers
  if element.is_a? Numeric
    p element ** 2
  elsif element.is_a? String
    p "Can't square strings."
  else
    p "Don't know what to do with that."
  end
end

=begin
returns:

1
4
9
Can't square strings.
16
Don't know what to do with that.
Don't know what to do with that.
25
Don't know what to do with that.
Can't square strings.

=end

Exponenet operator: The ** operator is known as the Exponent operator. It raises the number on the left to the power on the right.


while

Sometimes we need the code to run until something changes. The while loop is a helpful construct for this.

Basic while loop formula:

while condition # while `condition` evaluates to **true**
  # run this awesome block of code until the condition evaluates to false
end

# with the optional `do`
while condition do
  # run this awesome block of code until the condition evaluates to false
end

# or optional backslash

while condition \
  # run this awesome block of code until the condition evaluates to false
end

while condition ;
  # run this awesome block of code until the condition evaluates to false
end

while Loop Construction:

  1. while: The loop begins with the keyword while and is followed by condition. It must be boolean.
  2. do, backslash, semicolon or newline: The condition is followed by an optional do, backslash, semicolon or newline that signals the end of the loop heading.
  3. The loop body will run until the condition evaluates as false.
  4. end: The end keyword closes the loop body.
  • The while loop has several good use cases, but it's important to note that it is also likely to result in an infinite loop.
    • It must be possible for the condition to be made false inside the loop, otherwise our loop won't stop executing.

Let's write a loop that spends our money until we're broke:

$money = 5 #global variables start with $

while $money > 0
  p "You still have #{$money} in the bank."
  $money -= 1 # -= is the decrement operator. It reduces the value of the variable on the left by the number on the right
end

=begin
returns:

You still have 5 in the bank.
You still have 4 in the bank.
You still have 3 in the bank.
You still have 2 in the bank.
You still have 1 in the bank.

=end
  • Global Scope Variables: The $ sign as the first character in a variable name will make that variable a global variable. That means that the variable will be available in the global scope of the program. We need a global variable here because our loop creates it's own local scope it doesn’t know about the regular variables out of the local scope.

until

The until loop construct is the inverse of the while loop. It works the same way, except that the loop executes until the condition evaluates as true.

Let's take a look at the format for the until loop:

until condition # until `condition` evaluates to **true**
  # run this awesome block of code until the condition evaluates to true
end

# with the optional `do`
until condition do
  # run this awesome block of code until the condition evaluates to true
end

until condition ;
  # run this awesome block of code until the condition evaluates to true
end

Rewrite the while loop money example using an until loop:

$money = 5 #global variables start with $

until $money < 1
  p "You still have #{$money} in the bank."
  $money -= 1
end

=begin
returns:

You still have 5 in the bank.
You still have 4 in the bank.
You still have 3 in the bank.
You still have 2 in the bank.
You still have 1 in the bank.

=end

times

We can use the times method to have our loop execute a specific number of times.

  • times is a method of the Integer class.
    • For this reason, it must be called on a number.
5.times do
  p "This prints 5 times"
end

=begin
returns:

This prints 5 times
This prints 5 times
This prints 5 times
This prints 5 times
This prints 5 times

=end

If we need to know the number of the current iteration of the loop, we can pass a variable with the block:

5.times do |i|
  p "Iteration #{i}"
end

=begin
returns:

Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4

=end

upto

The upto method is another method of the Integer class.

  • This method must be called on an number and called with an argument.
  • There's another method called downto that does the opposite and counts down.

Here's the formula for it:

lower.upto(upper) do |iterator|
  #body of loop
end
  • In this case, lower refers to the starting point in the loop.
    • That means that our iterator will start at this number, instead of 0.
  • upper will be the last number our iterator will execute on.
1.upto(10) do |num|
  if num.even?
    p "#{num} is an even number"
  else
    p "#{num} is an odd number"
  end
end

=begin
returns:

1 is an odd number
2 is an even number
3 is an odd number
4 is an even number
5 is an odd number
6 is an even number
7 is an odd number
8 is an even number
9 is an odd number
10 is an even number

=end
  • Nested Loops:
    • Sometimes we need to work with multiple layers.
    • Just as can nest conditional statements, we can also nest loops.
    • This is helpful when doing table operations. The outer loop could represent a column and the inner loop would represent a row. We can represent tables using multi-dimensional Arrays (Arrays with Array elements).
5.times do |i| #outer loop
  p "outer loop i: #{i}"
  2.times do |j| #inner loop
    p "inner loop j: #{j} for outer loop i: #{i}"
  end
end

=begin
returns:

outer loop i: 0
inner loop j: 0 for outer loop i: 0
inner loop j: 1 for outer loop i: 0
outer loop i: 1
inner loop j: 0 for outer loop i: 1
inner loop j: 1 for outer loop i: 1
outer loop i: 2
inner loop j: 0 for outer loop i: 2
inner loop j: 1 for outer loop i: 2
outer loop i: 3
inner loop j: 0 for outer loop i: 3
inner loop j: 1 for outer loop i: 3
outer loop i: 4
inner loop j: 0 for outer loop i: 4
inner loop j: 1 for outer loop i: 4

=end

Note: Notice that we have to use a different iterator name for the inner loop. This is because our inner loop has access to the entire scope where i exists. Using the same variable name would cause it to overwrite the outer loop's iterator value.


@stacietaylorcima
Copy link
Author

Exercises - Title Case

Define a Title class which is initialized by a String.

It has one method --fix-- that returns a title-cased version of the String:

Title.new("a title of a book").fix
#=> A Title of a Book

We will need to use conditional logic --if and else statements -- to make this work. Read the test specification carefully so you understand the conditional logic to be implemented.

Some helpful methods to use are:

  • String#downcase
  • String#capitalize
  • Array#include?

You can also use each_with_index to detect when you're on the first word, which should always be capitalized.

class Title

  attr_reader :string

  def initialize(string)
    @string = string
  end

  def fix
    articles = %w{a and the of}
    title_array = string.downcase.split(" ")
    modified_title = []

    title_array.each_with_index do |word, index|
      if index == 0 || !articles.include?(word)
        modified_title << word.capitalize
      else
        modified_title << word
      end
    end

    modified_title.join(" ")
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment