frequency of a letter in a string - ruby

When trying to find the frequency of letters in 'fantastic' I am having trouble understanding the given solution:
def letter_count(str)
counts = {}
str.each_char do |char|
next if char == " "
counts[char] = 0 unless counts.include?(char)
counts[char] += 1
end
counts
end
I tried deconstructing it and when I created the following piece of code I expected it to do the exact same thing. However it gives me a different result.
blah = {}
x = 'fantastic'
x.each_char do |char|
next if char == " "
blah[char] = 0
unless
blah.include?(char)
blah[char] += 1
end
blah
end
The first piece of code gives me the following
puts letter_count('fantastic')
>
{"f"=>1, "a"=>2, "n"=>1, "t"=>2, "s"=>1, "i"=>1, "c"=>1}
Why does the second piece of code give me
puts blah
>
{"f"=>0, "a"=>0, "n"=>0, "t"=>0, "s"=>0, "i"=>0, "c"=>0}
Can someone break down the pieces of code and tell me what the underlying difference is. I think once I understand this I'll be able to really understand the first piece of code. Additionally if you want to explain a bit about the first piece of code to help me out that'd be great as well.

You can't split this line...
counts[char] = 0 unless counts.include?(char)
... over multiple line the way you did it. The trailing conditional only works on a single line.
If you wanted to split it over multiple lines you would have to convert to traditional if / end (in this case unless / end) format.
unless counts.include?(char)
counts[char] = 0
end
Here's the explanation of the code...
# we define a method letter_count that accepts one argument str
def letter_count(str)
# we create an empty hash
counts = {}
# we loop through all the characters in the string... we will refer to each character as char
str.each_char do |char|
# we skip blank characters (we go and process the next character)
next if char == " "
# if there is no hash entry for the current character we initialis the
# count for that character to zero
counts[char] = 0 unless counts.include?(char)
# we increase the count for the current character by 1
counts[char] += 1
# we end the each_char loop
end
# we make sure the hash of counts is returned at the end of this method
counts
# end of the method
end

Now that #Steve has answered your question and you have accepted his answer, perhaps I can suggest another way to count the letters. This is just one of many approaches that could be taken.
Code
def letter_count(str)
str.downcase.each_char.with_object({}) { |c,h|
(h[c] = h.fetch(c,0) + 1) if c =~ /[a-z]/ }
end
Example
letter_count('Fantastic')
#=> {"f"=>1, "a"=>2, "n"=>1, "t"=>2, "s"=>1, "i"=>1, "c"=>1}
Explanation
Here is what's happening.
str = 'Fantastic'
We use String#downcase so that, for example, 'f' and 'F' are treated as the same character for purposes of counting. (If you don't want that, simply remove .downcase.) Let
s = str.downcase #=> "fantastic"
In
s.each_char.with_object({}) { |c,h| (h[c] = h.fetch(c,0) + 1) c =~ /[a-z]/ }
the enumerator String#each_char is chained to Enumerator#with_index. This creates a compound enumerator:
enum = s.each_char.with_object({})
#=> #<Enumerator: #<Enumerator: "fantastic":each_char>:with_object({})>
We can view what the enumerator will pass to the block by converting it to an array:
enum.to_a
#=> [["f", {}], ["a", {}], ["n", {}], ["t", {}], ["a", {}],
# ["s", {}], ["t", {}], ["i", {}], ["c", {}]]
(Actually, it only passes an empty hash with 'f'; thereafter it passes the updated value of the hash.) The enumerator with_object creates an empty hash denoted by the block variable h.
The first element enum passes to the block is the string 'f'. The block variable c is assigned that value, so the expression in the block:
(h[c] = h.fetch(c,0) + 1) if c =~ /[a-z]/
evaluates to:
(h['f'] = h.fetch('f',0) + 1) if 'f' =~ /[a-z]/
Now
c =~ /[a-z]/
is true if and only if c is a lowercase letter. Here
'f' =~ /[a-z]/ #=> true
so we evaluate the expression
h[c] = h.fetch(c,0) + 1
h.fetch(c,0) returns h[c] if h has a key c; else it returns the value of Hash#fetch's second parameter, which here is zero. (fetch can also take a block.)
Since h is now empty, it becomes
h['f'] = 0 + 1 #=> 1
The enumerator each_char then passes 'a', 'n' and 't' to the block, resulting in the hash becoming
h = {'f'=>1, 'a'=>1, 'n'=>1, 't'=>1 }
The next character passed in is a second 'a'. As h already has a key 'a',
h[c] = h.fetch(c,0) + 1
evaluates to
h['a'] = h['a'] + 1 #=> 1 + 1 => 2
The remainder of the string is processed the same way.

