3

I'm very new to Ruby and learning the basics. I'm trying to figure out the maximum value in an array, the position of that value in the array and print it out. I ran into a strange problem that I'm hoping someone can help explain.

Here's my code:

highest_grocery = 0
#This loop will iterate over all the grocery expenditures to find the maximum
expenses["groceries"].length.times do |i|
  if highest_grocery < expenses["groceries"][i]
    highest_grocery = expenses["groceries"][i]
    friend_num_grocery = i + 1
  end

end
print "Friend #{friend_num_grocery} paid the most for groceries. With a total grocery bill of: $#{highest_grocery}"

When I run this, I get an error that says undefined local variable or method friend_num_grocery for main:Object.

I struggled with this for a while, but by chance I found that if I created the friend_num_grocery variable earlier, it would work fine, like this:

highest_grocery = 0
friend_num_grocery = 0
#This loop will iterate over all the grocery expenditures to find the maximum
expenses["groceries"].length.times do |i|
  if highest_grocery < expenses["groceries"][i]
    highest_grocery = expenses["groceries"][i]
    friend_num_grocery = i + 1
  end

end
print "Friend #{friend_num_grocery} paid the most for groceries. With a total grocery bill of: $#{highest_grocery}"

Does anyone know why the first one did not work, but the second one did? Thank you!

4
  • 1
    Where is friend_num_grocery defined in the first example? If you're intending it to be defined at the bottom of the loop, you'll need to look into "variable scoping". It's an extremely important concept to understand. Commented Sep 3, 2017 at 0:26
  • Basically when you declare it in the do/end block, it will only be visible in that block. Commented Sep 3, 2017 at 0:44
  • 1
    Possible duplicate of Ruby local variable scope Commented Sep 3, 2017 at 2:50
  • This is not a duplicate of the nominated exemplar. That question is about the scope of variables defined in a method. This question is (implicitly) about the scope of variables defined in a block. Commented Sep 3, 2017 at 5:18

1 Answer 1

1

In the first snippet the variable friend_num_grocery is defined within the block provided to the #times method so it is only accessible within the block itself (i.e. this variable scope is the block).

Defining the variable in an outer scope (just like in the second code snippet) is a way to work around this but a more Ruby-like fashion would be to rely on the return of the method you're providing the block to.

Also notice that Ruby provides facilities to iterating over a collection without the explicit need for indexing collection elements.

One possible alternative for achieving desired outcome would be

groceries = Array.new(10) { rand(100) } # build an array with random values
p groceries

highest_grocery, friend_num_grocery = groceries.each_with_index
                                               .sort { |a, b| a.first <=> b.first }
                                               .last

puts "Friend #{friend_num_grocery + 1} paid the most for groceries. With a total grocery bill of: $#{highest_grocery}"

In which

  • #each_with_index: gives us a data structure (array of arrays) with both values and indexes (like [[15, 0], [2, 1], ..., [13, 9]]) so we can sort based on values and receive both values and indexes in output.
  • #sort: sort arrays based on the value of their first element (but returns the complete arrays).
  • #last: pick the last array, which is the one with greatest value.
  • highest_grocery & friend_num_grocery are assigned through array matching (e.g. in a, b = [1, 2], a = 1 & b = 2).

Running this code a couple of times

$ ruby a.rb                                                                                                                                                           
[39, 67, 26, 75, 19, 28, 91, 40, 7, 42]
Friend 7 paid the most for groceries. With a total grocery bill of: $91
$ ruby a.rb 
[27, 21, 45, 34, 49, 77, 16, 54, 87, 12]
Friend 9 paid the most for groceries. With a total grocery bill of: $87
$ ruby a.rb 
[51, 70, 22, 25, 85, 4, 31, 64, 65, 6]
Friend 5 paid the most for groceries. With a total grocery bill of: $85
$ ruby a.rb 
[82, 17, 22, 28, 74, 70, 13, 37, 60, 68]
Friend 1 paid the most for groceries. With a total grocery bill of: $82
$ ruby a.rb 
[46, 6, 35, 60, 95, 36, 81, 27, 70, 78]
Friend 5 paid the most for groceries. With a total grocery bill of: $95

It's worth noting that:

  • Both #each_with_index & #sort are members from the Enumerable module. Getting familiar with it might help on writing effective Ruby code
  • My suggested code may not present expected behavior when there are identical grocery values
  • Initializing variables like a = 0 or h = {} is hardly elegant Ruby code
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.