Compressing a given String problem in Ruby - ruby

I have been trying to solve this problem for a while now, im supposed to take a given string like "aaabbc" and compress it into a new string that states multiples of a letter in a row in place. So it would output "3a2bc"
So far i managed to print it out except it counts all instances of a letter and im not sure how to get rid of the current repeats:
def compress_str(str)
new_str = []
word = str.split("")
word.each do |char|
count = 0
word.each do |ele|
if ele == char
count += 1
end
end
if count > 1
new_str << count
new_str << char
else
new_str << char
end
end
return new_str.join("")
Example output:
Any suggestions on how I'm supposed to get rid of them?

Using Enumerable#chunk might be a good fit for your needs.
uncompressed = %w(aaabbcddaaaaa aaabb 111ddttttabaaacc)
uncompressed.each do |str|
puts str.chars.chunk{|e| e}.map {|e| "#{e[1].length}#{e[0]}"}.join
end
>>> 3a2b1c2d5a
>>> 3a2b
>>> 312d4t1a1b3a2c
Sure, you can add another check inside map block, so omit 1 before a single element and print as is.

You could use String#chars (1), so Enumerable#chunk_while (2), then Enumerable#flat_map (3) into the desired format and finally Array#join:
str = "aaabbcaa"
str.chars.chunk_while { |x, y| x == y }.flat_map { |e| [(e.size unless e.size == 1), e.first] }.join
#=> "3a2bc2a"
Step by step
# (1)
str.chars#.to_a
#=> ["a", "a", "a", "b", "b", "c", "a", "a"]
so
# (2)
str.chars.chunk_while { |x, y| x == y }#.to_a
#=> [["a", "a", "a"], ["b", "b"], ["c"], ["a", "a"]]
then
# (3)
str.chars.chunk_while { |x, y| x == y }.flat_map { |e| [(e.size unless e.size == 1),e.first] }
#=> [3, "a", 2, "b", nil, "c", 2, "a"]

String#scan can also be handy here.
uncompressed = %w(aaabbcddaaaaa aaabb 111ddttttabaaacc)
uncompressed.map { |w| w.scan(/(.)(\1*)/).map(&:join) }
#⇒ [["aaa", "bb", "c", "dd", "aaaaa"],
# ["aaa", "bb"],
# ["111", "dd", "tttt", "a", "b", "aaa", "cc"]]
And to get the desired outcome.
uncompressed.map do |w|
w.scan(/(.)(\1*)/).map(&:join).map do |l|
"#{l.length}#{l[0]}"
end.join
end
#⇒ ["3a2b1c2d5a", "3a2b", "312d4t1a1b3a2c"]

Related

How to break down create a substring method