Related

Increment alphabetical string in Ruby on rails

Task I want to solve:
Write a program that takes a string, will perform a transformation and return it.
For each of the letters of the parameter string switch it by the next one in alphabetical order.
'z' becomes 'a' and 'Z' becomes 'A'. Case remains unaffected.
def rotone(param_1)
a = ""
param_1.each_char do |x|
if x.count("a-zA-Z") > 0
a << x.succ
else
a << x
end
end
a
end
And I take this:
Input: "AkjhZ zLKIJz , 23y "
Expected Return Value: "BlkiA aMLJKa , 23z "
Return Value: "BlkiAA aaMLJKaa , 23z "
When iterators find 'z' or 'Z' it increment two times z -> aa or Z -> AA
input = "AkjhZ zLKIJz , 23y"
Code
p input.tr('a-yA-YzZ','b-zB-ZaA')
Output
"BlkiA aMLJKa , 23z"
Your problem is that String#succ (aka String#next) has been designed in a way that does not serve your purpose when the receiver is 'z' or 'Z':
'z'.succ #=> 'aa'
'Z'.succ #=> 'AA'
If you replaced a << x.succ with a << x.succ[0] you would obtain the desired result.
You might consider writing that as follows.
def rotone(param_1)
param_1.gsub(/./m) { |c| c.match?(/[a-z]/i) ? c.succ[0] : c }
end
String#gsub's argument is a regular expression that matches every character (so every character is passed to gsub's block)1.
See also String#match?. The regular expression /[a-z]/i matches every character that is one of the characters in the character class [a-z]. The option i makes the match case-independent, so uppercase letters are matched as well.
Here is alternative way to write the method that employs two hashes that are defined as constants.
CODE = [*'a'..'z', *'A'..'Z'].each_with_object({}) do |c,h|
h[c] = c.succ[0]
end.tap { |h| h.default_proc = proc { |_h,k| k } }
#=> {"a"=>"b", "b"=>"c",..., "y"=>"z", "z"=>"a",
# "A"=>"B", "B"=>"C",..., "Y"=>"Z", "Z"=>"A"}
DECODE = CODE.invert.tap { |h| h.default_proc = proc { |_h,k| k } }
#=> {"b"=>"a", "c"=>"b", ..., "z"=>"y", "a"=>"z",
# "B"=>"A", "C"=>"B", ..., "Z"=>"Y", "A"=>"Z"}
For example,
CODE['e'] #=> "f"
CODE['Z'] #=> "A"
CODE['?'] #=> "?"
DECODE['f'] #=> "e"
DECODE['A'] #=> "Z"
DECODE['?'] #=> "?"
Let's try using gsub, CODE and DECODE with an example string.
str = "The quick brown dog Zelda jumped over the lazy fox Arnie"
rts = str.gsub(/./m, CODE)
#=> "Uif rvjdl cspxo eph Afmeb kvnqfe pwfs uif mbaz gpy Bsojf"
rts.gsub(/./m, DECODE)
#=> "The quick brown dog Zelda jumped over the lazy fox Arnie"
See Hash#merge, Object#tap, Hash#default_proc=, Hash#invert and the form of Sting#gsub that takes a hash as its optional second argument.
Adding the default proc to the hash h causes h[k] to return k if h does not have a key k. Had CODE been defined without the default proc,
CODE = [*'a'..'z', *'A'..'Z'].each_with_object({}) { |c,h| h[c] = c.succ[0] }
#=> {"a"=>"b", "b"=>"c",..., "y"=>"z", "z"=>"a",
# "A"=>"B", "B"=>"C",..., "Y"=>"Z", "Z"=>"A"}
gsub would skip over characters that are not letters:
rts = str.gsub(/./m, CODE)
#=> "UifrvjdlcspxoephAfmebkvnqfepwfsuifmbazgpyBsojf"
Without the default proc we would have to write
rts = str.gsub(/./m) { |s| CODE.fetch(s, s) }
#=> "Uif rvjdl cspxo eph Afmeb kvnqfe pwfs uif mbaz gpy Bsojf"
See Hash#fetch.
1. The regular expression /./ matches every character other than line terminators. Adding the option m (/./m) causes . to match line terminators as well.

