Usage of Pipes in Ruby Loops - 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

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.

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.

The pipes you are talking about is a parameter list of a block "variable". Actually that is some kind of a function pointer, and the pipes marks the parameter list.
Check the description of array.each.
This is not magic, the number of parameters is defined in the block, you can't add more than that, if you do, they won't get a value. The reason is for "sometime" there can be more than one, is that it's probably a hash.each, which has two parameters, a key and a value.
You can create your own functions with block parameters, read this.
For your iteration problem, you can use a hash, or you can write your own iterator.

Multiple Arguments to a Block
Array#each iterates over an array object, and passes either a single object into the block or returns an enumerator. You can redefine this behavior, but #each is the wrong method if you want multiple values at a time; see Enumerator#each_slice for an alternative.
Data Structures
Your problem would be easier to solve with the right data structure. Instead of an array, you should consider using a hash. For example:
categories =
{"Bathroom"=>["Bathroom Fixtures", "Plumbing"],
"Ceiling Fixtures"=>["Chandeliers", "Flush Mounts", "Mini Chandeliers"]}
categories.each do |key, value|
puts "#{key}:"
value.each { |v| puts "\t%s" % v }
end
This returns:
Bathroom:
Bathroom Fixtures
Plumbing
Ceiling Fixtures:
Chandeliers
Flush Mounts
Mini Chandeliers

Related

Optimize print output where i use check on zero. Ruby