Can someone help me break down this problem? I have the answer but I'm not really sure how my teacher got to it.
class String
# Write a method, String#substrings, that takes in a optional length argument
# The method should return an array of the substrings that have the given length.
# If no length is given, return all substrings.
#
# Examples:
#
# "cats".substrings # => ["c", "ca", "cat", "cats", "a", "at", "ats", "t", "ts", "s"]
# "cats".substrings(2) # => ["ca", "at", "ts"]
def substrings(length = nil)
subs = []
(0...self.length).each do |start_idx|
(start_idx...self.length) do |end_idx|
sub = self[start_idx..end_idx]
subs << sub
end
end
if length.nil?
subs
else
subs.select { |str| str.length == length}
end
end
end
The start_idx and the end_idx are really confusing, if the start_idx is "ca" for example is the end_idx "ca" as well? Please help..
So think of start_idx and end_idx as a constantly changing variable.
def substrings(length = nil)
subs = []
# in the case of 'cats' the length is 4
# so this is making an array UP TO BUT NOT INCLUDING 4
# [0,1,2,3].each do ...
# let's take 0
(0...self.length).each do |start_idx|
# start_idx = 0 the first time through this each
# Array from start_idx UP TO BUT NOT INCLUDING 4
# so the FIRST time through this is 0, second time through is 1, ...
#[0,1,2,3].each do ...
(start_idx...self.length) do |end_idx|
# end_idx = 0
# slice of the string from the 0th to the 0th value (first letter)
sub = self[start_idx..end_idx]
subs << sub
end
end
if length.nil?
subs
else
subs.select { |str| str.length == length}
end
end
So think of this as a bunch of nested loops using numbers that are reassigned during each pass of the loop.
Does that help?
The following would be a more Ruby-like way of writing that.
class String
def all_substrings
(1..size).flat_map { |n| all_substrings_by_length(n) }
end
def all_substrings_by_length(length)
each_char.each_cons(length).with_object([]) { |a,arr| arr << a.join }
end
end
"cats".all_substrings_by_length(1)
#=> ["c", "a", "t", "s"]
"cats".all_substrings_by_length(2)
#=> ["ca", "at", "ts"]
"cats".all_substrings_by_length(3)
#=> ["cat", "ats"]
"cats".all_substrings
#=> ["c", "a", "t", "s", "ca", "at", "ts", "cat", "ats", "cats"]
Note that 1..size is the same as 1..self.size, all_substrings_by_length(n) is the same as self.all_substrings_by_length(n) and each_char is the same as self.each_char, as self is implied when a method has no explicit receiver.
See Enumerable#flat_map, String#each_char, Enumerable#each_cons and Emumerator#with_object.
Let's break down
each_char.each_cons(length).with_object([]) { |a,arr| arr << a.join }
when length = 2 and self = "cats".
length = 2
e0 = "cats".each_char
#=> #<Enumerator: "cats":each_char>
We can see the elements that will be generated by this enumerator by converting it to an array.
e0.to_a
#=> ["c", "a", "t", "s"]
Continuing,
e1 = e0.each_cons(length)
#=> #<Enumerator: #<Enumerator: "cats":each_char>:each_cons(2)>
e1.to_a
#=> [["c", "a"], ["a", "t"], ["t", "s"]]
e2 = e1.with_object([])
#=> #<Enumerator: #<Enumerator: #<Enumerator:
# "cats":each_char>:each_cons(2)>:with_object([])>
e2.to_a
#=> [[["c", "a"], []], [["a", "t"], []], [["t", "s"], []]]
By examining the return values for the creation of e1 and e2 one can see that they could be thought of as compound enumerators, though Ruby has no formal concept of such. Also, as will be seen, the empty arrays in the last return value will be built up as the calculations progress.
Lastly,
e2.each { |a,arr| arr << a.join }
#=> ["ca", "at", "ts"]
which is our desired result. Now examine this last calculation in more detail. each directs e2 to generate an element and then sets the block variables equal to it.
First, observe the following.
e2
#=> #<Enumerator: #<Enumerator: #<Enumerator: "cats":each_char>:
# each_cons(2)>:=with_object(["ca", "at", "ts"])>
This shows us that we need to return e2 to its initial state in order to reproduce the calculations.
e2 = e1.with_object([])
#=> #<Enumerator: #<Enumerator: #<Enumerator:
# "cats":each_char>:each_cons(2)>:with_object([])>
Then:
a, arr = e2.next
#=> [["c", "a"], []]
Array decomposition breaks this array into parts for a and arr:
a #=> ["c", "a"]
arr
#=> []
We now perform the block calculation:
arr << a.join
#=> ["ca"]
each then commands e2 to generate the next element, assigns values to the block variables and performs the block calculation.
a, arr = e2.next
#=> [["a", "t"], ["ca"]]
a #=> ["a", "t"]
arr
#=> ["ca"]
arr << a.join
#=> ["ca", "at"]
This is repeated once more.
a, arr = e2.next
#=> [["t", "s"], ["ca", "at"]]
arr << a.join
#=> ["ca", "at", "ts"]
Lastly, the exception
a, arr = e2.next
#=> StopIteration (iteration reached an end)
causes each to return
arr
#=> ["ca", "at", "ts"]
from the block, which, being the last calculation, is returned by the method.

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

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