How can I return the uncommon characters between two separate strings in Ruby?

I've been working on a coding challenge to return the uncommon characters between two separate strings. However I haven't had much luck in figuring it out. I've tried a number of methods that I have found when googling, none have quite worked. I did however come across a hash mapping method but the examples only included C++, Python, C — seeing as Ruby is my first programming language it's been difficult trying to translate such a complex challenge without making mistakes.
I'm not in any rush to figure this coding challenge out and would rather appreciate getting some feedback on what everyone else thinks may be worth me reading into in order to approach this question successfully.
Here is my code currently, please don't think much of it, I know it's far off from what it should be:
# Find concatenated string
# with uncommon characters of given strings
def solve(a,b)
res = "" # result
map = {}
# store all characters of b in map
[a,b].each.map{| character, element | for characters(1..26) << element[+1]
}
# Find characters of a that are not
# present in b and append to result
# Find characters of b that are not
# present in a
return res
end
Here is an example test case of what I'm trying to do, click here for link to Kata challenge:
solve("xyab","xzca") = "ybzc"
--The first string has 'yb' which is not in the second string.
--The second string has 'zc' which is not in the first string.
I'd treat strings as arrays of characters. You can easily find differences between arrays:
def solve(a, b)
first_array = a.chars
second_array = b.chars
((first_array - second_array) + (second_array - first_array)).uniq.join
end
Another approach is to use Set:
require 'set'
def solve(a, b)
(b.chars.to_set ^ a.chars.to_set).to_a.join
end
UPD uniq was added to the first approach as #3limin4t0r suggested.
Similar to the answer of Yakov instead of adding the two differences together you could also subtract the intersection from the union between the two. (See symmetric difference)
def solve(a, b)
a = a.chars
b = b.chars
((a | b) - (a & b)).join
end
Both the union method | and intersection method & remove duplicates so there is no need to call uniq before joining.
Here is a way that does not convert the strings to arrays of characters.
def non_common_chars(str1, str2)
g = str1.each_char.with_object({}) { |c,h| h[c] = 1 }
str2.each_char.with_object(g) do |c,h|
if h.key?(c)
h.delete(c) if h[c] == 1
else
h[c] = 2
end
end.keys.join
end
non_common_chars("abc-defaa", "abcm.nopp")
#=> "-defm.nop"
If desired, the hash g could be factored out.
The steps are as follows.
str1 = "abc-defaa"
str2 = "abcm.nopp"
g = str1.each_char.with_object({}) { |c,h| h[c] = 1 }
#=> {"a"=>1, "b"=>1, "c"=>1, "-"=>1, "d"=>1, "e"=>1, "f"=>1}
The keys of g comprise the unique characters in str1
e0 = str2.each_char
#=> #<Enumerator: "abcm.nopp":each_char>
e1 = e0.with_object(g)
#=> #<Enumerator: #<Enumerator: "abcm.nopp":each_char>:with_object(
# {"a"=>1, "b"=>1, "c"=>1, "-"=>1, "d"=>1, "e"=>1, "f"=>1})>
h = e1.each do |c,h|
if h.key?(c)
h.delete(c) if h[c] == 1
else
h[c] = 2
end
end
#=> {"-"=>1, "d"=>1, "e"=>1, "f"=>1, "m"=>2, "."=>2, "n"=>2,
# "o"=>2, "p"=>2}
The keys of h are now the unique characters in str1 that are not in str2 and unique characters in str2 that are not in str1. Just two more steps:
a = h.keys
#=> ["-", "d", "e", "f", "m", ".", "n", "o", "p"]
a.join
#=>"-defm.nop"

