Using ampersand with an enumerator in a comparison - ruby

I have the following snippet that throws an error:
w = %w[ant dog]
w.all?(&:length > 4)
It throws an error comparison of Symbol with 4 failed (ArgumentError).
I'm not sure why &:length is a symbol and not a number. I tried:
w.all?((&:length) > 4)
but that gives me a syntax error. Is there a way to get this to work or do I have to do: w.all? { |word| word.length > 4 }.

You receive the following error, because the interpreter actually executes the following piece of code:
&:length > 4
and can't compare symbol :length with number 4.
Is there a way to get this to work or do I have to do: w.all? { |word| word.length > 4 }
You could do something like:
w.map(&:length).all? { |n| n > 4 }
But the following code is the simpliest way to go:
w.all? { |word| word.length > 4 }

Is there a way to get this to work this way w.all?(&:length > 4)?
There is no way to make it work this way because it will require you to rewrite Ruby's interpreter.
or do I have to do: w.all? { |word| word.length > 4 }
Yes, this is simple to read yet efficient way to do what you need.

I had to hunt around a bit for a good technical answer to what's going on. I found a thorough explanation here. The last section of that page explains what is happening in this particular case (but you should read the top part of the page as well).
Coming from Perl, I imagined the &:length was acting kind of like the $_ variable. This is not correct.
What is happening is that instead of passing a block to the all? method, you are passing arguments to the all? method (note the parenthesis instead of curly braces). Here, we are passing the symbol for the method length as an argument to to all?. You put the & in front of the symbol to get it to call the to_proc method (whatever that is) on the length method so it can call the length function inside the all? method for each element.

Related

Is there a way to take an array and modify each individual element in a unique way using a single line of code?

I'm attempting to modify a variable string in Ruby 2.4 that contains both a number and a unit of measurement. My goal is to take the following string, round the numeric portion, and capitalize the unit portion:
my_string = "123.456789 dollars"
There are all sorts of ways that I know of to complete this operation with multiple lines of code, but I'm working with a unique interface and in a unique environment where the goal is to write this using a single line of code that is as short as possible. I know that I can do something like this:
my_array =my_string.split; my_array[0].to_f.round(2).to_s + " " + my_array[1].capitalize
...but my goal is to do away with the semicolons, use less characters, and to avoid having to call the array twice.
My idea was to do something along the lines of:
my_string.split.*do_this*(self.to_f.round(2), self.capitalize).join(" ")
Is there any way to do something like this PRIOR to version 2.5?
I'm open to other ideas as well, but again, the goal is a single line of code that is as short as possible, and the ability to modify the 2 elements using different parameters on each.
Please note: Upgrading to a newer version of Ruby is not an option. 2.4 is the version currently hard coded into the software package we’re working with.
If there are only two elements, you could use then and string interpolation:
my_string.split.then { |f, s| "#{f.to_f.round(2)} #{s.capitalize}" }
Or perhaps one of:
my_string.split.then { |f, s| sprintf("%.2f #{s.capitalize}", f) }
my_string.split.then { |f, s| sprintf('%.2f %s', f, s.capitalize) }
my_string.split.then { |f, s| "%.2f #{s.capitalize}" % f }
Or, if there could be more than two elements, you could combine map and with_index:
my_string.split.map.with_index { |e, i| i == 0 ? e.to_f.round(2) : e.capitalize }.join(' ')
I found that in Ruby 2.4 we can use the instance_eval method in at least a few different ways such as:
my_string.split.instance_eval { |f, s| "#{f.to_f.round(2)} #{s.capitalize}" }
or
my_string.split.instance_eval { |a| "#{a[0].to_f.round(2)} #{a[1].capitalize}" }
or
my_string.split.instance_eval { |f, s| [f.to_f.round(2), s.capitalize]}.join(" ")
We can also use the tap method which requires just a few more characters:
my_string.split.tap { |a| a[0..-1] = [a[0].to_f.round(2), a[1].capitalize]}.join(" ")
Or we can even use the map method by creating a nested array like in these 2 examples:
[my_string.split].map{|a| "#{a[0].to_f.round(2)} #{a[1].capitalize}"}.join
and
[my_string.split].map{|a| [a[0].to_f.round(2)," ",a[1].capitalize]}.join
And just as an added bonus, we can even do something like the following which doesn't require using a block:
(a, b = my_string.split).replace([a.to_f.round(2)," ",b.capitalize ]).join

