Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Usage of Pipes in Ruby Loops

Tags:

ruby

So, maybe I'm over-complicating something that isn't that hard, but here goes.

In Ruby, there's a method of looping called .each. I think that this is very cool--but what I'm finding less cool is the amount of stuff written about the pipe that comes after it (or any other do-type loop in Ruby, it would seem). Sometimes there is a single thing in the pipe:

basket.each do |fruit|
    puts "This is a #{fruit}.\n"
end

But sometimes, there are two things in this pipe, like so:

contacts.each do |name, profession|
    puts "#{name} is a #{profession}.\n"
end

So what I'm wondering now, is it possible to have more than two items in that pipe? Like if I have a huge, big, and ugly multi-dim array?

What if I add things to my pipe and they're not there? Will it give the value in the pipe nil? Or will it throw an error?

Again, sorry if this is obvious to long-time Rubyists, but I came from the land of strictly typed variables, and I'm now leaving PHP land, too. :)

EDIT

So what if I have something like this:

categories = [["Bathroom", "Bathroom Fixtures", "Plumbing"],
                      ["Ceiling Fixtures", "Chandeliers", "Flush Mounts", "Mini Chandeliers", "Semi-Flush Mounts", "Pendants", "Track Lighting", "Mini Pendants", "Directional", "Island/Pool Table", "Recessed Lighting"],
                      ["Outdoor", "Exterior", "Landscape Lighting"],
                      ["Fans", "Fans", "Fan Accessories"],
                      ["Lamps", "Lamps", "Shades"],
                      ["Wall Fixtures", "Sconces", "Foyer/Hall Lanterns"],
                      ["Specialty", "Undercabinet", "Light Bulbs", "Lighting Controls", "Glass", "Specialty Items", "Utility"],
                      ["Home Decor", "Decor/Home Accents", "Furniture"]]

Can I loop through it like this?

categories.each do |category, subcats|
    puts "The main category is #{category} and the sub categories are: "
    subcats.each do |subcat|
        puts "#{subcat}, "
    end
end
like image 520
melissanoelle Avatar asked Dec 20 '22 19:12

melissanoelle


2 Answers

Lets start with a break down of the each method.

a = [1,2,3,4,5]

a.each do |num|
  puts num
end

# 1
# 2
# 3
# 4
# 5

The do ... end portion is called a block

This block accepts one parameter (an element in the array)

The way you pass parameters to a block is with |'s

If you supply more than one argument to the block:

a.each do |num, x|
  puts num
  puts x
end

# 1
# 
# 2
# 
# 3
# 
# 4
# 
# 5 
# 

x is nil for each iteration.

Lets write a method of our own that uses blocks so you can see how they work.

def my_each(a=[])
  a.each do |x|
    yield x if block_given?
  end
end

my_each(a) do |num|
  puts num
end

Here yield x is saying, execute the supplied block and pass x to it.

If you pass another parameter to your block, it is nil. Why?

Our implementation of my_each doesn't know anything about a second parameter so it does not yield anything so it remains nil.

like image 78
Kyle Avatar answered Jan 11 '23 09:01

Kyle


When you have a simple array, the following things happen:

arr = [1,2,3,4]

arr.each do |x|
  p x
end
1
2
3
4
=> [1,2,3,4]

arr.each do |x,y|
  p x
  p y
end
1
nil
2
nil
3
nil
4
nil
=> [1,2,3,4]

so if ruby doesn't know what to put into the block argument, it simply sets it to nil. Now consider a nested array:

arr = [[1,2],[3,4],[5,6]]

arr.each do |x|
  p x
end
[1, 2]
[3, 4]
[5, 6]
=> [[1,2],[3,4],[5,6]]

arr.each do |x,y|
  p x
  p y
end
1
2
3
4
5
6
=> [[1,2],[3,4],[5,6]]

In this case, ruby assumes that you want to assign the two elements of the inner arrays to the block variables x and y. The same thing applies to hashes, where Ruby assigns the key and value to x and y:

hash = {1 => 2, 3 => 4, 5 => 6}

hash.each do |x,y|
  p x
  p y
end
1
2
3
4
5
6
=> {1=>2,3=>4,5=>6}

When you don't have enough elements in the nested arrays, the block variables are assigned nil, indeed. When there are too many of them, they are simply discarded:

arr = [[1,2,3],[4,5],[6]]

arr.each do |x,y|
  p x
  p y
end
1
2
4
5
6
nil
=> [[1,2,3],[4,5],[6]]

pretty straightforward!

EDIT:

As for your edited question: no, you cannot apply this 1:1 to Ruby code, you would have to manually apply the splat operator (*) to subcats. This way, ruby assigns all remaining elements to the 'splatted' block variable:

categories.each do |category,*subcats|
    puts "The main category is #{category} and the sub categories are: "
    subcats.each do |subcat|
        puts "#{subcat}, "
    end
end

although i would generate a comma-separated list of subcategories like this:

categories.each do |category,*subcats|
    puts "The main category is #{category} and the sub categories are: "
    puts subcats.join(', ')
end

EDIT 2:

Oh, and you would not handle a huge ugly evil multidimensional array by defining a lot of block parameters for its elements. You probably would iterate through it using nested loops as in almost every other language, if only because you never know how many elements it contains.

like image 23
Patrick Oscity Avatar answered Jan 11 '23 11:01

Patrick Oscity