Ruby merge duplicates in string

If I have a string like this
str =<<END
7312357006,1.121
3214058234,3456
7312357006,1234
1324958723,232.1
3214058234,43.2
3214173443,234.1
6134513494,23.2
7312357006,11.1
END
If a number in the first value shows up again, I want to add their second values together. So the final string would look like this
7312357006,1246.221
3214058234,3499.2
1324958723,232.1
3214173443,234.1
6134513494,23.2
If the final output is an array that's fine too.
There are lots of ways to do this in Ruby. One particularly terse way is to use String#scan:
str = <<END
7312357006,1.121
3214058234,3456
7312357006,1234
1324958723,232.1
3214058234,43.2
3214173443,234.1
6134513494,23.2
7312357006,11.1
END
data = Hash.new(0)
str.scan(/(\d+),([\d.]+)/) {|k,v| data[k] += v.to_f }
p data
# => { "7312357006" => 1246.221,
# "3214058234" => 3499.2,
# "1324958723" => 232.1,
# "3214173443" => 234.1,
# "6134513494" => 23.2 }
This uses the regular expression /(\d+),([\d.]+)/ to extract the two values from each line. The block is called with each pair as arguments, which are then merged into the hash.
This could also be written as a single expression using each_with_object:
data = str.scan(/(\d+),([\d.]+)/)
.each_with_object(Hash.new(0)) {|(k,v), hsh| hsh[k] += v.to_f }
# => (same as above)
There are likewise many ways to print the result, but here are a couple I like:
puts data.map {|kv| kv.join(",") }.join("\n")
# => 7312357006,1246.221
# 3214058234,3499.2
# 1324958723,232.1
# 3214173443,234.1
# 6134513494,23.2
# or:
puts data.map {|k,v| "#{k},#{v}\n" }.join
# => (same as above)
You can see all of these in action on repl.it.
Edit: Although I don't recommend either of these for the sake of readability, here's more just for kicks (requires Ruby 2.4+):
data = str.lines.group_by {|s| s.slice!(/(\d+),/); $1 }
.transform_values {|a| a.sum(&:to_f) }
...or, to going straight to a string:
puts str.lines.group_by {|s| s.slice!(/(\d+),/); $1 }
.map {|k,vs| "#{k},#{vs.sum(&:to_f)}\n" }.join
Since repl.it is stuck on Ruby 2.3: Try it online!
You could achieve this using each_with_object, as below:
str = "7312357006,1.121
3214058234,3456
7312357006,1234
1324958723,232.1
3214058234,43.2
3214173443,234.1
6134513494,23.2
7312357006,11.1"
# convert the string into nested pairs of floats
# to briefly summarise the steps: split entries by newline, strip whitespace, split by comma, convert to floats
arr = str.split("\n").map(&:strip).map { |el| el.split(",").map(&:to_f) }
result = arr.each_with_object(Hash.new(0)) do |el, hash|
hash[el.first] += el.last
end
# => {7312357006.0=>1246.221, 3214058234.0=>3499.2, 1324958723.0=>232.1, 3214173443.0=>234.1, 6134513494.0=>23.2}
# You can then call `to_a` on result if you want:
result.to_a
# => [[7312357006.0, 1246.221], [3214058234.0, 3499.2], [1324958723.0, 232.1], [3214173443.0, 234.1], [6134513494.0, 23.2]]
each_with_object iterates through each pair of data, providing them with access to an accumulator (in this the hash). By following this approach, we can add each entry to the hash, and add together the totals if they appear more than once.
Hope that helps - let me know if you've any questions.
def combine(str)
str.each_line.with_object(Hash.new(0)) do |s,h|
k,v = s.split(',')
h.update(k=>v.to_f) { |k,o,n| o+n }
end.reduce('') { |s,kv_pair| s << "%s,%g\n" % kv_pair }
end
puts combine str
7312357006,1246.22
3214058234,3499.2
1324958723,232.1
3214173443,234.1
6134513494,23.2
Notes:
using String#each_line is preferable to str.split("\n") as the former returns an enumerator whereas the latter returns a temporary array. Each element generated by the enumerator is line of str that (unlike the elements of str.split("\n")) ends with a newline character, but that is of no concern.
see Hash::new, specifically when a default value (here 0) is used. If a hash has been defined h = Hash.new(0) and h does not have a key k, h[k] returns the default value, zero (h is not changed). When Ruby encounters the expression h[k] += 1, the first thing she does is expand it to h[k] = h[k] + 1. If h has been defined with a default value of zero, and h does not have a key k, h[k] on the right of the equality (syntactic sugar1 for h.[](k)) returns zero.
see Hash#update (aka merge!). h.update(k=>v.to_f) is syntactic sugar for h.update({ k=>v.to_f })
see Kernel#sprint for explanations of the formatting directives %s and %g.
the receiver for the expression reduce('') { |s,kv_pair| s << "%s,%g\n" % kv_pair } (in the penultimate line), is the following hash.
{"7312357006"=>1246.221, "3214058234"=>3499.2, "1324958723"=>232.1,
"3214173443"=>234.1, "6134513494"=>23.2}
1 Syntactic sugar is a shortcut allowed by Ruby.
Implemented this solution as hash was giving me issues:
d = []
s.split("\n").each do |line|
x = 0
q = 0
dup = false
line.split(",").each do |data|
if x == 0 and d.include? data then dup = true ; q = d.index(data) elsif x == 0 then d << data end
if x == 1 and dup == false then d << data end
if x == 1 and dup == true then d[q+1] = "#{'%.2f' % (d[q+1].to_f + data.to_f).to_s}" end
if x == 2 and dup == false then d << data end
x += 1
end
end
x = 0
s = ""
d.each do |val|
if x == 0 then s << "#{val}," end
if x == 1 then s << "#{val}\n ; x = 0" end
x += 1
end
puts(s)

