How does count method works in Ruby? - ruby

I have a hard time understanding the following code segment from the Ruby docs:
a = "hello world"
a.count "lo" #=> 5
a.count "lo", "o" #=> 2
a.count "hello", "^l" #=> 4
a.count "ej-m" #=> 4
"hello^world".count "\\^aeiou" #=> 4
"hello-world".count "a\\-eo" #=> 4
especially this code a.count "ej-m". Can anyone please explain how it works?

Just imagine the "pattern" strings as wrapped by [ and ] from regex syntax, that are matched against each character.
So, if we break a = "hello world" into characters:
[1] pry(main)> a = "hello world"
=> "hello world"
[2] pry(main)> a.split('')
=> ["h", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d"]
And convert "ej-m" to regex wrapped with [ and ] we get /[ej-m]/ - which means either 'e' or any character from 'j' to 'm'(including both):
[3] pry(main)> a.split('').select{|c| c=~ /[ej-m]/}
=> ["e", "l", "l", "l"]
We got 4 matches - which is also the result you get. Essensially a.count "ej-m" is equivalent to:
[4] pry(main)> a.split('').count{|c| c=~ /[ej-m]/}
=> 4
Multiple arguments to the method are just and between the matches:
[5] pry(main)> a.split('').count{|c| c =~ /[hello]/ and c =~ /[^l]/}
=> 4

The sequence c1-c2 means all characters between c1 and c2.
So you are providing a range, basically it counts which characters are in that range (>= c1 && <= c2)
i.e:
a = "hello world"
a.count "a-z"
=> 10
a.count "o-w"
=> 4 #(o, o, r, w)
a.count "e-l"
=> 5 #(h, e, l, l, l)

We find that
"hello world".count("ej-m") #=> 4 (_ell_____l_)
Examine the doc for String#count carefully.
Here is how count might be implemented to deal with patterns that closely resemble the pattern "ej-m".
def count_letters(str, pattern)
idx = pattern[1..-2].index('-')
if idx
idx += 1
before, after = pattern[idx-1], pattern[idx+1]
pattern[idx-1..idx+1] = (before..after).to_a.join
end
str.each_char.sum { |c| pattern.include?(c) ? 1 : 0 }
end
count_letters(str, pattern) #=> 4 (_ell_____l_)
However, String#count must also do the following.
Allow for multiple ranges in the pattern
"hello1world".count("e0-9j-mv-x") #=> 6 (_ell__1_w__l_)
If the pattern begins with the character '^'count the number of characters that do not match the remainder of the pattern
"hello world".count("^ej-m") #=> 7 (h___o*wor_d) * = space to count
"hello^world".count("e^j-m") #=> 5 (_ell_^___l_)
"hello world".count("\^ej-m") #=> 7 (h___o*wor_d) * = space to count
Note that escaping '^' at the beginning of the string makes no difference.
Match a hyphen
"hello-world".count("ej-m-") #=> 5 (_ell_-___l_)
"hello-world".count("-ej-m") #=> 5 (_ell_-___l_)
"hello-world".count("ej\-m") #=> 4 (_ell____l_)
Note that escaping a hyphen that is not the first or last character of the pattern makes no difference.
Match a backslash
'hello\world'.count("ej-m\\") #=> 5 (_ell_\___l_)
'hello\world'.count("\\ej-m") #=> 4 (_ell____l_)
Note that a backslash at the beginning of a string is disregarded.
Some of the above results (Ruby v2.4) do not seem to be consistent with the documentation.

Related

Print elements of array of arrays of different size in same line in Ruby

Maybe someone could help me with this. I have an array of arrays. The internal arrays have different sizes (from 2 to 4 elements).
letters = [["A", "B"],["C", "D", "F", "G"],["H", "I", "J" ]]
I'm trying to print in a same line each array havins as first column element[0] and element[1] joined, as 2nd column element[0], element[1], element[2] joined as 3rd column element[0], element[1], element[3] joined. Elements 2 and 3 not always exist.
The output I'm trying to get is like this:
AB
CD CDF CDG
HI HIJ
I'm doing in this way but I'm getting this error.
letters.map{|x| puts x[0]+x[1] + "," + x[0]+x[1]+x[2] + "," + x[0]+x[1]+x[3]}
TypeError: no implicit conversion of nil into String
from (irb):1915:in "+"
from (irb):1915:in "block in irb_binding"
from (irb):1915:in "map"
from (irb):1915
from /usr/bin/irb:11:in "<main>"
letters.each do |a,b,*rest|
puts rest.each_with_object([a+b]) { |s,arr| arr << arr.first + s }.join(' ')
end
prints
AB
CD CDF CDG
HI HIJ
The steps are as follows.
Suppose
letters = [["C", "D", "F", "G"],["H", "I", "J" ]]
Then
enum0 = letters.each
#=> #<Enumerator: [["C", "D", "F", "G"], ["H", "I", "J"]]:each>
The first element of this enumerator is generated and passed to the block, and the three block variables are assigned values.
a, b, *rest = enum0.next
#=> ["C", "D", "F", "G"]
a
#=> "C"
b
#=> "D"
rest
#=> ["F", "G"]
Next, we obtain
enum1 = rest.each_with_object([a+b])
#=> rest.each_with_object(["CD"])
#=> #<Enumerator: ["F", "G"]:each_with_object(["CD"])>
The first element of this enumerator is generated and passed to the block, and the block variables are assigned values.
s, arr = enum1.next
#=> ["F", ["CD"]]
s
#=> "F"
arr
#=> ["CD"]
The block calculation is now performed.
arr << arr.first + s
#=> arr << "CD" + "F"
#=> ["CD", "CDF"]
The second and last element of enum1 is generated and passed to the block, and block variables are assigned values and the block is computed.
s, arr = enum1.next
#=> ["G", ["CD", "CDF"]]
arr << arr.first + s
#=> ["CD", "CDF", "CDG"]
When an attempt to generate another element from enum1 we obtain
enum1.next
#StopIteration: iteration reached an end
Ruby handles the exception by breaking out of the block and returning arr. The elements of arr are then joined:
arr.join(' ')
#=> "CD CDF CDG"
and printed.
The second and last element of enum0 is now generated, passed to the block, and the three block variables are assigned values.
a, b, *rest = enum0.next
#=> ["H", "I", "J"]
a
#=> "H"
b
#=> "I"
rest
#=> ["J"]
The remaining calculations are similar.
Some readers may be unfamiliar with the method Enumerable#each_with_object, which is widely used. Read the doc, but note that here it yields the same result as the code written as follows.
letters.each do |a,b,*rest|
arr = [a+b]
rest.each { |s| arr << arr.first + s }
puts arr.join(' ')
end
By using each_with_object we avoid the need for the statement arr = [a+b] and the statement puts arr.join(' '). The functions of those two statements are of course there in the line using each_with_object, but most Ruby users prefer the flow when when chaining each_with_object to join(' '). One other difference is that the value of arr is confined to each_with_object's block, which is good programming practice.
Looks like you want to join the first two letters, then take the cartesian product with the remaining.
letters.each do |arr|
first = arr.take(2).join
rest = arr.drop(2)
puts [first, [first].product(rest).map(&:join)].join(" ")
end
This provides the exact output you specified.
Just out of curiosity, Enumerable#map-only solution.
letters = [["A", "B"],["C", "D", "F", "G"],["H", "I", "J" ]]
letters.map do |f, s, *rest|
rest.unshift(nil).map { |l| [f, s, l].join }.join(' ')
end.each(&method(:puts))
#⇒ AB
# CD CDF CDG
# HI HIJ

How to count consecutive consonants at beginning of a string in Ruby?

I'm still coming to terms with Regex and want to formulate an expression that will let me count the number of successive consonants at the beginning of a string. E.g. 'Cherry' will return 2, 'hello' 1, 'schlepp' 4 and so on. Since the number isn't predetermined (although English probably has some upper limit on initial consonants!) I'd need some flexible expression, but I'm a bit stuck about how to write it. Any pointers would be welcome!
This would work:
'Cherry'[/\A[bcdfghjklmnpqrstvwxyz]*/i].length #=> 2
The regex matches zero or more consonants at the beginning of the string. String#[] returns the matching part and length determines its length.
You can also express the consonants character class more succinct by intersecting [a-z] and [^aeiou] via &&:
'Cherry'[/\A[a-z&&[^aeiou]]*/i].length #=> 2
Something along this line would work:
>> 'Cherry'.downcase.split(/([aeiou].*)/).first.length
# => 2
>> 'hello'.downcase.split(/([aeiou].*)/).first.length
# => 1
>> 'schlepp'.downcase.split(/([aeiou].*)/).first.length
# => 4
Another way is to replace from the first vowel until end of string by nothing then take the length:
'Cherry'.gsub(/[aeiou].*$/,"").length
It is not necessary to use a regular expression.
CONSONANTS = (('a'..'z').to_a - 'aeiou'.chars).join
#=> "bcdfghjklmnpqrstvwxyz"
def consecutive_constants(str)
e, a = str.each_char.chunk { |c| CONSONANTS.include?(c.downcase) }.first
e ? a.size : 0
end
consecutive_constants("THIS is nuts") #=> 2
consecutive_constants("Is this ever cool?") #=> 0
consecutive_constants("1. this is wrong") #=> 0
Note
enum = "THIS is nuts".each_char.chunk { |c| CONSONANTS.include?(c.downcase) }
#=> #<Enumerator: #<Enumerator::Generator:0x000000010e1a40>:each>
We can see the elements that will be generated by this enumerator by applying Enumerable#entries (or Enumerable#to_a):
enum.entries
#=> [[true, ["T", "H"]], [false, ["I"]], [true, ["S"]], [false, [" ", "i"]],
# [true, ["s"]], [false, [" "]], [true, ["n"]], [false, ["u"]], [true, ["t", "s"]]]
Continuing,
e, a = enum.first
#=> [true, ["T", "H"]]
e #=> true
a #=> ["T", "H"]
a.size
#=> 2

How can I take out digits in each string?

I'm trying to take out a number from each string and add 4 to each of them, but the compiler keeps telling me that:
undefined method `captures' for nil:NilClass (NoMethodError)
There is output 8 with the error message if I don't add match2 and int2 codes.
expecting output:
8
23
9
14
How can I fix this?
[
"I have 4 cucumbers",
"I've been given 19 radishes",
"I have 5 carrots in my hand",
"I gots 10 zucchini!"
].each do |string|
match = /^I have (\d) ([a-z]*)$/.match(string)
match2 = /I've been given (\d+) ([a-z]*)$/.match(string)
int = match.captures[0].to_i
int += 4
int2 = match2.captures[0].to_i
int2 += 4
puts int
puts int2
end
You can try this
array = []
[
"I have 4 cucumbers",
"I've been given 19 radishes",
"I have 5 carrots in my hand",
"I gots 10 zucchini!"
].each do |string|
array.push(string.scan(/\d+/))
end
new_array = array.flatten.map {|i| i.to_i}
#=> [4, 19, 5, 10]
new_array.map {|i| i.to_i + 4} #if you want to add 4 to each element
=> [8, 23, 9, 14]
It's not entirely clear what your expected output should be.
Meditate on this:
ary = ["a 4 b", "a 19 b"]
ary.each do |string|
string.gsub!(/\b\d+\b/) { |num| (num.to_i + 4).to_s }
end
ary # => ["a 8 b", "a 23 b"]
gsub! changes a string in place, whereas gsub returns the changed string. The difference would be:
ary = ["a 4 b", "a 19 b"]
new_ary = ary.map do |string|
string.gsub(/\b\d+\b/) { |num| (num.to_i + 4).to_s }
end
ary # => ["a 4 b", "a 19 b"]
new_ary # => ["a 8 b", "a 23 b"]
Notice that each became map, because we want to return an array of changed values, and gsub! because gsub.
It's important to use \b when searching for numbers in strings, otherwise you can run into problems with false-positive hits affecting digits inside "words" like "foo1".
If you want to return only the values after they've been incremented:
ary = ["a 0 b", "a 0 b 1"]
ary.map{ |a| a.scan(/\b\d+/).map{ |i| i.to_i + 4 }} # => [[4], [4, 5]]
Which, broken down, is doing this:
ary
.map{ |a|
a # => "a 0 b", "a 0 b 1"
.scan(/\b\d+/) # => ["0"], ["0", "1"]
.map{ |i| i.to_i + 4 } # => [4], [4, 5]
} # => [[4], [4, 5]]
In your code you're doing:
match = /^I have (\d) ([a-z]*)$/.match(string)
match2 = /I've been given (\d+) ([a-z]*)$/.match(string)
If you're processing freeform text you can't create a match for every possible incoming string; There are infinite possibilities. Even if you're in charge of the string creation, you shouldn't need to match entire strings, only specific parts. The more you try to match, the more likely it is the code will fail.

Replacing every letter in a given string with the letter following in the alphabet [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I have to replace every letter in a string with the letter following it in the alphabet (i.e. c becomes d, z becomes a), capitalize every vowel (a, e, i, o, u), and return the modified string. I'm trying to find solutions without calling any functions like sort or find.
I have this:
def LetterChanges(str)
Changed_Letter = ""
alphabet = [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z]
for i in 0..str.length do
if str[i] ==
str[i] = alphabet[i] + 1
return str
end
but I am lost. Any help is appreciated.
You are being asked to "map" each letter of the alphabet to another letter, so you will want to use the method Enumerable#map.
VOWELS = "aeiou"
letters = ('a'..'z').to_a
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
letters.map do |c|
<code referencing c>
end
#=> ['b', 'c', 'd', 'E', 'f',..., 'z', 'A]
Now let's fill in the code, using the methods:
String#succ, which, given a character, returns the character with the next-higher ASCII value. For example, "b".ord #=> 98, so "b".succ #=> "c", since "c".ord #=> 99. Since "z".succ #=> 'aa', we need to treat "z" as a special case. String#succ is the same as String#next.
String#include?, which, given a string, returns true or false depending on whether include?'s argument (a string) is included in the receiver. For example, "cat".include?("at") #=> true; "cat".include?("a") #=> true; "cat".include?("z") #=> false. Note that VOWELS, since it begins with a capital letter, is a constant.
String#upcase, which converts all lowercase letters in a given string to upper case (and leaves all other characters unchanged).
letters.map do |c|
if c == 'z'
'A'
else
s = c.succ
if VOWELS.include?(s)
s.upcase
else
s
end
end
end
#=> ["b", "c", "d", "E", "f", "g", "h", "I", "j", "k", "l", "m", "n",
# "O", "p", "q", "r", "s", "t", "U", "v", "w", "x", "y", "z", "A"]
You could instead write this using a case statement and Ruby's ternary operator:
letters.map do |c|
case c
when 'z'
'A'
else
s = c.succ
VOWELS.include?(s) ? s.upcase : s
end
end
or you could make use of the methods String#ord and Integer#chr:
letters.map do |c|
s = ('a'.ord + ((c.ord-'a'.ord+1) % 26)).chr
VOWELS.include?(s) ? s.upcase : s
end
end
If, for example, c = 'r'
('a'.ord + ((c.ord-'a'.ord+1) % 26).chr
#=> (97 + ((114-97+1) % 26).chr
#=> (97 + 18 % 26).chr
#=> (97 + 18).chr
#=> 115.chr
#=> 's'
If, however, c = 'z'
('a'.ord + ((c.ord-'a'.ord+1) % 26).chr
#=> (97 + ((122-97+1) % 26).chr
#=> (97 + 26 % 26).chr
#=> (97 + 0).chr
#=> 97.chr
#=> 'a'
One more way. (You can figure out why this works.)
letters.map do |c|
s = c.succ[0]
VOWELS.include?(s) ? s.upcase : s
end
You might instead wish to create a hash.
letter_mapping = {}
letters.each do |c|
s = c.succ[0]
letter_mapping[c] = VOWELS.include?(s) ? s.upcase : s
end
letter_mapping
#=> { "a"=>"b", "b"=>"c", "c"=>"d", "d"=>"E", "e"=>"f", "f"=>"g", "g"=>"h",
# "h"=>"I", "i"=>"j", "j"=>"k", "k"=>"l", "l"=>"m", "m"=>"n", "n"=>"O",
# "o"=>"p", "p"=>"q", "q"=>"r", "r"=>"s", "s"=>"t", "t"=>"U", "u"=>"v",
# "v"=>"w", "w"=>"x", "x"=>"y", "y"=>"z", "z"=>"A"}
so, for example, letter_mapping['r'] #=> "s".
In time you will find that the Ruby way of writing this is:
letters.each_with_object({}) do |c, letter_mapping|
s = c.succ[0]
letter_mapping[c] = VOWELS.include?(s) ? s.upcase : s
end
#=> { "a"=>"b", ... "z"=>"A"}
One last thing. Enumerable#map is an instance method for every class that includes the Enumerable module. One such class is Array:
Array.included_modules
#=> [Enumerable, Kernel]
Array.instance_methods.include?(:map)
#=> true
Array has use of all of the module Enumerable's methods, just as though they had been defined in Array. That's why map works when the receiver is an array.
Another class that includes Enumerable is Range:
Range.included_modules
#=> [Enumerable, Kernel]
Range.instance_methods.include?(:map)
#=> true
Therefore, instead of writing:
letters = ('a'..'z').to_a
we could (should) write:
letters = ('a'..'z')
and all the above code would work just fine.
You can try this, it will replace a letter with its following letter also it will capitalize vowels.
def letter_changes(str)
alphabets = ('a'..'z').to_a
vowels = ["a","e","i","o","u"]
for i in 0..(str.length-1) do
index = (alphabets.index(str[i]) == (alphabets.size - 1) ? 0 : (alphabets.index(str[i]) + 1))
str[i] = alphabets[index]
str[i] = str[i].upcase if vowels.include?(str[i])
end
puts str
end
## call function
letter_changes("cadcarz")
## OUTPUT
dbEdbsA

String#count options

From the documentation for String#count I understand the first example, but I do not understand the rest of the examples:
a = "hello world"
a.count "lo" #=> 5
a.count "lo", "o" #=> 2
a.count "hello", "^l" #=> 4
a.count "ej-m" #=> 4
Any explanation will be helpful.
This is one of the dorkiest ruby methods, and pretty lousy documentation to boot. Threw me for a loop. I ended up looking at it because it looked like it should give me the count of occurrences of a given string. Nope. Not remotely close. But here is how I ended up counting string occurrences:
s="this is a string with is thrice"
s.scan(/is/).count # => 3
Makes me wonder why someone asked for this method, and why the documentation is so lousy. Almost like the person documenting the code really did not have a clue as to the human-understandable "business" reason for asking for this feature.
count([other_str]+) → fixnum
Each _other_str_ parameter defines a set of characters to count. The
intersection of these sets defines the characters to count in str. Any
_other_str_ that starts with a caret (^) is negated. The sequence c1–c2
means all characters between c1 and c2.
If you pass more than 1 parameter to count, it will use the intersection of those strings and will use that as the search target:
a = "hello world"
a.count "lo" #=> finds 5 instances of either "l" or "o"
a.count "lo", "o" #=> the intersection of "lo" and "o" is "o", so it finds 2 instances
a.count "hello", "^l" #=> the intersection of "hello" and "everything that is not "l" finds 4 instances of either "h", "e" or "o"
a.count "ej-m" #=> finds 4 instances of "e", "j", "k", "l" or "m" (the "j-m" part)
Let's break these down
a = "hello world"
to count the number of occurrences of the letters l and o
a.count "lo" #=> 5
to find the intersect of lo and o (which is counting the number of occurrences of l and o and taking only the count of o from the occurrences):
a.count "lo", "o" #=> 2
to count the number of occurrences of h, e, l, l and o, then intersect with any that are not l (which produces the same outcome to finding occurrences of h, e and o)
a.count "hello", "^l" #=> 4
to count the number of occurrences of e and any letter between j and m (j, k, l and m):
a.count "ej-m" #=> 4
Each argument defines a set of characters. The intersection of those sets determines the overall set that count uses to compute a tally.
a = "hello world"
a.count "lo" # l o => 5
a.count "lo", "o" # o => 2
And ^ can be used for negation (all letters in hello, except l)
a.count "hello", "^l" # h e o => 4
Ranges can be defined with -:
a.count "ej-m" # e j k l m => 4
Using gsub in string
a = "hello world hello hello hello hello world world world"
2.1.5 :195 > a.gsub('hello').count
=> 5
I'll take a stab:
Second example: using the wording "The intersection of these sets defines the characters to count in str" the parameters are "lo" and "o". The intersection of these is just "o" of which there are 2 in the string being counted on. Hence the return value of 2.
Third example: This one seems to be saying: "Any of the characters in 'hello' but not the character 'l'". Getting this from the line "Any other_str that starts with a caret (^) is negated". So, you can count the set of letters contained in the string "hello" which are found in "hello world" (i.e., h,e,l,l,o,o,l) but then comparing the intersection with the set of "^l" (i.e. h,e,o,w,o,r,d) you are left with 4 (i.e. h,e,o,o).
Fourth example: This one basically says "count all the 'e' characters and any character between 'j' and 'm'. There is one 'e' and 3 characters contained between 'j' and 'm' and all 3 happen to be the letter 'l' which leaves us with an answer of 4 again.

Resources