Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to count a string elements' occurrence in another string in ruby?

Tags:

string

ruby

How can I check how many times a phrase occurs in a string?

For example, let's say the phrase is donut

str1 = "I love donuts!"
#=> returns 1 because "donuts" is found once.
str2 = "Squirrels do love nuts" 
#=> also returns 1 because of 'do' and 'nuts' make up donut
str3 = "donuts do stun me" 
#=> returns 2 because 'donuts' and 'do stun' has all elements to make 'donuts'

I checked this SO that suggests using include, but it only works if donuts is spelled in order.

I came up with this, but it doesn't stop spelling after all elements of "donuts"is spelled. i.e. "I love donuts" #=> ["o", "d", "o", "n", "u", "t", "s"]

def word(arr)
  acceptable_word = "donuts".chars
  arr.chars.select { |name| acceptable_word.include? name.downcase }
end

How can I check how many occurrences of donuts are there in a given string? No edge cases. Input will always be String, no nil. If it contains elements of donut only it should not count as 1 occurrence; it needs to contain donuts, doesn't have to be in order.

like image 462
Iggy Avatar asked Oct 22 '25 04:10

Iggy


1 Answers

Code

def count_em(str, target)
  target.chars.uniq.map { |c| str.count(c)/target.count(c) }.min
end

Examples

count_em "I love donuts!", "donuts"                      #=> 1
count_em "Squirrels do love nuts", "donuts"              #=> 1
count_em "donuts do stun me", "donuts"                   #=> 2
count_em "donuts and nuts sound too delicious", "donuts" #=> 3
count_em "cats have nine lives", "donuts"                #=> 0
count_em "feeding force scout", "coffee"                 #=> 1
count_em "feeding or scout", "coffee"                    #=> 0

str = ("free mocha".chars*4).shuffle.join
  # => "hhrefemcfeaheomeccrmcre eef oa ofrmoaha "
count_em str, "free mocha"
  #=> 4

Explanation

For

str = "feeding force scout"
target = "coffee"

a = target.chars
  #=> ["c", "o", "f", "f", "e", "e"] 
b = a.uniq
  #=> ["c", "o", "f", "e"] 
c = b.map { |c| str.count(c)/target.count(c) }
  #=> [2, 2, 1, 1] 
c.min
  #=> 1 

In calculating c, consider the first element of b passed to the block and assigned to the block variable c.

c = "c"

Then the block calculation is

d = str.count(c)
  #=> 2 
e = target.count(c)
  #=> 1
d/e
  #=> 2

This indicates that str contains enough "c"'s to match "coffee" twice.

The remaining calculations to obtain c are similar.

Addendum

If the characters of str matching characters target must be in the same order as those of target, the following regex could be used.

target = "coffee"

r = /#{ target.chars.join(".*?") }/i
  #=> /c.*?o.*?f.*?f.*?e.*?e/i

matches = "xcorr fzefe yecaof tfe erg eeffoc".scan(r)
  #=> ["corr fzefe ye", "caof tfe e"]
matches.size
  #=> 2

"feeding force scout".scan(r).size
  #=> 0 

The questions marks in the regex are needed to make the searches non-greedy.

like image 183
Cary Swoveland Avatar answered Oct 24 '25 18:10

Cary Swoveland



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!