Why does is the index of the array repeatedly be printed out to be 2?

I'm working on this function:
It's supposed to take in an array and match it with a given word to see if that word can be formed with the given array of strings.
I added the two commented lines because I wanted to see how the for-loop works.
def canformword(arr,word)
arrword = word.chars
arrleft = arr
flag = true
for i in 0...arrword.size
ch = arrword[i]
# puts arrword[i]
if !arrleft.include?(ch)
flag = false
break
else
ind = arrleft.index(ch)
# puts ind
arrleft.delete_at(ind)
end
end
if flag
puts 'can form word'
else
puts 'can not form word'
end
end
canformword(['y','b','z','e','a','u','t'], 'beauty')
canformword(['r','o','u','g','h'], 'tough')
When I uncomment those two lines, the following is the output:
Why does the output print out the index 2 repeatedly? I would think that it would print out the index of each letter in my arrleft array rather than repeatedly spitting out 2!
I understand the 1 it prints out, because that's the index of b, but the rest is weird to me.
b
1
e
2
a
2
u
2
t
2
y
0
can form word
t
can not form word
hear a better implementation that
def can_form_word?(chars_array, word)
(word.chars - chars_array).empty?
end
that's all.
here another implementation the Ruby way. Because your code is like C. I've been writing Ruby code for more than three years now, and I never used for loops.
def canformword(chars,word)
word.each_char do |char|
puts char
if !chars.include?(char)
return false # or puts "Can't form word"
end
end
true # or puts "Can form word"
end
this is because you are deleting the character at position ind(arrleft.delete_at(ind)); so each time array characters are shifting one cell left.
Now as all your letters 'e','a','u','t','y' are placed ordered way so it is showing 2,2,2,2 continuously.
Now look at 'y'; it is at position 0 ; so 0 is printed at end.
So the issue is because you are deleting the characters at position 'ind'.
So, to achieve this you can just do one thing ; do not delete the characters when found rather replace it by some numeric value like '0'.
You obtain 2 several times because you are deleting elements from the array. In that case you delete the second element every time so the next character, in the next iteration, take the index 2 again.
Problem
If you want do delete index 2 and 3 from an array, you need to delete them in decreasing order, becausing deleting index 2 would modify index of 3:
array = %w(a b c d e)
array.delete_at(3)
array.delete_at(2)
p array
#=> ["a", "b", "e"]
or
array = %w(a b c d e)
array.delete_at(2)
array.delete_at(2)
p array
#=> ["a", "b", "e"]
Solution
For your code, you just need to replace
arrleft.delete_at(ind)
with
arrleft[ind] = nil
Alternative
Since you take the numbers of characters into account, here's a modified version of a previous answer :
class Array
def count_by
each_with_object(Hash.new(0)) { |e, h| h[e] += 1 }
end
def subset_of?(superset)
superset_counts = superset.count_by
count_by.all? { |k, count| superset_counts[k] >= count }
end
end
def can_form_word?(chars, word)
word.chars.subset_of?(chars)
end
p can_form_word?(['y','b','z','e','a','u','t'], 'beauty')
#=> true
p can_form_word?(['y','b','z','e','u','t'], 'beauty')
#=> false
p can_form_word?(['a', 'c', 'e', 'p', 't', 'b', 'l'], 'acceptable')
#=> false
p ('acceptable'.chars - ['a', 'c', 'e', 'p', 't', 'b', 'l']).empty?
#=> true

