I've always been searching for something like Python's while / else struct in Ruby to improve my code.
That means that the loop is executed and if the condition in the loop hasn't been true any time, then it returns the value in the else statement.
In ruby, I can do like this :
if #items.empty?
"Empty"
else
#items.each do |item|
item
end
end
So is there a way to improve this ?
Thank you in advance.
Remember that the iterator block returns what you put into it, which can be tested for further use.
if arr.each do |item|
item.some_action(some_arg)
end.empty?
else_condition_here
end
Hm, you could write it as a ternary:
#items.empty? ? 'Empty' : #items.each { |item| item }
You may want to do something more useful in your block though, since each is executed for its side effects and returns the original receiver.
Update as per your comment: I guess the closest you could get is something like
unless #items.empty?
#items.each { |item| p item }
else
'Empty'
end
Since we are in Ruby, let's have fun. Ruby has powerful case construct, which could be used such as this:
case items
when -:empty? then "Empty"
else items.each { |member|
# do something with each collection member
}
end
But to make the above code work, we have to modify the native class Symbol first. Modification of native classes is Ruby specialty. This needs to be done only once, typically in a library (gem), and it helps you ever after. In this case, the modification will be:
class Symbol
def -#
Object.new
.define_singleton_method :=== do |o| o.send self end
end
end
This code overloads the unary minus (-) operator of Symbol class in such way, that saying -:sym returns a new empty object monkey patched with :=== method, that is used behind the scenes by the case statement.
A more or less functional way:
empty_action = { true => proc{ "Empty"},
false => proc{ |arr| arr.each { |item| item }} }
empty_action[#items.empty?][#items]
Related
Does Ruby's Enumerable offer a better way to do the following?
output = things
.find { |thing| thing.expensive_transform.meets_condition? }
.expensive_transform
Enumerable#find is great for finding an element in an enumerable, but returns the original element, not the return value of the block, so any work done is lost.
Of course there are ugly ways of accomplishing this...
Side effects
def constly_find(things)
output = nil
things.each do |thing|
expensive_thing = thing.expensive_transform
if expensive_thing.meets_condition?
output = expensive_thing
break
end
end
output
end
Returning from a block
This is the alternative I'm trying to refactor
def costly_find(things)
things.each do |thing|
expensive_thing = thing.expensive_transform
return expensive_thing if expensive_thing.meets_condition?
end
nil
end
each.lazy.map.find
def costly_find(things)
things
.each
.lazy
.map(&:expensive_transform)
.find(&:meets_condition?)
end
Is there something better?
Of course there are ugly ways of accomplishing this...
If you had a cheap operation, you'd just use:
collection.map(&:operation).find(&:condition?)
To make Ruby call operation only "on a as-needed basis" (as the documentation says), you can simply prepend lazy:
collection.lazy.map(&:operation).find(&:condition?)
I don't think this is ugly at all—quite the contrary— it looks elegant to me.
Applied to your code:
def costly_find(things)
things.lazy.map(&:expensive_transform).find(&:meets_condition?)
end
I would be inclined to create an enumerator that generates values thing.expensive_transform and then make that the receiver for find with meets_condition? in find's block. For one, I like the way that reads.
Code
def costly_find(things)
Enumerator.new { |y| things.each { |thing| y << thing.expensive_transform } }.
find(&:meets_condition?)
end
Example
class Thing
attr_reader :value
def initialize(value)
#value = value
end
def expensive_transform
self.class.new(value*2)
end
def meets_condition?
value == 12
end
end
things = [1,3,6,4].map { |n| Thing.new(n) }
#=> [#<Thing:0x00000001e90b78 #value=1>, #<Thing:0x00000001e90b28 #value=3>,
# #<Thing:0x00000001e90ad8 #value=6>, #<Thing:0x00000001e90ab0 #value=4>]
costly_find(things)
#=> #<Thing:0x00000001e8a3b8 #value=12>
In the example I have assumed that expensive_things and things are instances of the same class, but if that is not the case the code would need to be modified in the obvious way.
I don't think there is a "obvious best general solution" for your problem, which is also simple to use. You have two procedures involved (expensive_transform and meets_condition?), and you also would need - if this were a library method to use - as a third parameter the value to return, if no transformed element meets the condition. You return nil in this case, but in a general solution, expensive_transform might also yield nil, and only the caller knows what unique value would indicate that the condition as not been met.
Hence, a possible solution within Enumerable would have the signature
class Enumerable
def find_transformed(default_return_value, transform_proc, condition_proc)
...
end
end
or something similar, so this is not particularily elegant either.
You could do it with a single block, if you agree to merge the semantics of the two procedures into one: You have only one procedure, which calculates the transformed value and tests it. If the test succeeds, it returns the transformed value, and if it fails, it returns the default value:
class Enumerable
def find_by(default_value, &block)
result = default_value
each do |element|
result = block.call(element)
break if result != default_value
end
end
result
end
You would use it in your case like this:
my_collection.find_by(nil) do |el|
transformed_value = expensive_transform(el)
meets_condition?(transformed_value) ? transformed_value : nil
end
I'm not sure whether this is really intuitive to use...
1.I can't find an elegant way to write this code:
if array.empty?
# process empty array
else
array.each do |el|
# process el
end
end
I'd like to have one loop, without writing array twice. I read this, but there is no solution good enough.
2.
I am actually in an HAML template. Same question.
- if array.empty?
%p No result
- else
%ul
- array.each do |el|
%li el
What about?
array.each do |x|
#...
puts "x",x
end.empty? and begin
puts "empty!"
end
The cleanest way I've seen this done in HAML (not plain Ruby) is something like:
- array.each do |item|
%li
= item.name
- if array.empty?
%li.empty
Nothing here.
As mentioned by other answers, there is no need for the else clause because that's already implied in the other logic.
Even if you could do the each-else in one clean line, you wouldn't be able to achieve the markup you're trying to achieve (<p> if array.empty?, <ul> if array.present?). Besides, the HAML you show in your question is the best way to tell the story behind your code, which means it will be more readable and maintainable to other developers, so I don't know why you would want to refactor into something more cryptic.
I think there is no much more elegant or readable way to write this. Any way to somehow combine an iteration with a condition will just result in blackboxed code, meaning: the condition will just most likely be hidden in an Array extension.
If array is empty, then it will not be iterated, so the each block does not need to be conditioned. Since the return value of each is the receiver, you can put the each block within the empty? condition.
if (array.each do |el|
# process el
end).empty?
# process empty array
end
Assuming that "process empty array" leaves it empty after processing, you can leave out the else:
if array.empty?
# process empty array
end
array.each do |el|
# process el
end
or in one line:
array.empty? ? process_empty_array : array.each { |el| process_el }
An if the array is nil then we can enforce to empty array
if (array || []).each do |x|
#...
puts "x",x
end.empty?
puts "empty!"
end
I saw some people asking how to handle this for nil cases.
The trick is to convert it to string. All nils converted to string becomes a empty string, all empty cases continue being empty.
nil.to_s.empty?
"".to_s.empty?
both will return true
Is there a keyword I can use to explicitly tell the map function what the result of that particular iteration should be?
Consider:
a = [1,2,3,4,5]
a.map do |element|
element.to_s
end
In the above example element.to_s is implicitly the result of each iteration.
There are some situations where I don't want to rely on using the last executed line as the result, I would prefer to explicitly say what the result is in code.
For example,
a = [1,2,3,4,5]
a.map do |element|
if some_condition
element.to_s
else
element.to_f
end
end
Might be easier for me to read if it was written like:
a = [1,2,3,4,5]
a.map do |element|
if some_condition
result_is element.to_s
else
result_is element.to_f
end
end
So is there a keyword I can use in place of result_is?
return will return from the calling function, and break will stop the iteration early, so neither of those is what I'm looking for.
The last thing left on the stack is automatically the result of a block being called. You're correct that return would not have the desired effect here, but overlook another possibility: Declaring a separate function to evaluate the entries.
For example, a reworking of your code:
def function(element)
if (some_condition)
return element.to_s
end
element.to_f
end
a.map do |element|
function(element)
end
There is a nominal amount of overhead on calling the function, but on small lists it should not be an issue. If this is highly performance sensitive, you will want to do it the hard way.
Yes, there is, it's called next. However, using next in this particular case will not improve readability. On the contrary, it will a) confuse the reader and b) give him the impression that the author of that code doesn't understand Ruby.
The fact that everything is an expression in Ruby (there are no statements) and that every expression evaluates to the value of the last sub-expression in that expression are fundamental Ruby knowledge.
Just like return, next should only be used when you want to "return" from the middle of a block. Usually, you only use it as a guard clause.
The nature of map is to assign the last executed line to the array. Your last example is very similar to the following, which follows the expected behavior:
a = [1,2,3,4,5]
a.map do |element|
result = if some_condition
element.to_s
else
element.to_f
end
result
end
No, there is no language keyword in ruby you can use to determine the result mapped into the resulting array before executing other code within the iteration.
You may assign a variable which you then return when some other code has been executed:
a.map do |element|
result = some_condition ? element.to_s : element.to_f
#do something else with element
result
end
Keep in mind the reason for ruby not providing a keyword for this kind of code is that these patterns tend to have a really low readability.
I have to write a Ruby method that:
Iterates through an array, doing Foo if one of the elements matches a certain condition.
If none of the array elements matched the condition, do Bar thing.
In any other language, I'd set a Boolean variable before entering the loop and toggle it if I did Foo. The value of that variable would tell me whether I needed to Bar. But that feels unRubyishly inelegant. Can anybody suggest a better way?
Edit Some really good answers, but they don't quite work because of a detail I should have mentioned. The something that Foo does is done to the array element that matches the condition. Also, it's guaranteed that at most one element will match the condition.
Do any of the items match? If yes, then do something, not involving the matching item.
if items.any? { |item| item.totally_awesome? }
foo "we're totally awesome!"
else
bar "not awesome :("
end
Grab the first matching item. If it exists, then do something, with the matching item.
awesome_item = items.find { |item| item.totally_awesome? }
if awesome_item
foo "#{awesome_item.name} is totally awesome!"
else
bar "no items are awesome :("
end
Grab all matching items. If the array has anything in it, then do something with all matching items.
awesome_items = items.find_all { |item| item.totally_awesome? }
if awesome_items.any?
foo "#{awesome_items.size} totally awesome items!"
else
bar "no items are awesome :("
end
You could do it like this:
if array.any? { |elem| elem.condition }
foo
else
bar
end
From the doc, Enumerable#any does the following:
Passes each element of the collection to the given block. The method returns true if the block ever returns a value other than false or nil.
What you want is Enumerable#find
Ex:
element = array.find { |x| x.passes_requirements? }
element ? element.foo! : bar
idx = the_array.index { |i| conditional(i) }
if idx
modify_object(the_array[idx])
else
no_matches
end
Edit: modified based on new question criteria.
found_index = nil
my_array.each_with_index.detect { |elem, i| elem.condition? && found_index = i }
if found_index.nil?
do_not_found_case
else
my_array[found_index] = some_conversion(elem)
end
This isn't as pretty, but it gets the job done and still short-circuits on the first match.
Thanks to everybody who tried to answer the question. None of you supplied an answer I found appropriate, but you all forced me to think about how you do things the Ruby way (which was the main point of this exercise!) and helped me come up with this answer:
I need to make use of the fact that iterators in Ruby are just methods. All methods return a value, and (oddly enough) each returns a useful value. If the iteration completes, it returns the collection you were iterating over; if you use break to terminate iteration early it returns nil (or an optional argument).
So, in a Boolean context, the whole loop is true if it completes and false if you break out. Thus
bar if array.each do |element|
if fooable(element) then
foo(element)
break
end
end
Very often in Ruby (and Rails specifically) you have to check if something exists and then perform an action on it, for example:
if #objects.any?
puts "We have these objects:"
#objects.each { |o| puts "hello: #{o}"
end
This is as short as it gets and all is good, but what if you have #objects.some_association.something.hit_database.process instead of #objects? I would have to repeat it second time inside the if expression and what if I don't know the implementation details and the method calls are expensive?
The obvious choice is to create a variable and then test it and then process it, but then you have to come up with a variable name (ugh) and it will also hang around in memory until the end of the scope.
Why not something like this:
#objects.some_association.something.hit_database.process.with :any? do |objects|
puts "We have these objects:"
objects.each { ... }
end
How would you do this?
Note that there's no reason to check that an array has at least one element with any? if you're only going to send each, because sending each to an empty array is a no-op.
To answer your question, perhaps you are looking for https://github.com/raganwald/andand?
Indeed, using a variable pollutes the namespace, but still, I think if (var = value).predicate is is a pretty common idiom and usually is perfectly ok:
if (objects = #objects.some_association.hit_database).present?
puts "We have these objects: #{objects}"
end
Option 2: if you like to create your own abstractions in a declarative fashion, that's also possible using a block:
#objects.some_association.hit_database.as(:if => :present?) do |objects|
puts "We have these objects: #{objects}"
end
Writing Object#as(options = {}) is pretty straigthforward.
What about tap?
#objects.some_association.something.hit_database.process.tap do |objects|
if objects.any?
puts "We have these objects:"
objects.each { ... }
end
end
Edit: If you're using Ruby 1.9, the Object#tap method provides the same functionality as the code listed below.
It sounds like you just want to be able to save a reference to an object without polluting the scope, correct? How about we open up the Object class and add a method do, which will just yield itself to the block:
class Object
def do
yield self if block_given?
return self # allow chaining
end
end
We can then call, for example:
[1,2,3].do { |a| puts a.length if a.any? }
=> 3
[].do { |a| puts a.length if a.any? }
=> nil