'Ruby' way to check for regex match before using captures - ruby

In a Ruby Sinatrat app, I'm looking to retrieve some numbers associated with strings that may or may not be present in my input. For example: "Cruisers #" might or might not be present, and the # may be any integer.
In addition, where commas would be in normal English number notation (1,000 for one thousand), there will be a period in this notation (1.000 for one thousand).
match = /Cruiser\s*([\d.]*)/m.match(report)
match ?
self.cruiser = match.captures[0].gsub(".", "") :
self.cruiser = 0
Seems like there should be a more compact, 'Ruby'ish way to do this - specifically, I'm looking for a way to combine the regex.match call and the conditional assignment into one statement. Is this, or any other refactoring, possible here? Thanks.

def get_cruiser(str)
if str =~ /Cruiser\s*([\d.]*)/m
$1.gsub(".","")
else
0
end
end
puts get_cruiser("Cruiser 23.444.221")
puts get_cruiser("Crusier")
prints:
23444221
0

There was a problem. Updated.
report1 = 'Cruiser 23.444.221'
report2 = 'Cruiser'
report3 = ''
report4 = '23/04/2010 Cruiser 23.444.221'
class String
def cruiser_count; self[/Cruiser\s*[\d.]*/].to_s.scan(/\d+/).join.to_i end
end
p report1.cruiser_count # => 23444221
p report2.cruiser_count # => 0
p report3.cruiser_count # => 0
p report4.cruiser_count # => 23444221

The following 1 liner is all you need
'1234 Cruiser 1.222'.match(/Cruiser\s*([\d.]*)/).nil? ? 0 : $1.gsub('.', '').to_i
=> 1222

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"

convert an input string to an integer

I need to convert an input string to an integer using a get_integer_from_string() function.
I am using Ruby
and I have this schedule.
based on the schedule we need to covert from base 7 to base 10
Please note :
1- If you encounter a string with no numerical sequences, it should return zero (0).
2- The string can contain additional characters after those that form the integral number, which are ignored and have no effect on the behavior of this function.
Thank you for helping me with my homework.
As Tim pointed out above, your table shows base 7, not 6. Both String#to_i and Fixnum#to_s take an optional radix (base) argument:
p "020".to_i(7)
# => 14
p 14.to_s(7)
# => "20"
In order to deal with trailing characters that aren't 0-6, String#slice (a.k.a. String#[]) works well:
expr = /^[0-6]*/
p "72"[expr].to_i(7)
# => 0
p "0202xyz"[expr].to_i(7)
# => 100
p "27"[expr].to_i(7)
# => 2
What you want to do is convert the string (which represents a base 7 number) to a base 10 integer. This can be done via
"021".to_i(7)
# => 15
Please see the Documentation of Ruby String class for the method to_i()
i made this not sure if it's right
def get_integer_from_string(str)
return 0 if (/^(?<num>\d+)$/ =~ str).nil?
result = 0
str.reverse.each_char.with_index do |char, index|
tmp = char.to_i * 7**index
result += tmp
end
result rescue 0
end

Reversing a Ruby String, without .reverse method