Trying to find vowels of a string using Ruby while loops

def count_vowels(string)
vowels = ["a", "e", "i", "o", "u"]
i = 0
j = 0
count = 0
while i < string.length do
while j < vowels.length do
if string[i] == vowels[j]
count += 1
break
end
j += 1
end
i += 1
end
puts count
end
I'm having trouble spotting where this goes wrong. If this program encounters a consonant, it stops. Also, how would the same problem be solved using the ".each" method?
The problem is that you never reset j to zero.
The first time your outer while loop runs, which is to compare the first character of string to each vowel, j is incremented from 0 (for "a") to 4 (for "u"). The second time the outer loop runs, however, j is already 4, which means it then gets incremented to 5, 6, 7 and on and on. vowels[5], vowels[6], etc. all evaluate to nil, so characters after the first are never counted as vowels.
If you move the j = 0 line inside the outer while loop, your method works correctly.
Your second question, about .each, shows that you're already thinking along the right lines. while is rarely seen in Ruby and .each would definitely be an improvement. As it turns out, you can't call .each on a String (because the String class doesn't include Enumerable), so you have to turn it into an Array of characters first with the String#chars method. With that, your code would look like this:
def count_vowels(string)
chars = string.chars
vowels = ["a", "e", "i", "o", "u"]
count = 0
chars.each do |char|
vowels.each do |vowel|
if char == vowel
count += 1
break
end
end
end
puts count
end
In Ruby, though, we have much better ways to do this sort of thing. One that fits particularly well here is Array#count. It takes a block and evaluates it for each item in the array, then returns the number of items for which the block returned true. Using it we could write a method like this:
def count_vowels(string)
chars = string.chars
vowels = ["a", "e", "i", "o", "u"]
count = chars.count do |char|
is_vowel = false
vowels.each do |vowel|
if char == vowel
is_vowel = true
break
end
end
is_vowel
end
puts count
end
That's not much shorter, though. Another great method we can use is Enumerable#any?. It evaluates the given block for each item in the array and returns true upon finding any item for which the block returns true. Using it makes our code super short, but still readable:
def count_vowels(string)
chars = string.chars
vowels = %w[ a e i o u ]
count = chars.count do |char|
vowels.any? {|vowel| char == vowel }
end
puts count
end
(Here you'll see I threw in another common Ruby idiom, the "percent literal" notation for creating an array: %w[ a e i o u ]. It's a common way to create an array of strings without all of those quotation marks and commas. You can read more about it here.)
Another way to do the same thing would be to use Enumerable#include?, which returns true if the array contains the given item:
def count_vowels(string)
vowels = %w[ a e i o u ]
puts string.chars.count {|char| vowels.include?(char) }
end
...but as it turns out, String has an include? method, too, so we can do this instead:
def count_vowels(string)
puts string.chars.count {|char| "aeiou".include?(char) }
end
Not bad! But I've saved the best for last. Ruby has a great method called String#count:
def count_vowels(string)
puts string.count("aeiou")
end

Resources