How to reduce this loop to single line of code? - ruby

I need the code below to be written on a single line. It's looking ugly to me:
#photos = []
#vehicle.photos.each { |p| #photos << view_context.present(p) }
How can I do?

The pattern of iterating over an enumerable object (#vehicle), apply a function to each element (view_context.present(p)), and append the result to a newly created array (#photos) is encapsulated by the map method:
#photos = #vehicle.photos.map { |p| view_context.present(p) }

you can use inject also, which is simple to understand. The inject method takes an argument and a block. The block will be executed once for each element contained in the object, the argument passed to inject will be yielded as the first argument to the block, the first time it's executed. The second argument yielded to the block will be the first element of the object that we called inject on.
#photos = #vehicle.photos.inject([]) { |res, p| res << view_context.present(p) }
more details here.
You can use each_with_object as well, which works better for mutable object such as an Array, Hash, String as suggested by toro2k. In that case code will look like:-
#photos = #vehicle.photos.each_with_object([]) { |p,res| res << view_context.present(p) }

Shorter version of map (or may be not. But without curly braces and pipes)
#photos = #vehicle.photos.map &view_context.method(:present)

Related

Why isn't the following code working the way it should?

array = ["car","carrs"]
array.each { |x|
x.capitalize
}
I have tried doing with do too by removing the curly braces and adding do after .each, I have also tried for each in array, but that didnt work too. Am i doing something wrong because nothing gets capitalized?
String#capitalize returns a copy of the object with the first letter capitalized. What you're basically doing is looping through your array and generating new copies of the strings, but then immediately throwing them away.
You have a couple of ways to approach this:
You can use #map rather than #each to take each result of your loop block body and collect it into a new array:
array = ["car","carrs"]
capitalized_array = array.map { |x| x.capitalize }
Or, if you actually want to mutate the original strings, use String#capitalize! rather than capitalize, which mutates the input object, rather than returning a new object:
array = ["car","carrs"]
array.each { |x| x.capitalize! }
While it may seem tempting to use the mutative version, it is frequently a good idea to use non-mutative methods to produce transformations of your data, so you don't lose your original input data. Mutate-in-place can introduce subtle bugs by making the state of the data harder to reason about.
You have to understand the difference between map vs each. You can read it here.
For those who don't want to read that:
Each is like a more primitive version of map. It gives you every element so you can work with it, but it doesn’t collect the results. Each always returns the original, unchanged object. While map does the same thing, but. It returns a new array with the transformed elements.
So, you have to use map in order to return a new array:
array = ["car","carrs"]
capitalized_array = array.map { |x| x.capitalize }
# or
array = ["car","carrs"]
array.map! { |x| x.capitalize }
Now, what is the different between map and map!? We need to read the documentation
map invokes the given block once for each element of self. Creates a new array containing the values returned by the block. While map! invokes the given block once for each element of self, replacing the element with the value returned by the block.

No implicit conversion of Enumerator into Array

I have:
qs = ["all=true", "limit=-1"]
value = ["agent", "service", "token"]
This code:
qs.concat value.map do |val|
"#{field}=#{val}"
end
ends up with the following error:
`concat': no implicit conversion of Enumerator into Array (TypeError)`
whereas this code:
values = value.map do |val|
"field=#{val}"
end
qs.concat values
does not.
What is the difference between them?
Your issue is caused by the different precedences when providing blocks to chained method calls. In your case you use the following code:
qs.concat value.map do |val|
"#{field}=#{val}"
end
Ruby assumes here that you mean the following:
qs.concat(value.map) do |val|
"#{field}=#{val}"
end
That is, Ruby passes the block to the first method (i.e qs.concat) which ignores the block. Since Array#map returns an Enumerator if you don't pass a block, you get your error you saw.
To solve this, you can use the braces form of passing the block, i.e.:
qs.concat value.map { |val|
"#{field}=#{val}"
}
In this form, the block is always passed to the "last" method, u.e. your map.
In any case, if there is any doubt about operator precedence, it is always a good idea to use explicit parenthesis or intermediate variables to make it clear both to human readers as well as the Ruby interpreter how your code is supposed to work.
When you say qs.concat value.map do |val|, what does do belong to? (Hint: not value.map!)
Use parentheses when uncertain.
qs=["all=true", "limit=-1"]
value=["agent", "service", "token"]
qs.concat(value.map do |val|
"field=#{val}"
end)
{...} would be more typical than do...end, and the priority works out so you don't actually need parentheses, as {...} do go to value.map rather than to qs.concat. This also works:
qs=["all=true", "limit=-1"]
value=["agent", "service", "token"]
qs.concat value.map { |val|
"field=#{val}"
}

Using yield within a select statement

If I wanted to prune an array by a given set of parameters I would write something like this:
array = [4,5,6,7,8]
a = array.select{|i| i>=5}
puts a.inspect
which would return [5,6,7,8].
I want to write a function "filter" which accomplishes the same thing. In this case my first thought is to write something like:
array = [4,5,6,7,8]
a = filter(array) {|i| i >= 5}
puts a.inspect
What I can't figure out is how to properly call yield within the method to invoke the code block during the select statement:
a = array.select{yield}
Doesn't seem to work since it attempts to call the code block on nil, not the array within the function. What's the proper way of doing this?
Don't know if it makes sense for you, but try:
def filter(array)
array.select { |i| yield(i) }
end
array = [4,5,6,7,8]
p filter(array) {|i| i >= 5}
When you write code within braces {...} to be passed to a method, this code is called a block. It is normally passed implicitly to a method (i.e. it is not a named argument). To invoke this implicit block, you call yield.
In your case, you don't want to invoke the block yourself; you want your filter method to pass the block along to select, where the actual filtering takes place.
To "pass along a block", you can make the method's block argument explicit by using the & prefix. Note that the name block in this example is just convention; there is no special block keyword. The important part is the & character:
def filter(array, &block)
array.select(&block)
end
array = [4,5,6,7,8]
filter(array) { |i| i >= 5 } # => [5,6,7,8]

Simple way to understand returning from a block in ruby

My code is supposed to print integers in an array.
odds_n_ends = [:weezard, 42, "Trady Blix", 3, true, 19, 12.345]
ints = odds_n_ends.select { |x| if x.is_a?(Integer) then return x end }
puts ints
It gives me an error in the 2nd line - in 'block in <main>': unexpected return (LocalJumpError)
When I remove the return, the code works exactly as desired.
To find the mistake in my understanding of blocks, I read related posts post1 and post2. But, I am not able to figure out how exactly are methods and blocks being called and why my approach is incorrect.
Is there some call stack diagram explanation for this ? Any simple explanation ?
I am confused because I have only programmed in Java before.
You generally don't need to worry exactly what blocks are to use them.
In this situation, return will return from the outside scope, e.g. if these lines were in a method, then from that method. It's the same as if you put a return statement inside a loop in Java.
Additional tips:
select is used to create a copied array where only the elements satisfying the condition inside the block are selected:
only_ints = odds_n_ends.select { |x| x.is_a?(Integer) }
You're using it as a loop to "pass back" variables that are integers, in which case you'd do:
only_ints = []
odds_n_ends.each { |x| if x.is_a?(Integer) then only_ints << x end }
If you try to wrap your code in a method then it won't give you an error:
def some_method
odds_n_ends = [:weezard, 42, "Trady Blix", 3, true, 19, 12.345]
ints = odds_n_ends.select { |x| if x.is_a?(Integer) then return true end }
puts ints
end
puts some_method
This code output is true. But wait, where's puts ints??? Ruby didn't reach that. When you put return inside a Proc, then you're returning in the scope of the entire method. In your example, you didn't have any method in which you put your code, so after it encountered 'return', it didn't know where to 'jump to', where to continue to.
Array#select basically works this way: For each element of the array (represented with |x| in your code), it evaluates the block you've just put in and if the block evaluates to true, then that element will be included in the new array. Try removing 'return' from the second line and your code will work:
ints = odds_n_ends.select { |x| if x.is_a?(Integer) then true end }
However, this isn't the most Ruby-ish way, you don't have to tell Ruby to explicitly return true. Blocks (the code between the {} ) are just like methods, with the last expression being the return value of the method. So this will work just as well:
ints = odds_n_ends.select { |x| if x.is_a?(Integer) } # imagine the code between {} is
#a method, just without name like 'def is_a_integer?' with the value of the last expression
#being returned.
Btw, there's a more elegant way to solve your problem:
odds_n_ends = [:weezard, 42, "Trady Blix", 3, true, 19, 12.345]
ints = odds_n_ends.grep(Integer)
puts ints
See this link. It basically states:
Returns an array of every element in enum for which Pattern ===
element.
To understand Pattern === element, simply imagine that Pattern is a set (let's say a set of Integers). Element might or might not be an element of that set (an integer). How to find out? Use ===. If you type in Ruby:
puts Integer === 34
it will evalute to true. If you put:
puts Integer === 'hey'
it will evalute to false.
Hope this helped!
In ruby a method always returns it's last statement, so in generall you do not need to return unless you want to return prematurely.
In your case you do not need to return anything, as select will create a new array with just the elements that return true for the given block. As ruby automatically returns it's last statement using
{ |x| x.is_a?(Integer) }
would be sufficient. (Additionally you would want to return true and not x if you think about "return what select expects", but as ruby treats not nil as true it also works...)
Another thing that is important is to understand a key difference of procs (& blocks) and lambdas which is causing your problem:
Using return in a Proc will return the method the proc is used in.
Using return in a Lambdas will return it's value like a method.
Think of procs as code pieces you inject in a method and of lambdas as anonymous methods.
Good and easy to comprehend read: Understanding Ruby Blocks, Procs and Lambdas
When passing blocks to methods you should simply put the value you want to be returned as the last statement, which can also be in an if-else clause and ruby will use the last actually reached statement.

Yield within Set to eliminate in an Array

I found the following code here for eliminating duplicate records in an array:
require 'set'
class Array
def uniq_by
seen = Set.new
select{ |x| seen.add?( yield( x ) ) }
end
end
And we can use the code above as follows:
#messages = Messages.all.uniq_by { |h| h.body }
I would like to know how and what happens when the method is called. Can someone explain the internals of the code above? In the uniq_by method, we did not do anything to handle block argument. How is the passed argument handled by uniq_by method?
Let's break it down :
seen = Set.new
Create an empty set
select{ |x| seen.add?( yield( x ) ) }
Array#select will keep elements when the block yields true.
seen.add?(yield(x)) will return true if the result of the block can be added in the set, or false if it can't.
Indeed, yield(x) will call the block passed to the uniq_by method, and pass x as an argument.
In our case, since our block is { |h| h.body }, it would be the same as calling seen.add?(x.body)
Since a set is unique, calling add? when the element already exists will return false.
So it will try to call .body on each element of the array and add it in a set, keeping elements where the adding was possible.
The method uniq_by accepts a block argument. This allows to specify, by what criteria you wish to identify two elements as "unique".
The yield statement will evaluate the value of the given block for the element and return the value of the elements body attribute.
So, if you call unique_by like above, you are stating that the attribute body of the elements has to be unique for the element to be unique.
To answer the more specific question you have: yield will call the passed block {|h| h.body} like a method, substituting h for the current x and therefore return x.body
In Ruby, when you are putting yield keyword inside any method(say #bar), you are explicitly telling #bar that, you will be using a block with the method #bar. So yield knows, inside the method block will be converted to a Proc object, and yield have to call that Proc object.
Example :
def bar
yield
end
p bar { "hello" } # "hello"
p bar # bar': no block given (yield) (LocalJumpError)
In the uniq_by method, we did not do anything to handle block argument. How is the passed argument handled by uniq_by method?
You did do, that is you put yield. Once you will put this yield, now method is very smart to know, what it supposed to so. In the line Messages.all.uniq_by { |h| h.body } you are passing a block { |h| h.body }, and inside the method definition of uniq_by, that block has been converted to a Proc object, and yield does Proc#call.
Proof:
def bar
p block_given? # true
yield
end
bar { "hello" } # "hello"
Better for understanding :
class Array
def uniq_by
seen = Set.new
select{ |x| seen.add?( yield( x ) ) }
end
end
is same as
class Array
def uniq_by
seen = Set.new
# Below you are telling uniq_by, you will be using a block with it
# by using `yield`.
select{ |x| var = yield(x); seen.add?(var) }
end
end
Read the doc of yield
Called from inside a method body, yields control to the code block (if any) supplied as part of the method call. If no code block has been supplied, calling yield raises an exception. yield can take an argument; any values thus yielded are bound to the block's parameters. The value of a call to yield is the value of the executed code block.
Array#select returns a new array containing all elements of the array for which the given block returns a true value.
The block argument of the select use Set#add? to determine whether the element is already there. add? returns nil if there is already the same element in the set, otherwise it returns the set itself and add the element to the set.
The block again pass the argument (an element of the array) to another block (the block passed to the uniq_by) using yield; Return value of the yield is return value of the block ({|h| h.body })
The select .. statement is basically similar to following statement:
select{ |x| seen.add?(x.body) }
But by using yield, the code avoid hard-coding of .body, and defers decision to the block.

Resources