0

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.

2 Answers2

3

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(' ')
mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • 1
    A slight variant of your antepenultimate solution: `my_string.sub(/(\d+\.\d+) ([a-z])/) { sprintf("%.2f %s", $1, $2.capitalize) }`. – Cary Swoveland Dec 19 '21 at 08:36
  • This solution does not work in Ruby 2.4 since ```then``` wasn’t introduced till version 2.6. It should be noted that ```yield_self``` would work as well except that wasn’t introduced till version 2.5. In version 2.4, the only replacement I’ve found so far is ```instance_eval``` as noted in my own answer below. –  Dec 19 '21 at 10:08
1

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