Ben Sinkhorn
Ben Sinkhorn

Reputation: 11

How to use a for loop on an array in Ruby

I need to use a for loop to take the last four strings off the more_stuff array.

Here is my code:

# assigns string Apples Oranges Crows Telephone Light Sugar to variable 
ten_things
ten_things = "Apples Oranges Crows Telephone Light Sugar"

# prints Wait there are not ten things on that list. Let's fix that.
puts "Wait there are not 10 things in that list. Let's fix that."

# first seperates string ten_things into an array at space character, then 
# assigns the new array to variable stuff
stuff = ten_things.split(' ')
# assigns array to more stuff with strings, Day, Night, Song, Frisbee, Girl, 
# and Boy
more_stuff = ["Day", "Night", "Song", "Frisbee", "Corn", "Banana", "Girl", 
"Boy"]

# using math to make sure there's 10 items

# assigns fifth through eighth elements in array more_stuff to array stuff_3
stuff_3 = more_stuff[4..8]
# prints array stuff_3 in brackets
puts "#{stuff_3}"
# for all strings in stuff_3
stuff_3.each do |stuff_3|
  # pops the last item off of stuff_3
  next_one = stuff_3.pop
  # puts adding (next_one)
  puts "Adding #{next_one}"
  # adds (next_one) to array stuff
  stuff.push(next_one)
# ends for loop
end

Also here is the error that comes up when I run it from Powershell:

Wait there are not 10 things in that list. Let's fix that.
["Corn", "Banana", "Girl", "Boy"]
ex38for.rb:17:in `block in <main>': undefined method `pop' for "Corn":String 
(NoMethodError)
    from ex38for.rb:16:in `each'
    from ex38for.rb:16:in `<main>'

I'm confused how for loops work, specifically each and where to put stuff in the array command.

Upvotes: 1

Views: 1449

Answers (3)

Sebasti&#225;n Palma
Sebasti&#225;n Palma

Reputation: 33420

What pop does is to take the last element or value on an array (when is used without specifying the number of elements):

p stuff_3.pop
# => "Boy"
p stuff_3.pop(2)
# => ["Banana", "Girl"]

But in your case you're trying to use pop with an element that's inside the main array.

If you check it doing it outside your each method:

puts stuff_3.pop
# => Boy

Then that will print Boy because is the last element within the stuff_3 array which you declare as more_stuff[4..8]:

stuff_3 = more_stuff[4..8]
p stuff_3
# => ["Corn", "Banana", "Girl", "Boy"]

But then, when you do stuff_3.each do |stuff_3| you're using the same name stuff_3 to access each element inside that array, which has the same name. So there's where you're getting the undefined method 'pop' for "Corn":String error, because pop is waiting for an array and if you iterate over each element inside stuff_3 you're getting String elements.

A possible solution is that you use a different name to access the elements when you use each with the stuff_3 array, maybe just as stuffs, and that will give you "Adding Boy" and "Adding Girl".

Or maybe any word to refer the elements inside stuff_3 being different to this one could work, because you're not accessing those elements, you also could use _ to specify they're not being used:

stuff_3.each do |_|
  next_one = stuff_3.pop
  puts "Adding #{next_one}"
  stuff.push(next_one)
end

This would be stuff before pushing the elements:

["Apples", "Oranges", "Crows", "Telephone", "Light", "Sugar"]

And after:

["Apples", "Oranges", "Crows", "Telephone", "Light", "Sugar", "Boy", "Girl"]

Upvotes: 3

eiko
eiko

Reputation: 5345

Let's focus on this part of your code:

stuff_3.each do |stuff_3|
  # pops the last item off of stuff_3
  next_one = stuff_3.pop
  # puts adding (next_one)
  puts "Adding #{next_one}"
  # adds (next_one) to array stuff
  stuff.push(next_one)
# ends for loop
end

This code: stuff_3.each do |stuff_3| is a loop which takes each item in stuff_3, saves it to a local variable (which you have also called stuff_3) and executes the following code with it.

So when you call stuff_3.pop, you're actually calling pop on the local variable which contains only one item, instead of the array containing all the items. Because of this, you get the error:

undefined method `pop' for "Corn":String

Which is telling you that you're calling pop on a single item (the String "Corn" in this case), instead of an array of items like you expected.

One way to fix this is to change your code like so:

stuff_3.each do |next_one|
  # puts adding (next_one)
  puts "Adding #{ next_one }"
  # adds (next_one) to array of stuff
  stuff.push(next_one)
# ends for loop
end

This code will go through each item in stuff_3 and add it to stuff. But it may work differently than you expect:

  • stuff_3.each will iterate through items from first to last, whereas pop takes the last item first, meaning that this code will add items to your array in opposite order.
  • stuff_3.each goes through items without removing them, whereas pop will remove each item, eventually leaving your stuff_3 array empty.

If you don't want these variations, you can still use pop like so:

stuff_3.times do |i|
  #pops the last item off of stuff_3
  next_one = stuff_3.pop
  # puts adding (next_one)
  puts "Adding #{next_one}"
  # adds (next_one) to array of stuff
  stuff.push(next_one)
# ends for loop
end

This code uses stuff_3.times to execute a loop as many times as there are items in stuff_3. So if stuff_3 has 3 items in it, the loop executes 3 times. The variable i is a counter that starts at 0 and counts up each time the loop is executed. In this case, times works much more like the traditional for-loop and the above code will execute pretty much exactly like you expected yours to execute.

Upvotes: 0

Tom Lord
Tom Lord

Reputation: 28305

You are shadowing the outer variable:

stuff_3.each do |stuff_3|
  stuff_3.pop

Within this block of code, the "inner" stuff_3 variable takes precedence over the "outer" stuff_3 variable.

That's why you are seeing an error message:

undefined method `pop' for "Corn":String

Shadowing outer variables like this is generally considered bad practice, as it leads to confusing code and "unexpected" behaviour (like what you found here!). A simple fix is to just use a different variable name:

stuff_3.each do |stuff_3_item|
  stuff_3.pop

...Although I think what you're actually trying to do here should be written a little differently - e.g.

stuff_3.each do |stuff_3_item|
  puts "Adding #{stuff_3_item}"
  stuff.push(stuff_3_item)
end

Upvotes: 2

Related Questions