I am working on this coding challenge, and I have found that I am stuck. I thought it was possible to call the .string method on an argument that was passed in, but now I'm not sure. Everything I've found in the Ruby documentation suggests otherwise. I'd really like to figure this out without looking at the solution. Can someone help give me a push in the right direction?
# Write a method that will take a string as input, and return a new
# string with the same letters in reverse order.
# Don't use String's reverse method; that would be too simple.
# Difficulty: easy.
def reverse(string)
string_array = []
string.split()
string_array.push(string)
string_array.sort! { |x,y| y <=> x}
end
# These are tests to check that your code is working. After writing
# your solution, they should all print true.
puts(
'reverse("abc") == "cba": ' + (reverse("abc") == "cba").to_s
)
puts(
'reverse("a") == "a": ' + (reverse("a") == "a").to_s
)
puts(
'reverse("") == "": ' + (reverse("") == "").to_s
)
This is the simplest one line solution, for reversing a string without using #reverse, that I have come across -
"string".chars.reduce { |x, y| y + x } # => "gnirts"
Additionally, I have never heard of the #string method, I think you might try #to_s.
Easiest way to reverse a string
s = "chetan barawkar"
b = s.length - 1
while b >= 0
print s[b]
b=b-1
end
You need to stop the search for alternative or clever methods, such as altering things so you can .sort them. It is over-thinking the problem, or in some ways avoiding thinking about the core problem you have been asked to solve.
What this test is trying to get you you to do, is understand the internals of a String, and maybe get an appreciation of how String#reverse might be implemented using the most basic string operations.
One of the most basic String operations is to get a specific character from the string. You can get the first character by calling string[0], and in general you can get the nth character (zero-indexed) by calling string[n].
In addition you can combine or build longer strings by adding them together, e.g. if you had a="hell" and b="o", then c = a + b would store "hello" in the variable c.
Using this knowledge, find a way to loop through the original string and use that to build the reverse string, one character at a time. You may also need to look up how to get the length of a string (another basic string method, which you will find in any language's string library), and how to loop through numbers in sequence.
You're on the right track converting it to an array.
def reverse(str)
str.chars.sort_by.with_index { |_, i| -i }.join
end
Here is a solution I used to reverse a string without using .reverse method :
#string = "abcde"
#l = #string.length
#string_reversed = ""
i = #l-1
while i >=0 do
#string_reversed << #string[i]
i = i-1
end
return #string_reversed
Lol, I am going through the same challenge. It may not be the elegant solution, but it works and easy to understand:
puts("Write is a string that you want to print in reverse")
#taking a string from the user
string = gets.to_s #getting input and converting into string
def reverse(string)
i = 0
abc = [] # creating empty array
while i < string.length
abc.unshift(string[i]) #populating empty array in reverse
i = i + 1
end
return abc.join
end
puts ("In reverse: " + reverse(string))
Thought i'd contribute my rookie version.
def string_reverse(string)
new_array = []
formatted_string = string.chars
new_array << formatted_string.pop until formatted_string.empty?
new_array.join
end
def reverse_str(string)
# split a string to create an array
string_arr = string.split('')
result_arr = []
i = string_arr.length - 1
# run the loop in reverse
while i >=0
result_arr.push(string_arr[i])
i -= 1
end
# join the reverse array and return as a string
result_arr.join
end

Ruby function that changes each letter for a string and also capitalizes vowels?

I am trying to do a challenge on Coderbyte. The question states:
Have the function LetterChanges(str) take the str parameter being passed and modify it using the following algorithm. Replace every letter in the string with the letter following it in the alphabet (ie. c becomes d, z becomes a). Then capitalize every vowel in this new string (a, e, i, o, u) and finally return this modified string.
Here is my code:
hash = {"a" => 1,"b" => 2,"c" => 3,"d" => 4,"e" => 5,"f" => 6,"g" => 7,"h" => 8,"i" => 9,"j" => 10, "k" => 11,"l" => 12,"m" => 13,"n" => 14,"o" => 15,"p" => 16,"q" => 17,"r" => 18,"s" => 19,"t" => 20,"u" => 21,"v" => 22,"w" => 23,"x" => 24,"y" => 25,"z" => 26}
def LetterChanges(str)
chars = str.split("")
newstr = Array.new
i = 0
newletter = 0
while i <= chars.length
if hash.has_key?(chars[i]) == true #I think this is where the problem is
newletter = hash[chars[i]] + 1
newstr.push(has.key(newletter))
if newstr[i].include?('a','e','i','o','u')
newstr[i].upcase!
end
else
newstr.push(chars[i])
end
i += 1
end
return newstr
end
It keeps saying there is an error with 'has_key?'. I also tried using '.include?' and 'chars[i] =~ [a-zA-Z]' but all return an error. I'm not sure why it isn't accepting any of these methods/regex. If you do decide to answer using regular expressions, please explain in details because they still confuse me a little.
Thanks in advance.
***EDIT: I have taken all of your advice and thought I had a working code, but apparently not. =/
I get this error: (eval):8: undefined method key' for #<Hash:0x149bf0> (NoMethodError) from (eval):4:ineach' from (eval):4:in `LetterChanges' from (eval):18
1 def LetterChanges(str)
2 hash = {"a" => 0,"b" => 1,"c" => 2,"d" => 3,"e" => 4,"f" => 5,"g" => 6,"h" => 7,"i" => 8,"j" => 9, "k" => 10,"l" => 11,"m" => 12,"n" => 13,"o" => 14,"p" => 15,"q" => 16,"r" => 17,"s" => 18,"t" => 19,"u" => 20,"v" => 21,"w" => 22,"x" => 23,"y" => 24,"z" => 25}
3 newstr = Array.new
4 newletter = 0
5 str.each do |i|
6 if hash.has_key?(str[i])
7 newletter = hash[str[i]] + 1
8 newletter = 0 if newletter == 25
9 newstr.push(hash.key(newletter))
10 newstr[i].upcase! if newstr[i] =~ /[aeiou]/
11 else
12 newstr.push(str[i])
13 end
14 end
15 return newstr.to_s
16end
First of all I don't know Coderbyte but it's common practice in Ruby to use snake_case for naming methods (but that's not important here)
The biggest point is this: As soon as you get a 'z' on your input string, you're gonna have a baaad time: In the Line newletter = hash[chars[i]] + 1 you correctly determine the letter's ID (hash[chars[i]], which results in 26) and add 1. However, when converting back to a letter (in the line which I assume should be like this: newstr.push(hash.key(newletter)) - you mistyped hash) you reference a value (27) which does not exist in the hash!
Next, you use include? the wrong way around. Note that include? only takes one argument. We actually have two ways to check if a char is a vovel:
letter.upcase! if %{a e i o u}.include?(letter)
(note that %w{a e i o u} constructs an array containing all the letters). Or:
letter.upcase! if letter =~ /[aeiou]/
(=~ checks if letter matches the RegEx /[aeiou]/)
This should help you get it working!
Here's a working example (but try solving it yourself first!!!) https://gist.github.com/mhutter/8678067
Here are some Tips:
When using Hashes with numbers, it's good practice to make them Zero-Indexed: the lowest number (in your case 'a' => 1 should always be 0).
The % operator returns the remainder of a division. This is especially handy when incrementing indexes of an array. array[(i+1) % array.length] will always get you a valid value.
Your line if hash.has_key?(chars[i]) == true will work as expected, BUT Hash#has_key? already returns true or false so no need for checking for true. Simply write if hash.has_key?(chars[i]).
Strings can be accessed like this: "Foobar"[3], so there's no need splitting them up.
BUT if you do so, you can then do this:
chars = str.split ""
chars.each do |char|
# do something with char
end
however, if you do NOT split your string :), you can replace this:
i = 0
while i <= chars.length
# current char is char[i]
with this:
0.upto(chars.length) do |i|
# current char is char[i]
Constructs like this:
if something?
do_something
end
can be shortened to this:
do_something if something?
I believe your problem with has_key? is because hash is not in the scope of that method. Move the hash={blah} to the line after the def, and also take snowe2010's advice on refactoring the code to make it more ruby like.
I think you meant to put newstr.push(hash.key(newletter)) not newstr.push(has.key(newletter)).
Also, you declare hash as a class variable and not an instance variable. Try puts hash inside the function and you'll see what I mean.
I would also suggest changing your method definition to follow a few ruby conventions. In ruby, we usually use snake case unless it's a class, i.e. def letter_changes(str).
We also don't use while if we don't have to. You can change that code to look like this
chars.each_with_index do |current_char, i|
if hash.has_key?(current_char)
newletter = hash[current_char] + 1
newstr.push(hash.key(newletter))
newstr[i].upcase! if newstr[i].include?('a','e','i','o','u')
else
newstr.push(current_char)
end
end
And we also don't usually return explicitly unless it's from within an if/else/unless/case statement.
There is a pretty easy two line way to do that....
def LetterChanges(str)
str.tr!('a-y','b-z')
str.tr!('aeiou','AEIOU')
return str
end
Nate Beers' two-line solution above is almost perfect but doesn't seem to wrap z -> a. Unfortunately, I don't have the reputation to leave this correction as a comment.
You can solve this by modifying his code to:
def LetterChanges(str)
str.tr!('a-z','b-za')
str.tr!('aeiou','AEIOU')
return str
end
Is this a typo?
newstr.push(has.key(newletter)) instead of hash.key
If that's not the problem, post the error with 'has_key?'.

How to format float with spaces after every 3 decimal places in Ruby?

Is it possible to use Ruby number formatting (such as sprinf or other?) to format floats with spaces after every 3 decimal places?
1.5 => 1.5
1.501 => 1.501
1.501001 => 1.501 001
1.501001001 => 1.501 001 001
Is there another easy way to do it?
Working in Ruby, not Rails.
I don't believe there is any built-in support for this, but you can change the behavior of Float#inspect and Float#to_s as follows:
class Float
alias :old_inspect :inspect
def inspect
pieces = old_inspect.split('.')
if pieces.length == 1
pieces[0]
else
pieces[1].gsub!(/(...)(?!$)/,'\1 ')
pieces.join('.')
end
end
alias :to_s :inspect
end
Note: I only minimally tested this and there are certainly more elegant ways to code it in terms of the Ruby string operations. There is also significant risk this will screw up code that depends on the traditional float formatting.
Using String methods:
def add_sep(s, n=3, sep=' ')
s.split('').each_slice(n).map(&:join).join(sep)
end
def add_spaces(fl)
f, l = fl.to_s.split('.')
f + '.' + add_sep(l)
end
add_spaces(1.5) # => "1.5"
add_spaces(1.501) # => "1.501"
add_spaces(1.50101) # => "1.501 01"
add_spaces(1.501011) # => "1.501 011"
add_spaces(1.501001001) # => "1.501 001 001"
def add_spaces_both_sides(fl)
f, l = fl.to_s.split('.')
add_sep(f.reverse).reverse + '.' + add_sep(l)
end
add_spaces_both_sides(1234567.12345) # => "1 234 567.123 45"
You can accomplish this by opening the Float class and adding the following method:
class Float
def format_spaced
parts = self.to_s.split(".")
parts[0] + "." + parts[1].scan(/\d{1,3}/).join(" ")
end
end
f = 1.23456789
f.format_spaced # => "1.234 567 89"
Fairly straightforward I hope!
It's technically part of rails, but you could grab some code from the number helpers. Namely number_with_delimiter:
number_with_delimiter(98765432.98, delimiter: " ", separator: ",")
# => 98 765 432,98
http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_with_delimiter
The meat of it is in the NumberToRoundedConverter class.

Resources