Consecutive letter frequency

I am trying to write code to determine consecutive frequency of letters within a string.
For example:
"aabbcbb" => ["a",2],["b",2],["c", 1], ["b", 2]
My code gives me the first letter frequency but doesn't move on to the next.
def encrypt(str)
array = []
count = 0
str.each_char do |letter|
if array.empty?
array << letter
count += 1
elsif array.last == letter
count += 1
else
return [array, count]
array = []
end
end
end
p "aabbcbb".chars.chunk{|c| c}.map{|c, a| [c, a.size]}
# => [["a", 2], ["b", 2], ["c", 1], ["b", 2]]
"aabbcbb".chars.slice_when(&:!=).map{|a| [a.first, a.length]}
# => [["a", 2], ["b", 2], ["c", 1], ["b", 2]]
There's a simple regular expression-based solution involving back-references:
"aabbbcbb".scan(/((.)\2*)/).map { |m,c| [c, m.length] }
# => [["a", 2], ["b", 3], ["c", 1], ["b", 2]]
But I would prefer the chunk method for clarity (and almost certainly efficiency).
Actually out of curiosity, I wrote a quick benchmark and scan is a little more than four times faster than chunk.map, but I'd still use chunk.map for clarity unless you're actually doing this hundreds of thousands of times:
require 'benchmark'
N = 10000
data = ('a'..'z').map { |c| c * 10 }.join("")
Benchmark.bm do |bm|
bm.report do
N.times { data.chars.chunk{ |c| c }.map { |c, a| [c, a.size] } }
end
bm.report do
N.times { data.scan(/((.)\2*)/).map { |m,c| [c, m.size] } }
end
end
user system total real
0.800000 0.010000 0.810000 ( 0.803824)
0.190000 0.000000 0.190000 ( 0.192915)
You need to build up an array of results, rather than simply stopping at the first one:
def consecutive_frequencies(str)
str.each_char.reduce([]) do |frequencies_arr, char|
if frequencies_arr.last && frequencies_arr.last[0] == char
frequencies_arr.last[1] += 1
else
frequencies_arr << [char, 1]
end
frequencies_arr
end
end
#steenslag gave the answer I would have given, so I'll try something different.
"aabbcbb".each_char.with_object([]) { |c,a| (a.any? && c == a.last.first) ?
a.last[-1] += 1 : a << [c, 1] }
#=> [["a", 2], ["b", 2], ["c", 1], ["b", 2]]
def encrypt(str)
count = 0
array = []
str.chars do |letter|
if array.empty?
array << letter
count += 1
elsif array.last == letter
count += 1
else
puts "[#{array}, #{count}]"
array.clear
count = 0
array << letter
count += 1
end
end
puts "[#{array}, #{count}]"
end
There are several errors with your implementation, I would try with a hash (rather than an array) and use something like this:
def encrypt(str)
count = 0
hash = {}
str.each_char do |letter|
if hash.key?(letter)
hash[letter] += 1
else
hash[letter] = 1
end
end
return hash
end
puts encrypt("aabbcbb")

Ruby: How to create a letter counting function

