Delete the last instance of a letter in string - ruby

How do I delete only the last "l" from a string and not the others?
string = "Hello"
desired outcome:
string # => "Helo"
I did:
string.delete!("l")
string #= > "Heo"

string[string.rindex('l')] = ''

You can use sub to replace a single occurrence and tweak the regexp to replace the last match.
string = "Homemade"
string.sub(/(.*)m/, '\1')
# => "Homeade"
In your case the regexp will be
string.sub(/(.*)l/, '\1')

str = 'hello'
r = /
.* # match any number of any character
\K # discard everything matched so far
l # match last 'l'
/x # extended mode
str.gsub(r,'')
#=> "helo"

Related

Find nth occurrence of variable regex in Ruby?

Writing a method for what the question says, need to find the index of the nth occurrence of a particular left bracket (defined by the user, i.e. if user provides a string with the additional parameters '{' and '5' it will find the 5th occurrence of this, same with '(' and '[').
Currently doing it with a while loop and comparing each character but this looks ugly and isn't very interesting, is there a way to do this with regex? Can you use a variable in a regex?
def _find_bracket_n(str,left_brac,brackets_num)
i = 0
num_of_left_bracs = 0
while i < str.length && num_of_left_bracs < brackets_num
num_of_left_bracs += 1 if str[i] == left_brac
i += 1
end
n_th_lbrac_index = i - 1
end
The offset of the nth instance of a given character in a string is wanted, or nil if the string contains fewer than n instances of that character. I will give four solutions.
chr = "("
str = "a(b(cd((ef(g(hi("
n = 5
Use Enumerable#find_index
str.each_char.find_index { |c| c == chr && (n = n-1).zero? }
#=> 10
Use a regular expression
chr_esc = Regexp.escape(chr)
#=> "\\("
r = /
\A # match the beginning of the string
(?: # begin a non-capture group
.*? # match zero or more characters lazily
#{chr_esc} # match the given character
) # end the non-capture group
{#{n-1}} # perform the non-capture group `n-1` times
.*? # match zero or more characters lazily
#{chr_esc} # match the given character
/x # free-spacing regex definition mode
#=> /
\A # match the beginning of the string
(?: # begin a non-capture group
.*? # match zero or more characters lazily
\( # match the given character
) # end the non-capture group
{4} # perform the non-capture group `n-1` times
.*? # match zero or more characters lazily
\( # match the given character
/x
str =~ r
#=> 0
$~.end(0)-1
#=> 10
For the last line we could instead write
Regexp.last_match.end(0)-1
See Regexp::escape, Regexp::last_match and MatchData#end.
The regex is conventionally written (i.e., not free-spacing mode) written as follows.
/\A(?:.*?#{chr_esc}){#{n-1}}.*?#{chr_esc}/
Convert characters to offsets, remove offsets to non-matching characters and return the nth offset of those that remain
str.size.times.select { |i| str[i] == chr }[n-1]
#=> 10
n = 20
str.size.times.select { |i| str[i] == chr }[n-1]
#=> nil
Use String#index repeatedly to decapitate substrings
s = str.dup
n.times.reduce(0) do |off,_|
i = s.index(chr)
break nil if i.nil?
s = s[i+1..-1]
off + i + 1
end - 1
#=> 10

i have a regular expression that i need to figure out

"peter,nick,jake,jack"
i need to have something like this.
i cannot have any whitespace after the word for example,
"peter,," "peter," "peter,,nick " will all be incorrect.
it has to be just a word such as "peter" or a word follow by a comma then word ("peter,nick")
First confirm that the string has the required structure.
r = /
\A # match the beginning of the string
[[:alpha:]]+ # match > 0 letters
(?:,[[:alpha:]]+) # match a comma then > 0 letters in a non-capture group
* # match the preceding non-capture group >= 0 times
\z # match end of the string
/x # free-spacing regex definition mode
str = "peter,nick,jake,jack"
str =~ r #=> 0
Since it matches the regex, simply split on commas to return an array of the words.
str.split(',') #=> ["peter", "nick", "jake", "jack"]
By contrast:
"peter,nick,,jake,jack" =~ r #=> nil
"peter,nick,jake, jack" =~ r #=> nil
"peter,nick,jake,jack " =~ r #=> nil
"peter ispeter,nick" =~ r #=> nil
I assume the string must contain at least one letter.

Removing trailings zeros in string

I have a string and I need to remove trailing zeros after the 2nd decimal place:
remove_zeros("1,2,3,4.2300") #=> "1,2,3,4.23"
remove_zeros("1,2,3,4.20300") #=> "1,2,3,4.203"
remove_zeros("1,2,3,4.0200") #=> "1,2,3,4.02"
remove_zeros("1,2,3,4.0000") #=> "1,2,3,4.00"
Missing zeros don't have to be appended, i.e.
remove_zeros("1,2,3,4.0") #=> "1,2,3,4.0"
How could I do this in Ruby? I tried with converting into Float but it terminates the string when I encounter a ,. Can I write any regular expression for this?
Yes, a regular expression could be used.
R = /
\. # match a decimal
\d*? # match one or more digits lazily
\K # forget all matches so far
0+ # match one or more zeroes
(?!\d) # do not match a digit (negative lookahead)
/x # free-spacing regex definition mode
def truncate_floats(str)
str.gsub(R,"")
end
truncate_floats "1,2,3,4.2300"
#=> "1,2,3,4.23"
truncate_floats "1.34000,2,3,4.23000"
#=> "1.34,2,3,4.23"
truncate_floats "1,2,3,4.23003500"
#=> "1,2,3,4.230035"
truncate_floats "1,2,3,4.3"
#=> "1,2,3,4.3"
truncate_floats "1,2,3,4.000"
#=> "1,2,3,4."
> a = "1,2,3,4.2300"
> a.split(",").map{|e| e.include?(".") ? e.to_f : e}.join(",")
#=> "1,2,3,4.23"
> a = "1,2,3,4.20300"
> a.split(",").map{|e| e.include?(".") ? e.to_f : e}.join(",")
#=> "1,2,3,4.203"
First, you need to parse the string into its component numbers, then remove the trailing zeros on each number. This can be done by:
1) splitting the string on ',' to get an array of numeric strings
2) for each numeric string, convert it to a Float, then back to a string:
#!/usr/bin/env ruby
def parse_and_trim(string)
number_strings = string.split(',')
number_strings.map { |s| Float(s).to_s }.join(',')
end
p parse_and_trim('1,2,3,4.2300') # => "1.0,2.0,3.0,4.23"
If you really want to remove the trailing '.0' fragments, you could replace the script with this one:
#!/usr/bin/env ruby
def parse_and_trim_2(string)
original_strings = string.split(',')
converted_strings = original_strings.map { |s| Float(s).to_s }
trimmed_strings = converted_strings.map do |s|
s.end_with?('.0') ? s[0..-3] : s
end
trimmed_strings.join(',')
end
p parse_and_trim_2('1,2,3,4.2300') # => "1,2,3,4.23"
These could of course be made more concise, but I've used intermediate variables to clarify what's going on.

How to write a regex in a single line

I have this code:
str = 'printf("My name is %s and age is %0.2d", name, age);'
SPECIFIERS = 'diuXxofeEgsc'
format_specifiers = /((?:%(?:\*?([-+]?\d*\.?\d+)*(?:[#{SPECIFIERS}]))))/i
variables = /([.[^"]]*)\);$/
format = str.scan(format_specifiers)
var = str.scan(variables).first.first.split(/,/)
Is there any way a single regex can do that in a couple of lines?
My desired output is:
%s, name
%0.2d, age
I'm a big believer in keeping regular expressions as simple as possible; They can too quickly mushroom into unwieldy/unmaintainable messes. I'd start with something like this, then tweak as necessary:
str = 'printf("My name is %s and age is %0.2d", name, age);'
formats = str.scan(/%[a-z0-9.]+/) # => ["%s", "%0.2d"]
str[/,(.+)\);$/] # => ", name, age);"
vars = str[/,(.+)\);$/].scan(/[a-z]+/) # => ["name", "age"]
puts formats.zip(vars).map{ |a| a.join(', ')}
# >> %s, name
# >> %0.2d, age
Your question has two parts:
Q1: Is it possible to do this with a single regex?
Q2: Can this be done in one or two lines of code?
The answer to both questions is "yes".
format_specifiers = /
%[^\s\"\z]+ # match % followed by > 0 characters other than a
# whitespace, a double-quote or the end of the string
/x # free-spacing regex definition mode
variables = /
,\s* # match comma followed by >= 0 whitespaces
\K # forget matches so far
[a-z] # match a lowercase letter
\w* # match >= 0 word characters
/x
You can decide, after testing, if these two regexes do their jobs adequately. For testing, refer to Kernel#sprintf.
r = /
(?:#{format_specifiers}) # match format_specifiers in a non-capture group
| # or
(?:#{variables}) # match variables in a non-capture group
/x
#=> /
(?:(?x-mi:
%[^\s\"\z]+ # match % followed by > 0 characters other than a
# whitespace, a double-quote or the end of the string
)) # match format_specifiers in a non-capture group
| # or
(?:(?x-mi:
,\s* # match comma followed by >= 0 whitespaces
\K # forget matches so far
[a-zA-Z] # match a letter
\w* # match >= 0 word characters
)) # match variables in a non-capture group
/x
r can of course also be written:
/(?:(?x-mi:%[^\s\"\z]+))|(?:(?x-mi:,\s*\K[a-zA-Z]\w*))/
One advantage of constructing r from two regexes is that each of the latter can be tested separately.
str = 'printf("My name is %s and age is %0.2d", name, age);'
arr = str.scan(r)
#=> ["%s", "%0.2d", "name", "age"]
arr.each_slice(arr.size/2).to_a.transpose.map { |s| s.join(', ') }
#=> ["%s, name", "%0.2d, age"]
I have five lines of code. We could reduce this to two by simply substituting out r in str.scan(r). We could make it a single line by writing:
str.scan(r).tap { |a|
a.replace(a.each_slice(a.size/2).to_a.transpose.map { |s| s.join(', ') }) }
#=> ["%s, name", "%0.2d, age"]
with r substituted out.
The steps here are as follows:
a = str.scan(r)
#=> ["%s", "%0.2d", "name", "age"]
b = a.each_slice(a.size/2)
#=> a.each_slice(2)
#=> #<Enumerator: ["%s", "%0.2d", "name", "age"]:each_slice(2)>
c = b.to_a
#=> [["%s", "%0.2d"], ["name", "age"]]
d = c.transpose
#=> [["%s", "name"], ["%0.2d", "age"]]
e = d.map { |s| s.join(', ') }
#=> ["%s, name", "%0.2d, age"]
a.replace(e)
#=> ["%s, name", "%0.2d, age"]
The methods used (aside from Array#size) are String#scan, Enumerable#each_slice, Enumerable#to_a, Enumerable#map, Array#transpose and Array#replace.

Replace s string in Ruby by gsub

I have a string
path = "MT_Store_0 /47/47/47/opt/47/47/47/data/47/47/47/FCS/47/47/47/oOvt4wCtSuODh8r9RuQT3w"
I want to remove the part of string from first /47 using gsub.
path.gsub! '/47/', '/'
Expected output:
"MT_Store_0 "
Actual output:
"MT_Store_0 /47/opt/47/data/47/FCS/47/oOvt4wCtSuODh8r9RuQT3w"
path.gsub! /\/47.*/, ''
In the regex, \/47.* matches /47 and any characters following it.
Or, you can write the regex using %r to avoid escaping the forward slashes:
path.gsub! %r{/47.*}, ''
If the output have to be MT_Store_0
then gsub( /\/47.*/ ,'' ).strip is what you want
Here are two solutions that employ neither Hash#gsub nor Hash#gsub!.
Use String#index
def extract(str)
ndx = str.index /\/47/
ndx ? str[0, ndx] : str
end
str = "MT_Store_0 /47/47/oOv"
str = extract str
#=> "MT_Store_0 "
extract "MT_Store_0 cat"
#=> "MT_Store_0 cat"
Use a capture group
R = /
(.+?) # match one or more of any character, lazily, in capture group 1
(?: # start a non-capture group
\/47 # match characters
| # or
\z # match end of string
) # end non-capture group
/x # extended mode for regex definition
def extract(str)
str[R, 1]
end
str = "MT_Store_0 /47/47/oOv"
str = extract str
#=> "MT_Store_0 "
extract "MT_Store_0 cat"
#=> "MT_Store_0 cat"

Resources