Ruby: how to find the next match in an array

I have to search an item in an array and return the value of the next item. Example:
a = ['abc.df','-f','test.h']
i = a.find_index{|x| x=~/-f/}
puts a[i+1]
Is there any better way other than working with index?
A classical functional approach uses no indexes (xs.each_cons(2) -> pairwise combinations of xs):
xs = ['abc.df', '-f', 'test.h']
(xs.each_cons(2).detect { |x, y| x =~ /-f/ } || []).last
#=> "test.h"
Using Enumerable#map_detect simplifies it a litte bit more:
xs.each_cons(2).map_detect { |x, y| y if x =~ /-f/ }
#=> "test.h"
The reason something like array.find{something}.next doesn't exist is that it's an array rather than a linked list. Each item is just it's own value; it doesn't have a concept of "the item after me".
#tokland gives a good solution by iterating over the array with each pair of consecutive items, so that when the first item matches, you have your second item handy. There are strong arguments to be made for the functional style, to be sure. Your version is shorter, though, and I'd argue that yours is also more quickly and easily understood at a glance.
If the issue is that you're using it a lot and want something cleaner and more to the point, then of course you could just add it as a singleton method to a:
def a.find_after(&test)
self[find_index(&test).next]
end
Then
a.find_after{|x| x=~/-f/}
is a clear way to find the next item after the first match.
All of that said, I think #BenjaminCox makes the best point about what appears to be your actual goal. If you're parsing command line options, there are libraries that do that well.
I don't know of a cleaner way to do that specific operation. However, it sure looks like you're trying to parse command-line arguments. If so, I'd recommend using the built-in OptionParser module - it'll save a ton of time and hair-pulling trying to parse them yourself.
This article explains how it works.
Your solution working with indexes is fine, as others have commented. You could use Enumerable#drop_while to get an array from your match on and take the second element of that:
a = ['abc.df','-f','test.h']
f_arg = a.drop_while { |e| e !~ /-f/ }[1]

Converting this into a Ruby one-liner

I'm currently trying to making my obfuscated, short mandelbrot set code into a one-liner, but I'm having a lot of trouble in doing so. \
It was originally written in Python, but due to Python's limitations, I could not compress the code to one line. So now I'm going to try Ruby. I'm familiar with the language, but I'm not very skilled in using blocks - which is where I am having the issue.
Anyway, the code I want to "convert" is
for q in range(801):
if q%40==0:print s;s=''
i,k=0,0
while(abs(k)<2*(i<15)):k,i=k**2+complex(q%40*.075-2,q/40*-.1+1),i+1
s+=h[i/3]
Which I've attempted to rewrite in Ruby...
h,s='.;*&8#',''
0.upto(800).each {|q|
if !q%40
s=''
p s
end
i,k=0,0
while(k.abs<2*(i<15))
k,i=k**2+Complex(q%40*0.075-2,q/40*-0.1+1),i+1
end
s+=h[i/3]
}
Which throws the error
Line 2:in `upto': no block given (LocalJumpError)
from t.rb:2
After sorting this out, I'd like to shorten it further to one line. Which I've started here...
h,s='.;*&8#','';0.upto(800).each {|q| if !q%40 then s='';p s end;i,k=0,0;while(k.abs<2*(i<15))do k,i=k**2+Complex(q%40*0.075-2,q/40*-0.1+1),i+1 end}
But anyway, I'm just doing this for fun, and hoping to learn a little more Ruby in the process. So if anyone can explain to me what is throw these errors, that would be great.
require 'complex'
h,s='.;*&8#',''
0.upto(800).each {|q|
if q%40 == 0
p s
s=''
end
i,k=0,0
while(k.abs<2 && (i<15))
k,i=k**2+Complex(q%40*0.075-2,q/40*-0.1+1),i+1
end
s+=h[i/3, 1]
}
Issues I dealt with:
Ruby conditionals return boolean values, not 1 or 0, and ! has a high priority
You were clobbering s before printing it
To work on 1.8.x and 1.9.x you need to index strings with [x, 1]
And here it is rearranged a little as a better starting point for a one-liner:
require 'complex'
h,s='.;*&8#',''
800.times { |q|
(p s; s='') if q%40 == 0
i,k=0,0
k,i=k**2+Complex(q%40*0.075-2,q/40*-0.1+1),i+1 while k.abs<2 && i<15
s+=h[i/3, 1]
}
Fist, get rid of the each, the block should go with upto. Once you did that, you'll get another error: undefined method '%' for false:FalseClass. This is because of !q%40, since precedence will first do the logical negation of q (anything but nil and false are true) and then try to evaluate false%40. Also you seem to assume that a zero would evaluate to false, which it doesn't. Then the next problem will be in the condition of your while loop, since k.abs<2 as well as i<15 evaluate to boolen values (`*': true can't be coerced into Fixnum). This should get you started...
Here's a multi-line version; feel free to put it all on one line:
h,s='.;*&8#','';
0.upto(800).each { |q|
(puts s;s='') if q%40==0;
i,k=0,0;
k,i=k**2+Complex(q%40*0.075-2,q/40*-0.1+1),i+1 while k.abs<2*(i<15?1:0);
s+=h[i/3]
}

