Both tests are returning false even though in my mind the code executes perfectly - ruby

# 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.
#
# Difficulty: medium.
def most_common_letter(string)
letter = 0
letter_count = 0
idx1 = 0
mostfreq_letter = 0
largest_letter_count = 0
while idx1 < string.length
letter = string[idx1]
idx2 = 0
while idx2 < string.length
if letter == string[idx2]
letter_count += 1
end
idx2 += 1
end
if letter_count > largest_letter_count
largest_letter_count = letter_count
mostfreq_letter = letter
end
idx1 += 1
end
return [mostfreq_letter, largest_letter_count]
end
# These are tests to check that your code is working. After writing
# your solution, they should all print true.
puts(
'most_common_letter("abca") == ["a", 2]: ' +
(most_common_letter('abca') == ['a', 2]).to_s
)
puts(
'most_common_letter("abbab") == ["b", 3]: ' +
(most_common_letter('abbab') == ['b', 3]).to_s
)
So in my mind the program should set a letter and then once that is set cycle through the string looking for letters that are the same, and then once there is one it adds to letter count and then it judges if its the largest letter count and if it is those values are stored to the eventual return value that should be correct once the while loop ends. However I keep getting false false. Where am I going wrong?

Your code does not return [false, false] to me; but it does return incorrect results. The hint by samgak should lead you to the bug.
However, for a bit shorter and more Rubyish alternative:
def most_common_letter(string)
Hash.new(0).tap { |h|
string.each_char { |c| h[c] += 1 }
}.max_by { |k, v| v }
end
Create a new Hash that has a default value of 0 for each entry; iterate over characters and count the frequency for each of them in the hash; then find which hash entry is the largest. When a hash is iterated, it produces pairs, just like what you want for your function output, so that's nice, too.

Related

How can I improve the performance of this small Ruby function?

I am currently doing a Ruby challenge and get the error Terminated due to timeout
for some testcases where the string input is very long (10.000+ characters).
How can I improve my code?
Ruby challenge description
You are given a string containing characters A and B only. Your task is to change it into a string such that there are no matching adjacent characters. To do this, you are allowed to delete zero or more characters in the string.
Your task is to find the minimum number of required deletions.
For example, given the string s = AABAAB, remove A an at positions 0 and 3 to make s = ABAB in 2 deletions.
My function
def alternatingCharacters(s)
counter = 0
s.chars.each_with_index { |char, idx| counter += 1 if s.chars[idx + 1] == char }
return counter
end
Thank you!
This could be faster returning the count:
str.size - str.chars.chunk_while{ |a, b| a == b }.to_a.size
The second part uses String#chars method in conjunction with Enumerable#chunk_while.
This way the second part groups in subarrays:
'aababbabbaab'.chars.chunk_while{ |a, b| a == b}.to_a
#=> [["a", "a"], ["b"], ["a"], ["b", "b"], ["a"], ["b", "b"], ["a", "a"], ["b"]]
Trivial if you can use squeeze:
str.length - str.squeeze.length
Otherwise, you could try a regular expression that matches those A (or B) that are preceded by another A (or B):
str.enum_for(:scan, /(?<=A)A|(?<=B)B/).count
Using enum_for avoids the creation of the intermediate array.
The main issue with:
s.chars.each_with_index { |char, idx| counter += 1 if s.chars[idx + 1] == char }
Is the fact that you don't save chars into a variable. s.chars will rip apart the string into an array of characters. The first s.chars call outside the loop is fine. However there is no reason to do this for each character in s. This means if you have a string of 10.000 characters, you'll instantiate 10.001 arrays of size 10.000.
Re-using the characters array will give you a huge performance boost:
require 'benchmark'
s = ''
options = %w[A B]
10_000.times { s << options.sample }
Benchmark.bm do |x|
x.report do
counter = 0
s.chars.each_with_index { |char, idx| counter += 1 if s.chars[idx + 1] == char }
# create a character array for each iteration ^
end
x.report do
counter = 0
chars = s.chars # <- only create a character array once
chars.each_with_index { |char, idx| counter += 1 if chars[idx + 1] == char }
end
end
user system total real
8.279767 0.000001 8.279768 ( 8.279655)
0.002188 0.000003 0.002191 ( 0.002191)
You could also make use of enumerator methods like each_cons and count to simplify the code, this doesn't increase performance cost a lot, but makes the code a lot more readable.
Benchmark.bm do |x|
x.report do
counter = 0
chars = s.chars
chars.each_with_index { |char, idx| counter += 1 if chars[idx + 1] == char }
end
x.report do
s.each_char.each_cons(2).count { |a, b| a == b }
# ^ using each_char instead of chars to avoid
# instantiating a character array
end
end
user system total real
0.002923 0.000000 0.002923 ( 0.002920)
0.003995 0.000000 0.003995 ( 0.003994)

