I know in ruby you can search and replace a string using gsub and a regular expression.
However is there away to apply an expression to the result of the search and replace before it is replaced.
For example in the code below though you can use the matched string with \0 you cannot apply expressions to it (eg \0.to_i * 10) and doing so results in an error:
shopping_list = <<LIST
3 apples
500g flour
1 ham
LIST
new_list = shopping_list.gsub(/\d+/m, \0.to_i * 10)
puts new_list #syntax error, unexpected $undefined
It seems to only work with a string literal:
shopping_list = <<LIST
3 apples
500g flour
1 ham
LIST
new_list = shopping_list.gsub(/\d+/m, '\0 string and replace')
puts new_list
Is this what you want?
shopping_list = <<LIST
3 apples
500g flour
1 ham
LIST
new_list = shopping_list.gsub(/\d+/m) do |m|
m.to_i * 10
end
puts new_list
# >> 30 apples
# >> 5000g flour
# >> 10 ham
Documentation: String#gsub.
Related
What is the efficient and straightforward code for finding array elements in a string.
For example:
a = Array['aaa', 'bbb', 'ccc', 'ddd', 'eee']
b = "This is the sample string with aaa , blah blah"
c = someFunction(b, a)
puts c
=> ['aaa']
Suppose a array have 100 elements, I want to know which of array element is found in the string.
I should match exacct word. So xbbb, bbaa, ... not matched.
I think this is one of the possible solutions:
def some_method(string, array)
string.split & array
end
a = Array['aaa', 'bbb', 'ccc', 'ddd', 'eee']
b = "This is the sample string with aaa , blah blah"
> some_method(b, a)
=> ['aaa']
a = Array['aaa', 'bbb', 'ccc', 'ddd', 'eee']
b = "This is the sample string with xaaa , blah blah"
> some_method(b, a)
=> []
def find_elements(my_string, my_array)
my_string.split & my_array
end
You can split the string into an array and then find the intersection of both arrays using & or even intersection if you are on ruby 2.7. This will return an array containing all of the unique matching elements.
One way I found is like below -
array = Array['aaa', 'bbb', 'ccc', 'ddd', 'eee']
string = "This is the sample string with aaa , blah blah"
found = []
array.each { |a| found << a if string.include? a }
puts found
=> ["aaa"]
EDIT
After knowing another use case where it is needed exact match and as include? matches 'aaa' even if it is in 'xxaaa', one possible solution is using Set Intersection with Arrays in Ruby -
def some_methodd(array, string)
string.split & array
end
Then it will return the exact match.
=> ["aaa"]
You can also use #select with a regular expression to determine which array elements are in the string.
def check_string(ary, str)
ary.select do |e|
str =~ /\b#{e}\b/
end
end
p check_string(%w(aaa bbb ccc), 'Here is a saaample bbb string ccc') # => ['bbb', 'ccc']
This gives you a lot of flexibility as to what matches and what doesn't, since if you want to change that, all you have to do is change the regex. This example assumes that you want whole word matches with words in an array.
We can do it in Ruby: "I have %{amount} %{food}" % {amount: 5, food: 'apples'} to get "I have 5 apples". Is there common way for the inverse transformation: using "I have 5 apples" and "I have %{amount} %{food}" to get {amount: 5, food: 'apples'}?
def doit(s1, s2)
a1 = s1.split
a2 = s2.split
a2.each_index.with_object({}) do |i,h|
word = a2[i][/(?<=%\{).+(?=\})/]
h[word.to_sym] = a1[i] unless word.nil?
end.transform_values { |s| s.match?(/\A\-?\d+\z/) ? s.to_i : s }
end
s1 = "I have 5 apples"
s2 = "I have %{amount} %{food}"
doit(s1, s2)
#=> {:amount=>5, :food=>"apples"}
s1 = "223 parcels were delivered last month"
s2 = "%{number} parcels were %{action} last %{period}"
doit(s1, s2)
#=> {:number=>223, :action=>"delivered", :period=>"month"}
The regular expression reads, "match one or more characters (.+), immediately preceded by "%{" ((?<=%\{) being a positive lookbehind) and immediately followed by "}" ((?=\}) being a positive lookahead).
If the substrings are separated with spaces, you could find the corresponding regex with named captures:
text = "I have 5 apples"
# "I have %{amount} %{food}"
format = /\AI have (?<amount>\S+) (?<food>\S+)\z/
p text.match(format).named_captures
# {"amount"=>"5", "food"=>"apples"}
You didn't show any code, so I'll leave it as an exercise to transform the "I have %{amount} %{food}" string into the /\AI have (?<amount>\S+) (?<food>\S+)\z/ regex.
Trying to write a Ruby code that will count unique words and return their total occurrences.
So suppose I want to find number of occurrences for Sally, Marina and Tina in the following sentence "Monday Tina will meet Sally and Harris. Then Tina will visit her mom Marina. Marina and Tina will meet David for dinner."
I tried the following but this defeats the dry principal. Is there a better way?
string = "Monday Tina will meet Sally and Harris. Then Tina will visit her mom Marina. Marina and Tina will meet David for dinner. Sally will then take Tina out for a late night party."
puts "Marina appears #{string.split.count("brown").to_i} times."
puts "Tina appears #{string.split.count("grey").to_i} times."
puts "Sally appears #{string.split.count("blue").to_i} times."
Expected result: program looks through the text for unique words and returns them.
Actual: I had to hard code each unique word on its own PUTS line and do string.split.count(for that unique word)
Note:
I tried the following but this gives me EVERY word. I need to refine it to give me just the ones I ask for. This is where I am struggling.
def cw(string)
w = string.split(' ')
freq = Hash.new(0)
w.each { |w| freq[w.downcase] += 1 }
return freq
end
puts cw(string)
def count_em(str, who)
str.gsub(/\b(?:#{who.join('|')})\b/i).
each_with_object(Hash.new(0)) { |person,h| h[person] += 1 }
end
str = "Monday Tina will meet Sally and Harris. Then Tina will visit her " +
"mom Marina. Marina and Tina will meet David for dinner. Sally will " +
"then take Tina out for a late night party."
who = %w| Sally Marina Tina |
count_em(str, who)
#> {"Tina"=>4, "Sally"=>2, "Marina"=>2}
The first steps are as follows.
r = /\b(?:#{who.join('|')})\b/i
#=> /\b(?:Sally|Marina|Tina)\b/i
enum = str.gsub(r)
#=> #<Enumerator: "Monday Tina will meet Sally and Harris. Then
# ...
# for a late night party.":gsub(/\b(?:Sally|Marina|Tina)\b/i)>
We can convert this to an array to see the values that will be passed to each_with_object.
enum.to_a
#=> ["Tina", "Sally", "Tina", "Marina", "Marina", "Tina", "Sally", "Tina"]
We then simply count the number of instances of the unique values generated by enum.
enum.each_with_object(Hash.new(0)) { |person,h| h[person] += 1 }
#=> {"Tina"=>4, "Sally"=>2, "Marina"=>2}
See String#gsub, in particular the case when there is one argument and no block. This is admittedly an unusual use of gsub, as it is making no substitutions, but here I prefer it to String#scan because gsub returns an enumerator whereas scan produces a temporary array.
See also Hash::new, the case where new takes an argument and no block. The argument is called the default value. If h is the hash so-defined, the default value is returned by h[k] if h does not have a key k. The hash is not altered.
Here the default value is zero. When the expression h[person] += 1 it is parsed it is converted to:
h[person] = h[person] + 1
If person equals "Tina", and it is the first time "Tina" is generated by the enumerator and passed to the block, h will not have a key "Tina", so the expression becomes:
h["Tina"] = 0 + 1
as 0 is the default value. The next time "Tina" is passed to the block the hash has a key "Tina" (with value 1), so the following calculation is performed.
h["Tina"] = h["Tina"] + 1 #=> 1 + 1 #=> 2
To get only the required people name:
people = ['Marina', 'Tina', 'Sally', 'Dory']
tmp = string.scan(/\w+/).keep_if{ |w| people.include? w }
counts people.map{ |name| [name, tmp.count{|n| n == name }] }.to_h
counts #=> {"Marina"=>2, "Tina"=>4, "Sally"=>2, "Dory"=>0}
This maps the peopole array against tmp to a nested array containing [name, count], then converted to a hash.
The good is that it returns 0 if people doesn't appear, see 'Dory'.
To get the total count, two ways:
tmp.size #=> 8
counts.values.sum #=> 8
This question already has an answer here:
Reference - What does this regex mean?
(1 answer)
Closed 5 years ago.
I have a string:
"1 chocolate bar at 25"
and I want to split this string into:
[1, "chocolate bar", 25]
I don't know how to write a regex for this split. And I wanted to know whether there are any other functions to accomplish it.
You could use scan with a regex:
"1 chocolate bar at 25".scan(/^(\d+) ([\w ]+) at (\d+)$/).first
The above method doesn't work if item_name has special characters.
If you want a more robust solution, you can use split:
number1, *words, at, number2 = "1 chocolate bar at 25".split
p [number1, words.join(' '), number2]
# ["1", "chocolate bar", "25"]
number1 is the first part, number2 is the last one, at the second to last, and *words is an array with everything in-between. number2 is guaranteed to be the last word.
This method has the advantage of working even if there are numbers in the middle, " at " somewhere in the string or if prices are given as floats.
It is not necessary to use a regular expression.
str = "1 chocolate bar, 3 donuts and a 7up at 25"
i1 = str.index(' ')
#=> 1
i2 = str.rindex(' at ')
#=> 35
[str[0,i1].to_i, str[i1+1..i2-1], str[i2+3..-1].to_i]
#=> [1, "chocolate bar, 3 donuts and a 7up", 25]
I would do:
> s="1 chocolate bar at 25"
> s.scan(/[\d ]+|[[:alpha:] ]+/)
=> ["1 ", "chocolate bar at ", "25"]
Then to get the integers and the stripped string:
> s.scan(/[\d ]+|[[:alpha:] ]+/).map {|s| Integer(s) rescue s.strip}
=> [1, "chocolate bar at", 25]
And to remove the " at":
> s.scan(/[\d ]+|[[:alpha:] ]+/).map {|s| Integer(s) rescue s[/.*(?=\s+at\s*)/]}
=> [1, "chocolate bar", 25]
You may try returning captures property of match method on regex (\d+) ([\w ]+) at (\d+):
string.match(/(\d+) +(\D+) +at +(\d+)/).captures
Live demo
Validating input string
If you didn't validate your input string to be within desired format already, then there may be a better approach in validating and capturing data. This solution also brings the idea of accepting any type of character in item_name field and decimal prices at the end:
string.match(/^(\d+) +(.*) +at +(\d+(?:\.\d+)?)$/).captures
You can also do something like this:
"1 chocolate bar at 25"
.split()
.reject {|string| string == "at" }
.map {|string| string.scan(/^\D+$/).empty? ? string.to_i : string }
Code Example: http://ideone.com/s8OvlC
I live in the country where prices might be float, hence the more sophisticated matcher for the price.
"1 chocolate bar at 25".
match(/\A(\d+)\s+(.*?)\s+at\s+(\d[.\d]*)\z/).
captures
#⇒ ["1", "chocolate bar", "25"]
My question is about how to convert array elements to string in ruby 1.9 without getting the brackets and quotation marks. I've got an array (DB extract), from which I want to use to create a periodic report.
myArray = ["Apple", "Pear", "Banana", "2", "15", "12"]
In ruby 1.8 I had the following line
reportStr = "In the first quarter we sold " + myArray[3].to_s + " " + myArray[0].to_s + "(s)."
puts reportStr
Which produced the (wanted) output
In the first quarter we sold 2 Apple(s).
The same two lines in ruby 1.9 produce (not wanted)
In the first quarter we sold ["2"] ["Apple"] (s).
After reading in the documentation
Ruby 1.9.3 doc#Array#slice
I thought I could produce code like
reportStr = "In the first quarter we sold " + myArray[3] + " " + myArray[0] + "(s)."
puts reportStr
which returns a runtime error
/home/test/example.rb:450:in `+': can't convert Array into String (TypeError)
My current solution is to remove brackets and quotation marks with a temporary string, like
tempStr0 = myArray[0].to_s
myLength = tempStr0.length
tempStr0 = tempStr0[2..myLength-3]
tempStr3 = myArray[3].to_s
myLength = tempStr3.length
tempStr3 = tempStr3[2..myLength-3]
reportStr = "In the first quarter we sold " + tempStr3 + " " + tempStr0 + "(s)."
puts reportStr
which in general works.
However, what would be a more elegant "ruby" way how to do that?
You can use the .join method.
For example:
my_array = ["Apple", "Pear", "Banana"]
my_array.join(', ') # returns string separating array elements with arg to `join`
=> Apple, Pear, Banana
Use interpolation instead of concatenation:
reportStr = "In the first quarter we sold #{myArray[3]} #{myArray[0]}(s)."
It's more idiomatic, more efficient, requires less typing and automatically calls to_s for you.
And if you need to do this for more than one fruit the best way is to transform the array and the use the each statement.
myArray = ["Apple", "Pear", "Banana", "2", "1", "12"]
num_of_products = 3
tranformed = myArray.each_slice(num_of_products).to_a.transpose
p tranformed #=> [["Apple", "2"], ["Pear", "1"], ["Banana", "12"]]
tranformed.each do |fruit, amount|
puts "In the first quarter we sold #{amount} #{fruit}#{amount=='1' ? '':'s'}."
end
#=>
#In the first quarter we sold 2 Apples.
#In the first quarter we sold 1 Pear.
#In the first quarter we sold 12 Bananas.
You can think of this as arrayToString()
array = array * " "
E.g.,
myArray = ["One.","_1_?! Really?!","Yes!"]
=> "One.","_1_?! Really?!","Yes!"
myArray = myArray * " "
=> "One. _1_?! Really?! Yes."