I wouldn't have asked for help without first spending a few hours trying to figure out my error but I'm at a wall. So there is my attempt but I'm getting false when I try to pass the argument though and I'm not sure why. I know there are other ways to solve this problem that are a little shorter but I'm more interested in trying to get my code to work. Any help is much appreciated.
Write a method that takes in a string. Your method should return the most common letter in the array, and a count of how many times it appears.
def most_common_letter(string)
idx1 = 0
idx2 = 0
counter1 = 0
counter2 = 0
while idx1 < string.length
idx2 = 0
while idx2 < string.length
if string[idx1] == string[idx2]
counter1 += 1
end
idx2 += 1
end
if counter1 > counter2
counter2 = counter1
counter1 = 0
var = [string[idx1], counter2]
end
idx1 += 1
end
return var
end
puts("most_common_letter(\"abca\") == [\"a\", 2]: #{most_common_letter("abca") == ["a", 2]}")
puts("most_common_letter(\"abbab\") == [\"b\", 3]: #{most_common_letter("abbab") == ["b", 3]}")
I didn't rewrite your code because I think it's important to point out what is wrong with the existing code that you wrote (especially since you're familiar with it). That said, there are much more 'ruby-like' ways to go about this.
The issue
counter1 is only being reset if you've found a 'new highest'. You need to reset it regardless of whether or not a new highest number has been found:
def most_common_letter(string)
idx1 = 0
idx2 = 0
counter1 = 0
counter2 = 0
while idx1 < string.length
idx2 = 0
while idx2 < string.length
if string[idx1] == string[idx2]
counter1 += 1
end
idx2 += 1
end
if counter1 > counter2
counter2 = counter1
# counter1 = 0 THIS IS THE ISSUE
var = [string[idx1], counter2]
end
counter1 = 0 # this is what needs to be reset each time
idx1 += 1
end
return var
end
Here's what the output is:
stackoverflow master % ruby letter-count.rb
most_common_letter("abca") == ["a", 2]: true
most_common_letter("abbab") == ["b", 3]: true
I think you're aware there are way better ways to do this but frankly the best way to debug this is with a piece of paper. "Ok counter1 is now 1, indx2 is back to zero", etc. That will help you keep track.
Another bit of advice, counter1 and counter2 are not very good variable names. I didn't realize what you were using them for initially and that should never be the case, it should be named something like current_count highest_known_count or something like that.
Your question has been answered and #theTinMan has suggested a more Ruby-like way of doing what you want to do. There are many other ways of doing this and you might find it useful to consider a couple more.
Let's use the string:
string = "Three blind mice. Oh! See how they run."
First, you need to answer a couple of questions:
do you want the frequency of letters or characters?
do you want the frequency of lowercase and uppercase letters combined?
I assume you want the frequency of letters only, independent of case.
#1 Count each unique letter
We can deal with the case issue by converting all the letters to lower or upper case, using the method String#upcase or String#downcase:
s1 = string.downcase
#=> "three blind mice. oh! see how they run."
Next we need to get rid of all the characters that are not letters. For that, we can use String#delete1:
s2 = s1.delete('^a-z')
#=> "threeblindmiceohseehowtheyrun"
Now we are ready to convert the string s2 to an an array of individual characters2:
arr = s2.chars
#=> ["t", "h", "r", "e", "e", "b", "l", "i", "n", "d",
# "m", "i", "c", "e", "o", "h", "s", "e", "e", "h",
# "o", "w", "t", "h", "e", "y", "r", "u", "n"]
We can combine these first three steps as follows:
arr = string.downcase.gsub(/[^a-z]/, '').chars
First obtain all the distinct letters present, using Array.uniq.
arr1 = arr.uniq
#=> ["t", "h", "r", "e", "b", "l", "i", "n",
# "d", "m", "c", "o", "s", "w", "y", "u"]
Now convert each of these characters to a two-character array consisting of the letter and its count in arr. Whenever you need convert elements of a collection to something else, think Enumerable#map (a.k.a. collect). The counting is done with Array#count. We have:
arr2 = arr1.map { |c| [c, arr.count(c)] }
#=> [["t", 2], ["h", 4], ["r", 2], ["e", 6], ["b", 1], ["l", 1],
# ["i", 2], ["n", 2], ["d", 1], ["m", 1], ["c", 1], ["o", 2],
# ["s", 1], ["w", 1], ["y", 1], ["u", 1]]
Lastly, we use Enumerable#max_by to extract the element of arr2 with the largest count3:
arr2.max_by(&:last)
#=> ["e", 6]
We can combine the calculation of arr1 and arr2:
arr.uniq.map { |c| [c, arr.count(c)] }.max_by(&:last)
and further replace arr with that obtained earlier:
string.downcase.gsub(/[^a-z]/, '').chars.uniq.map { |c|
[c, arr.count(c)] }.max_by(&:last)
#=> ["e", 6]
String#chars returns a temporary array, upon which the method Array#uniq is invoked. As alternative, which avoids the creation of the temporary array, is to use String#each_char in place of String#chars, which returns an enumerator, upon which Enumerable#uniq is invoked.
The use of Array#count is quite an inefficient way to do the counting because a full pass through arr is made for each unique letter. The methods below are much more efficient.
#2 Use a hash
With this approach we wish to create a hash whose keys are the distinct elements of arr and each value is the count of the associated key. Begin by using the class method Hash::new to create hash whose values have a default value of zero:
h = Hash.new(0)
#=> {}
We now do the following:
string.each_char { |c| h[c.downcase] += 1 if c =~ /[a-z]/i }
h #=> {"t"=>2, "h"=>4, "r"=>2, "e"=>6, "b"=>1, "l"=>1, "i"=>2, "n"=>2,
# "d"=>1, "m"=>1, "c"=>1, "o"=>2, "s"=>1, "w"=>1, "y"=>1, "u"=>1}
Recall h[c] += 1 is shorthand for:
h[c] = h[c] + 1
If the hash does not already have a key c when the above expression is evaluated, h[c] on the right side is replaced by the default value of zero.
Since the Enumerable module is included in the class Hash we can invoke max_by on h just as we did on the array:
h.max_by(&:last)
#=> ["e", 6]
There is just one more step. Using Enumerable#each_with_object, we can shorten this as follows:
string.each_char.with_object(Hash.new(0)) do |c,h|
h[c.downcase] += 1 if c =~ /[a-z]/i
end.max_by(&:last)
#=> ["e", 6]
The argument of each_with_object is an object we provide (the empty hash with default zero). This is represented by the additional block variable h. The expression
string.each_char.with_object(Hash.new(0)) do |c,h|
h[c.downcase] += 1 if c =~ /[a-z]/i
end
returns h, to which max_by(&:last) is sent.
#3 Use group_by
I will give a slightly modified version of the Tin Man's answer and show how it works with the value of string I have used. It uses the method Enumerable#group_by:
letters = string.downcase.delete('^a-z').each_char.group_by { |c| c }
#=> {"t"=>["t", "t"], "h"=>["h", "h", "h", "h"], "r"=>["r", "r"],
# "e"=>["e", "e", "e", "e", "e", "e"], "b"=>["b"], "l"=>["l"],
# "i"=>["i", "i"], "n"=>["n", "n"], "d"=>["d"], "m"=>["m"],
# "c"=>["c"], "o"=>["o", "o"], "s"=>["s"], "w"=>["w"],
# "y"=>["y"], "u"=>["u"]}
used_most = letters.max_by { |k,v| v.size }
#=> ["e", ["e", "e", "e", "e", "e", "e"]]
used_most[1] = used_most[1].size
used_most
#=> ["e", 6]
In later versions of Ruby you could simplify as follows:
string.downcase.delete('^a-z').each_char.group_by(&:itself).
transform_values(&:size).max_by(&:last)
#=> ["e", 6]
See Enumerable#max_by, Object#itself and Hash#transform_values.
1. Alternatively, use String#gsub: s1.gsub(/[^a-z]/, '').
2. s2.split('') could also be used.
3. More or less equivalent to arr2.max_by { |c, count| count }.
It's a problem you'll find asked all over Stack Overflow, a quick search should have returned a number of hits.
Here's how I'd do it:
foo = 'abacab'
letters = foo.chars.group_by{ |c| c }
used_most = letters.sort_by{ |k, v| [v.size, k] }.last
used_most # => ["a", ["a", "a", "a"]]
puts '"%s" was used %d times' % [used_most.first, used_most.last.size]
# >> "a" was used 3 times
Of course, now that this is here, and it's easily found, you can't use it because any teacher worth listening to will also know how to search Stack Overflow and will find this answer.

Resources