Ruby undefined Method/NoMethodError '*' for nil:NilClass, works until looped

My program orders arrays 1 and 2, and iterates to check whether each element in 1 is the sqrt of each element in 2. I've tested the comparison without the loop and it works fine, so I don't think that there's an uninitialised variable.
def comp(array1, array2)
order1 = array1.sort
order2 = array2.sort
i = 0
while i < order1.length
if order1[i] * order1[i] == order2[i]
i += 1
else
false
end
end
order1[i] * order1[i] == order2[i]
end
Can you point me in the direction of the issue? I've also not used Math.sqrt because it times out on my interface.
Your i equals order1.length, after your loop, so the last line of your method is basically
order1[order1.length] * order1[order1.length] == order2[order1.length]
which is (assuming your arrays are the same length):
nil * nil == nil
which throws an error. Not sure why you need the last line, if you remove it and simply return a counter, your method works as expected if you use a dedicated counter, for the elements which match your condition, instead of using index for that (your index has to be incremented always):
def comp(array1, array2)
order1 = array1.sort
order2 = array2.sort
i = 0
counter = 0
while i < order1.length
if order1[i] * order1[i] == order2[i]
counter += 1
end
i += 1
end
counter
end
In Ruby it is pretty common to use proper enumerators for iterating over collections, so your while can be nicely substituted by Enumerable#each_with_index:
def comp(array1, array2)
order1 = array1.sort
order2 = array2.sort
counter = 0
order1.each_with_index do |el, i|
if el * el == order2[i]
counter += 1
end
end
counter
end
And as the last step, we can also Array#count how many elements in an array meet a certain condition without needing to specify a local variable, like so:
def comp(array1, array2)
order2 = array2.sort
array1.sort.each_with_index.count { |el, i| el ** 2 == order2[i] }
end

While loop through a string in Ruby

I am entering "901000" as an argument to the following method, and I expect it to remove the last three zeros and return "901".
def zeros(x)
str = x.to_s
i = str.length-1
while str[i] == "0"
str = str.delete(str[i])
i = i-1
end
return str
end
But it returns "91" instead. I cannot understand why my code does not work. Can someone help please?
At first, str is "901000", i = str.length-1 is 5, and str[i] is "0". Hence,
str = str.delete(str[i])
is equivalent to
str = "901000".delete("0")
That gives you "91".
Then, by i = i-1, i becomes 4, and str[i] (which is equivalent to "91"[4]) becomes nil, and str[i] == 0 (which is equivalent to nil == 0) becomes false. So the loop ends, returning the value str, which is "91".
To do what you want, some simple ways are:
"901000".sub(/0+\z/, "") # => "901"
"901000"[/.*?(?=0+\z)/] # => "901"

Optimising code for matching two strings modulo scrambling