Code folding on consecutive collect/select/reject/each

I play around with arrays and hashes quite a lot in ruby and end up with some code that looks like this:
sum = two_dimensional_array.select{|i|
i.collect{|j|
j.to_i
}.sum > 5
}.collect{|i|
i.collect{|j|
j ** 2
}.average
}.sum
(Let's all pretend that the above code sample makes sense now...)
The problem is that even though TextMate (my editor of choice) picks up simple {...} or do...end blocks quite easily, it can't figure out (which is understandable since even I can't find a "correct" way to fold the above) where the above blocks start and end to fold them.
How would you fold the above code sample?
PS: considering that it could have 2 levels of folding, I only care about the outer consecutive ones (the blocks with the i)
To be honest, something that convoluted is probably confusing TextMate as much as anyone else who has to maintain it, and that includes you in the future.
Whenever you see something that rolls up into a single value, it's a good case for using Enumerable#inject.
sum = two_dimensional_array.inject(0) do |sum, row|
# Convert row to Fixnum equivalent
row_i = row.collect { |i| i.to_i }
if (row_i.sum > 5)
sum += row_i.collect { |i| i ** 2 }.average
end
sum # Carry through to next inject call
end
What's odd in your example is you're using select to return the full array, allegedly converted using to_i, but in fact Enumerable#select does no such thing, and instead rejects any for which the function returns nil. I'm presuming that's none of your values.
Also depending on how your .average method is implemented, you may want to seed the inject call with 0.0 instead of 0 to use a floating-point value.

Troubles: Matrices, Vectors and Arrays

As far as I understood, matrices are very inflexible to work with. Therefor, I'm trying to get an array of vectors do deal with. My needs are: to be able to add vectors and make arithmetical operations on their components. Writing the code below,
require 'matrix'
x = Matrix.rows( IO.readlines("input.txt").each {|line| line.split} )
puts x.row_vectors
ruby falls into an exception. Why?
matrix.rb:1265:in `to_s': undefined method `join' for "1.2357 2.1742 -5.4834 -2.0735":String (NoMethodError)
OK then, I've calmed down and tried another approach. I wrote:
a = Array.[]( IO.readlines("input.txt").each {|line| Vector.[](line.split) } )
But the only way I can access my vectors inside an array is adressing the second index:
puts a[0][0]
This means, that when I would like to access desired scalar inside a vector, I'll will be forced to use the third index, like:
puts a[0][0][1]
So, the second question is - where the hell that second index comes from? How to get rid of it? Am I missing something when reading data into array?
I can't reproduce your first problem. Extracting what looks like input.txt, I can execute that first expression without an exception.
As to the second question, your expression seems kind of complex. How about:
b = IO.readlines("input.txt").map { |x| x.split(' ') }
This will get you a "2D" array of arrays, and you will need only two subscripts. (As to your question about where did the extra array come from, you got one from the Array constructor, one from IO.readlines, and one from the Vector constructor . . . I think.)
Or maybe:
result = []
IO.foreach('input.txt') { |ln| result << ln.split(' ') }

Resources