Currently, I'm having print like this
print ((stamp_amount[0], 'first mark') unless stamp_amount[0].zero?), (', ' if !stamp_amount[0].zero? && !stamp_amount[1].zero?),
((stamp_amount[1], 'second mark') unless stamp_amount[1].zero?)
stamp_amount is an array with 2 integer values
Let's say in the current situation stamp_amount[0] = 10 and stamp_amount[1] = 3
Output preview:
10 first mark, 3 second mark
So if stamp_amount[0] = 0 the 10 first mark, part won't be show. Same if stamp_amount[1] = 0 the , 3 second mark part won't be shown
For me, it seems a little bit incorrect in terms of theory. Could you please suggest me the more correct or less painful print of this? :)
Cheers!
Your code is trying to join a sequence of up to two elements with a separator. The joining is a solved problem, see Array#join.
The problem can be then reduced to "how can I produce the correct sequence, given my stamp_amount input". Now this can be done in a thousand ways. Here's one:
def my_print(stamp_amount)
ary = [
!stamp_amount[0].zero? && stamp_amount[0],
!stamp_amount[1].zero? && stamp_amount[1],
].select{|elem| elem }
ary.join(', ')
end
my_print([10, 3]) # => "10, 3"
my_print([0, 3]) # => "3"
my_print([10, 0]) # => "10"
my_print([0, 0]) # => ""
Here's another
ary = []
ary << stamp_amount[0] unless stamp_amount[0].zero?
ary << stamp_amount[1] unless stamp_amount[1].zero?
ary.join(', ')
Here's yet another. This version can handle stamp_amount of any length.
ary = stamp_amount.reject(&:zero?)
ary.join(', ')
I'd go with the third, but the second one may be the easiest to understand for a beginner.
Use the select, as an alternative to reject (shown in part 3 of the answer by Sergio Tulentsev). It is just asa readable, and depending on the context and on the future changes to the code, you may prefer one versus the other.
puts stamp_amount.select{ |a| !a.zero? }.join(", ")
A few examples of inputs and outputs are:
stamp_amount output
--------------------------------------------------------------------------
10, 3 10, 3
10, 0 10
0, 3 3
0, 0 (prints an empty line, because the selected array is empty)
You're calculating zero? on index points more often than is needed, but the first thing I would look at refactoring here is the readability of the code. It might be nicer to calculate the message to print outside of the print method and explain what is happening with variable names.
# rubocop is going to complain about variable assignment like this
first_amount, second_amount = *stamp_amount
We can actually use the reason rubocop prefers the .zero? over == 0 or .empty? method to guide our development. zero? is in essence just empty? but it communicates the meaning of what you are attempting to do in a better manner. I would use this reasoning when assigning strings to variables that explain what they are doing.
some_name_that_explains_what_this_is_0 = "#{first_amount} piecu centu marka"
some_name_that_explains_what_this_is_1 = "#{second_amount} tris centu marka"
Your current code is confusing as you have the possibility of printing a string like "10 tris centu marka" which does not make lexical sense and probably not what you are after considering tis evaluates to 'second mark', which would pose an issue if the first value is zero. We also could reject zero integers before we start converting them to strings.
array = [1, 0].reject(&:zero?)
Now we can take the array and do something like:
string = []
array.each_with_index { |e, i| string << "#{e} #{Ordinalize.new(i).ordinalize} mark" }
message = string.join(', ')
print(message)
# ord class
class Ordinalize
def initialize(value)
#value = value
end
def ordinalize
mapping[#value]
end
def mapping
# acounting for zero index
['first', 'second']
end
end
where we are calculating the ordinalization and letting our new class handle the sentence structure for us.
Outputs:
[1, 0] => "1 first mark"
[0, 1] => "1 first mark"
[1, 2] => "1 first mark, 2 second mark"

Why does Ruby require or operators for find_all method when searching multiple things?

Given the following code:
Why does Ruby require the or operator when searching for multiple things using the find_all method? I.e.:
x = [1,2,3,4,5]
variable = x.find_all do |x|
x.even?||x.odd?
end
puts variable
I am trying figure out whether each number within the array is either even or odd. At the end, eventually putting the result of variable, if it is either of those two.
If you try to write the conditions you want find sequentially, it does not work:
variable = x.find_all do |x|
x.even?
x.odd?
end
I am curious to know why that the first example works and the second one does not. The second equation, in my head I'm thinking you are simply listing them sequentially, trying to figure out whether x is even, THEN figure out whether x is odd. Unsure why it is not working. Can someone explain why or operators must be used as opposed to listing methods sequentially, when trying to search for multiple conditions?
Ruby has an implicit return where the last executed line in a method or a block will be the line that is returned. With this in mind
variable = a.find_all do |x|
x.even?
x.odd?
end
Will only regard the line x.odd? when it comes to keeping that. You could do it without an or operator like this
variable = a.find_all do |x|
if x.even?
true
else
x.odd?
end
end
For this if x.even? is true, then true will be returned as that is the last line of code run inside the block. If x.even? is false, then x.odd? will be returned as that is the last line of code run inside the block. If you are trying to check for multiple conditions using || is efficient but you can also do something like this.
Let's say I have the array a = ['dog', 2, 3.4, 'PIZZA', false, nil]. Let's say that I want all lowercase strings, all integers, and all nil values. I can do this
a.find_all do |i|
if i.is_a? String
i == i.downcase
elsif i.is_a? Numeric
i == i.to_i
else
i.nil?
end
end
This would return ['dog', 2, nil].
The find_all methods iterates over each element of the collection, using the return value of the block you pass to it to test whether the element matches. Then returns a new array with the matching elements.
If you use this code:
variable = x.find_all do |x|
x.even?
x.odd?
end
the return value of the block is x.odd?, as in ruby unless you use the return operator explicitly, the last expression of a block or method is returned.
If you want to group the elements of a collection by whether it is odd or even you should use some other approach. For example you could use group_by:
[1,2,3].group_by { |x| x.even? }
=> {false=>[1, 3], true=>[2]}
[1,2,3].group_by { |x| x.even? ? :even : :odd }
=> {:odd=>[1, 3], :even=>[2]}

How can I know how many parameters a method passes to a block?

If I have two variables like a and h.
a = ["cat", "dog", "mat"]
h = {cat: 'gatto', dog: 'cane', mat: 'stuoia'} # (Italian translations)
And I call the method .each on them, if I don't know the kind of object they are pointing to, how can I know that the block passed to a.each can take one parameter and the block passed to b.each can take two?
In other words, when I pass a block to a method, how can I know how many block parameters the method will set?
Is there some_method which returns the number of parameters a block should take? So that obj.general_method_that_takes_a_block.some_method would return the number of parameters that general_method_that_takes_a_block passes to its block?
A straightforward way is:
a.each{|e| p [*e].length}
# => 1 1 1
h.each{|e| p [*e].length}
# => 2 2 2
The each blocks always gets a single parameter, it never gets two. In the Hash case, when you do this:
h.each { |k, v| ... }
Ruby is, more or less, doing this behind your back:
h.each { |a| k, v = a; ... }
So you could check if the block's argument is an Array:
e.each do |x|
if x.kind_of? Array
# e might be a Hash
else
# e might be an Array
end
end
The problem is that e might be something like [ [1,2], [3,4] ] which would incorrectly put you into the might be a Hash branch; this sort of e will also fool a [*e].length check.
I don't think there is any clean and simple way to know what you're iterating over from inside the block.

Storing output into a variable to be used in an array

A snippet of my code below flips a coin and outputs a result of 10 total heads or tails.
(e.g. Heads Tails Heads Tails...)
I'd like to store this into a variable where I can put it into an array and use its strings.
%w[act] only outputs the string "act". How can I get that line of code to output my array of strings from the line act = coin.flip?
Updated and added full code
class Coin
def flip
flip = 1 + rand(2)
if flip == 2
then puts "Heads"
else
puts "Tails"
end
end
end
array = []
10.times do
coin = Coin.new
array << coin.flip
end
puts array
This:
10.times do
coin = Coin.new
act = coin.flip
end
doesn't produce an array. It simply creates ten coin flips and throws them all away, the result of that expression is, in fact, 10. If you want an array, you'll need to build one.
You could take Douglas's approach or try something a bit more idiomatic.
The Integer#times method returns an enumerator so you can use any of the Enumerable methods on it rather than directly handing it a block. In particular, you could use collect to build an array in one nice short piece of code:
a = 10.times.collect { Coin.new.flip }
That gives you 10 flips in the Array a and then you can puts a or puts a.join(', ') or whatever you want.
The %w[] won't work because that's for generating an Array of whitespace separated words:
%w[] Non-interpolated Array of words, separated by whitespace
So %w[a b c] is just a nicer way of saying ['a', 'b', 'c'] and the words within %w[] are treated as single quoted strings rather than variables or method calls to be evaluated.
Seems that there is some editing going on. You'll also want to modify your flip method to return the flip rather than print it:
def flip
flip = 1 + rand(2)
if flip == 2
"Heads"
else
"Tails"
end
end
Then you'll get your Heads and Rails in the array.
Put the act results into an array.
arr = []
10.times do
coin = Coin.new
arr << coin.flip
end
p arr # => [...]

escaping the .each { } iteration early in Ruby

code:
c = 0
items.each { |i|
puts i.to_s
# if c > 9 escape the each iteration early - and do not repeat
c++
}
I want to grab the first 10 items then leave the "each" loop.
What do I replace the commented line with? is there a better approach? something more Ruby idiomatic?
While the break solution works, I think a more functional approach really suits this problem. You want to take the first 10 elements and print them so try
items.take(10).each { |i| puts i.to_s }
There is no ++ operator in Ruby. It's also convention to use do and end for multi-line blocks. Modifying your solution yields:
c = 0
items.each do |i|
puts i.to_s
break if c > 9
c += 1
end
Or also:
items.each_with_index do |i, c|
puts i.to_s
break if c > 9
end
See each_with_index and also Programming Ruby Break, Redo, and Next.
Update: Chuck's answer with ranges is more Ruby-like, and nimrodm's answer using take is even better.
break works for escaping early from a loop, but it's more idiomatic just to do items[0..9].each {|i| puts i}. (And if all you're doing is literally printing the items with no changes at all, you can just do puts items[0..9].)
Another option would be
items.first(10).each do |i|
puts i.to_s
end
That reads a little more easily to me than breaking on an iterator, and first will return only as many items as available if there aren't enough.
Another variant:
puts items.first(10)
Note that this works fine with arrays of less than 10 items:
>> nums = (1..5).to_a
=> [1, 2, 3, 4, 5]
>> puts nums.first(10)
1
2
3
4
5
(One other note, a lot of people are offering some form of puts i.to_s, but in such a case, isn't .to_s redundant? puts will automatically call .to_s on a non-string to print it out, I thought. You would only need .to_s if you wanted to say puts 'A' + i.to_s or the like.)
Does this look like what you want?
10.times { |i|
puts items[i].to_s
}
items.each_with_index { |i, c| puts i and break if c <= 9 }
It was asked:
I want to grab the first 10 items then leave the "each" loop.
Use throw and catch to accomplish this, with few changes to the example:
catch(:done) do
c = 0
collected = []
items.each do |item|
collected << item
throw(:done, collected) if c == 9 # started at 0
c += 1
end
collected # if the list is less than 10 long, return what was collected
end
Simply throw the label :done with collected and the catch which is waiting for :done will return collected.
And to "ruby" this up a bit:
catch(:done) do
items.inject([]) do |collected, item|
throw(:done, collected) if collected.size == 10
collected << item # collected gets returned here and populates the first argument of this block
end
end
I do not know why some people refuse to use inject and use reduce instead (they are equivalent) when clearly the empty array given to inject([]) is being injected with items! Anyhow, the inject will return collected if there are less than 10 items.
Most answers are trying to answer what might be the intent of the question instead of what was asked and items.take(10) does make perfect sense in that case. But I can imagine wanting to grab the first items that fit within my $100 budget. Then you can simply:
catch(:done) do
items.inject({items: [], budget: 100}) do |ledger, item|
remainder = ledger[:budget] - item.price
if remainder < 0
throw(:done, ledger)
else
ledger.tap do |this|
this[:items] << item
this[:budget] = remainder
end # tap just returns what is being tapped into, in this case, ledger
end
end
end

Resources