I am trying to write a function scramble(str1, str2) that returns true if a portion of str1 characters can be rearranged to match str2, otherwise returns false. Only lower case letters (a-z) will be used. No punctuation or digits will be included. For example:
str1 = 'rkqodlw'; str2 = 'world' should return true.
str1 = 'cedewaraaossoqqyt'; str2 = 'codewars' should return true.
str1 = 'katas'; str2 = 'steak' should return false.
This is my code:
def scramble(s1, s2)
#sorts strings into arrays
first = s1.split("").sort
second = s2.split("").sort
correctLetters = 0
for i in 0...first.length
#check for occurrences of first letter
occurrencesFirst = first.count(s1[i])
for j in 0...second.length
#scan through second string
occurrencesSecond = second.count(s2[j])
#if letter to be tested is correct and occurrences of first less than occurrences of second
#meaning word cannot be formed
if (s2[j] == s1[i]) && occurrencesFirst < occurrencesSecond
return false
elsif s2[j] == s1[i]
correctLetters += 1
elsif first.count(s1[s2[j]]) == 0
return false
end
end
end
if correctLetters == 0
return false
end
return true
end
I need help optimising this code. Please give me suggestions.
Here is one efficient and Ruby-like way of doing that.
Code
def scramble(str1, str2)
h1 = char_counts(str1)
h2 = char_counts(str2)
h2.all? { |ch, nbr| nbr <= h1[ch] }
end
def char_counts(str)
str.each_char.with_object(Hash.new(0)) { |ch, h| h[ch] += 1 }
end
Examples
scramble('abecacdeba', 'abceae')
#=> true
scramble('abecacdeba', 'abweae')
#=> false
Explanation
The three steps are as follows.
str1 = 'abecacdeba'
str2 = 'abceae'
h1 = char_counts(str1)
#=> {"a"=>3, "b"=>2, "e"=>2, "c"=>2, "d"=>1}
h2 = char_counts(str2)
#=> {"a"=>2, "b"=>1, "c"=>1, "e"=>2}
h2.all? { |ch, nbr| nbr <= h1[ch] }
#=> true
The last statement is equivalent to
2 <= 3 && 1 <= 2 && 1 <= 2 && 2 <=2
The method char_counts constructs what is sometimes called a "counting hash". To understand how char_counts works, see Hash::new, especially the explanation of the effect of providing a default value as an argument of new. In brief, if a hash is defined h = Hash.new(0), then if h does not have a key k, h[k] returns the default value, here 0 (and the hash is not changed).
Suppose, for different data,
h1 = { "a"=>2 }
h2 = { "a"=>1, "b"=>2 }
Then we would find that 1 <= 2 #=> true but 2 <= 0 #=> false, so the method would return false. The second comparison is 2 <= h1["b"]. As h1 does not have a key "b", h1["b"] returns the default value, 0.
The method char_counts is effectively a short way of writing the method expressed as follows.
def char_counts(str)
h = {}
str.each_char do |ch|
h[ch] = 0 unless h.key?(ch) # instead of Hash.new(0)
h[ch] = h[c] + 1 # instead of h[c][ += 1
end
h # no need for this if use `each_with_object`
end
See Enumerable#each_with_object, String#each_char (preferable to String.chars, as the latter produces an unneeded temporary array whereas the former returns an enumerator) and Hash#key? (or Hash#has_key?, Hash#include? or Hash#member?).
An Alternative
def scramble(str1, str2)
str2.chars.difference(str1.chars).empty?
end
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
I have found the method Array#difference to be so useful I proposed it be added to the Ruby Core (here). The response has been, er, underwhelming.
One way:
def scramble(s1,s2)
s2.chars.uniq.all? { |c| s1.count(c) >= s2.count(c) }
end
Another way:
def scramble(s1,s2)
pool = s1.chars.group_by(&:itself)
s2.chars.all? { |c| pool[c]&.pop }
end
Yet another:
def scramble(s1,s2)
('a'..'z').all? { |c| s1.count(c) >= s2.count(c) }
end
Since this appears to be from codewars, I submitted my first two there. Both got accepted and the first one was a bit faster. Then I was shown solutions of others and saw someone using ('a'..'z') and it's fast, so I include that here.
The codewars "performance tests" aren't shown explicitly but they're all up to about 45000 letters long. So I benchmarked these solutions as well as Cary's (yours was too slow to be included) on shuffles of the alphabet repeated to be about that long (and doing it 100 times):
user system total real
Stefan 1 0.812000 0.000000 0.812000 ( 0.811765)
Stefan 2 2.141000 0.000000 2.141000 ( 2.127585)
Other 0.125000 0.000000 0.125000 ( 0.122248)
Cary 1 2.562000 0.000000 2.562000 ( 2.575366)
Cary 2 3.094000 0.000000 3.094000 ( 3.106834)
Moral of the story? String#count is fast here. Like, ridiculously fast. Almost unbelievably fast (I actually had to run extra tests to believe it). It counts through about 1.9 billion letters per second (100 times 26 letters times 2 strings of ~45000 letters, all in 0.12 seconds). Note that the difference to my own first solution is just that I do s2.chars.uniq, and that increases the time from 0.12 seconds to 0.81 seconds. Meaning this double pass through one string takes about six times as long as the 52 passes for counting. The counting is about 150 times faster. I did expect it to be very fast, because it presumably just searches a byte in an array of bytes using C code (edit: looks like it does), but this speed still surprised me.
Code:
require 'benchmark'
def scramble_stefan1(s1,s2)
s2.chars.uniq.all? { |c| s1.count(c) >= s2.count(c) }
end
def scramble_stefan2(s1,s2)
pool = s1.chars.group_by(&:itself)
s2.chars.all? { |c| pool[c]&.pop }
end
def scramble_other(s1,s2)
('a'..'z').all? { |c| s1.count(c) >= s2.count(c) }
end
def scramble_cary1(str1, str2)
h1 = char_counts(str1)
h2 = char_counts(str2)
h2.all? { |ch, nbr| nbr <= h1[ch] }
end
def char_counts(str)
str.each_char.with_object(Hash.new(0)) { |ch, h| h[ch] += 1 }
end
def scramble_cary2(str1, str2)
str2.chars.difference(str1.chars).empty?
end
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
Benchmark.bmbm do |x|
n = 100
s1 = (('a'..'z').to_a * (45000 / 26)).shuffle.join
s2 = s1.chars.shuffle.join
x.report('Stefan 1') { n.times { scramble_stefan1(s1, s2) } }
x.report('Stefan 2') { n.times { scramble_stefan2(s1, s2) } }
x.report('Other') { n.times { scramble_other(s1, s2) } }
x.report('Cary 1') { n.times { scramble_cary1(s1, s2) } }
x.report('Cary 2') { n.times { scramble_cary2(s1, s2) } }
end

NoMethodError with .chr.to_i

I'm trying to create a recursive method sum_of_digits(i) that takes the sum of integers, i.e. '456' = 4+5+6 = 15
However, I receive a NoMethodError for chr.to_i in the following code:
def sum_of_digits(i)
input = i.to_s
if i == 0
return 0
elsif input.length == 1
return i
else
for n in 1..input.length
sum += input[i].chr.to_i % 10^(n-1)
end
end
return sum
end
Thank you!
String indexes are zero-based in ruby. The problem is here:
for n in 1..input.length
it should be written as
for n in 0..input.length-1
BTW, call to chr is superfluous as well, since you already have a string representation of a digit there. As well, sum must be declared in advance and set to zero.
Also, the whole code is not ruby idiomatic: one should avoid using unnecessary returns and for-loop. The modified version (just in case) would be:
def sum_of_digits(i)
input = i.to_s
case
when i == 0 then 0 # return zero
when input.length == 1 then i # return i
else
sum = 0
input.length.times do |index|
sum += input[index].to_i % 10^index
end
sum
end
end
or, even better, instead of
sum = 0
input.length.times do |index|
sum += input[index].to_i % 10^index
end
sum
one might use inject:
input.length.times.inject(0) do |sum, index|
sum += input[index].to_i % 10^index
end

Resources