longest palindrome in Ruby on Rails - ruby-on-rails

Write a method that takes in a string of lowercase letters (no uppercase letters, no repeats). Consider the substrings of the string: consecutive sequences of letters contained inside the string.
Find the longest such string of letters that is a palindrome.
Based on local method Palindrome?(string), I implemented longest-palindrome(string) as below with test cases:
def palindrome?(string)
i = 0
while i < string.length
if string[i] != string[(string.length - 1) - i]
return false
end
i += 1
end
return true
end
def longest_palindrome(string)
dix = 0
lstr = ""
lstrc = nil
while dix < string.length
dix2 = 1
while dix2 < string.length
str = string.slice(dix,dix2)
count = str.length
if palindrome?(str)
if lstrc == nil || lstrc < count
lstr = str
lstrc = count
end
end
dix2 += 1
end
dix += 1
end
puts(lstr)
return lstr
end
# These are tests to check that your code is working. After writing
# your solution, they should all print true.
puts(
'longest_palindrome("abcbd") == "bcb": ' +
(longest_palindrome('abcbd') == 'bcb').to_s
)
puts(
'longest_palindrome("abba") == "abba": ' +
(longest_palindrome('abba') == 'abba').to_s
)
puts(
'longest_palindrome("abcbdeffe") == "effe": ' +
(longest_palindrome('abcbdeffe') == 'effe').to_s
)
Test results as below:
bcb
longest_palindrome("abcbd") == "bcb": true
bb
longest_palindrome("abba") == "abba": false
effe
longest_palindrome("abcbdeffe") == "effe": true
Why did the second test failed?

... this line is preventing you from considering the entire string
while dix2 < string.length
So when dix is the whole string, you're not doing any testing for palindromes
Change the line to...
while dix2 <= string.length
It would actually be slightly more efficient if you did...
while dix2 <= string.length - dix
Which would prevent you from testing (for, say, a string of length 10), string(7,3) and string(7,4) and string(7,5) etc. etc., which are all basically the same string.

Related

How to reduce 'complexity too high' with || - or operator

I've got a simple method that counts total lesson hours in the university schedule for additional modules in the department (students can attend many departments)
def hours_total
#hours_total = user.open_departments.each_with_object({}) do |department, h|
h[department] = (sports_hours[department] || 0) +
(science_hours[department] || 0) +
(intership_sum[department] || 0) +
(art[department] || 0) -
((obligatory_topics[department] || 0) +
(base[department] || 0))
end
end
How can I fix here Cyclomatic complexity for hours_total is too high.? I have no idea how to not repeat || 0 cause in some departments sports_hours[department] can be nil value
The first step I'd take
def hours_total
#hours_total = user.open_departments.each_with_object({}) do |department, h|
positive = [sport_hours, science_hours, internship_sum, art].sum do |pos_h|
pos_h[department].to_i
end
negative = [obligatory_topics, base].sum do |neg_h|
neg_h[department].to_i
end
h[department] = positive - negative
end
end
Note: if your hours can be float values, substitute to_i with to_f.
Now if you and your Rubocop are ok with that, I'd probably leave it. If any of you is unhappy, the positive and negative should be extracted to a method.

Ruby String Arrays Overwriting Themselves?

For some reason, string is getting rewritten as mirror by the operation mirror[j] = string[i]
I have no idea why this would be, I'm not commanding it to. The outputs of my reverse(string) function will stop at the middle, like:
ABCDEF -> FEDDEF
but I'm trying to do:
ABCDEF -> FEDCBA
def reverse(string)
mirror = string
i = string.length - 1
j = 0
while j < string.length
mirror[j] = string[i]
puts(mirror)
j = j + 1
i = i - 1
end
return mirror
end
You need to use #dup. In your case, mirror = string means both the variables are holding the reference of the same object. So, when you change the object, that can be reflected through both string and mirror.
def reverse(string)
mirror = string.dup
i = string.length - 1
j = 0
while j < string.length
mirror[j] = string[i]
j += 1 # same as j = j + 1
i -= 1 # same as i = i - 1
end
mirror
# last expression is returned by default, so you can write only `mirror` instead of `return mirror`
end
p reverse 'ABCDEF' # "FEDCBA"

Array not returning most frequent value

def most_common_letter(string)
letter = nil
final_letter = nil
letter_count = nil
idx1 = 0
idx2 = 0
count = 0
while idx1 < string.length
letter = string[idx1]
while idx2 < string.length
if letter == string[idx2]
count += 1
end
idx2 += 1
end
if (letter_count == nil) || (count > letter_count)
letter_count = count
final_letter = letter
end
idx1 += 1
end
arr = []
arr.push(final_letter)
arr.push(letter_count)
return arr
end
puts(most_common_letter("abbab") == ["b", 3])
#should be true
Keep getting [a,2] which leads me to believe it is only executing parts of the code when in my mind the code should be resetting the value of final letter and letter count every time there is a more frequent letter. Where have I gone wrong?
Often a counting hash is used for this type of problem:
def most_frequent_letter(str)
str.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }.max_by(&:last)
end
most_frequent_letter("bananas are so-so")
#=> ["a",4]
The steps:
h = "bananas are so-so".each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }
#=> {"b"=>1, "a"=>4, "n"=>2, "s"=>3, " "=>2, "r"=>1, "e"=>1, "o"=>2, "-"=>1}
h.max_by(&:last)
#=> ["a",4]
Here is an easier way to solve this:
def most_common_letter(string)
letters = string.split('').sort!
frequency = letters.inject(Hash.new(0)) { |h,v| h[v] += 1; h }
frequency.max_by{|k,v| v}
end
most_common_letter("abbab")
=> ["b", 3]
Let me go through this code line-by-line to explain.
1) Get the letters of the string and sort them alphabetically:
string = "abbab"
letters = string.split('').sort!
=> ["a", "a", "b", "b", "b"]
2) Create a hash with keys (letters) and values (occurrence)
frequency = letters.inject(Hash.new(0)) { |h,v| h[v] += 1; h }
=> {"a"=>2, "b"=>3}
3) Find the highest value, and return the key value pair as an array.
frequency.max_by{|k,v| v}
=> ["b", 3]
The problem you are running into is that you are not resetting your idx2 after the loop.
You can debug this if you puts the idx two as follows:
def most_common_letter(string)
letter = nil
final_letter = nil
letter_count = nil
idx1 = 0
idx2 = 0
count = 0
while idx1 < string.length
letter = string[idx1]
while idx2 < string.length
if letter == string[idx2]
count += 1
end
idx2 += 1
end
puts "#{idx2}" #=> This is always 5
if (letter_count == nil) || (count > letter_count)
letter_count = count
final_letter = letter
end
idx1 += 1
end
arr = []
arr.push(final_letter)
arr.push(letter_count)
return arr
end
puts(most_common_letter("abbab"))
Your algorithm is simply doing the following:
comparing idx1 with idx2:
str[0] with str[0]
str[0] with str[1]
str[0] with str[2]
str[0] with str[3]
str[0] with str[4]
then idx2 is out of range and will never satisfy idx2 < string.length again, because the value is not reset.
This is where the logical error is. I won't give you another solution because it seems like you are trying to figure this out on your own. Hope this helps.
Try this
def most_common_letter(str)
str.chars.
group_by{|v| v}.
map{|k, l| [k, l.size]}.
sort_by{|k, size| size}.
last
end
Now:
puts(most_common_letter("abbab") == ["b", 3])
# true

How to use .map in Rails

Pig Latin
Rule 1: If a word begins with a vowel sound, add an "ay" sound to
the end of the word.
Rule 2: If a word begins with a consonant sound, move it to the end
of the word, and then add an "ay" sound to the end of the word.
The following program works in ruby. But I'm confused on how to use the "map" function? Please see the code as follows:
def translate(sentence)
if sentence.include?(" ")
words = sentence.split(" ").map do |word|
translate_word(word)
end
return words.join(" ")
else single_word = sentence
translate_word(single_word)
end
end
The above sentences works! but if I use:
words = sentence.split(" ")
words.map do |word|
translate_word(word)
end
It DOESN'T work! Why? I thought they were the same...
def translate_word(w)
vowels = %w[a e i o u]
consonants = ("a".."z").to_a - vowels
if vowels.include?(w[0])
w + "ay"
elsif consonants.include?(w[0]) && vowels.include?(w[1]) && w[1] != "u"
w[1..-1] + w[0] + "ay"
elsif (consonants.include?(w[0]) && consonants.include?(w[1]) && vowels.include?(w[2]) && w[2] != "u") || (w[0] == "q" && w[1] == "u")
w[2..-1] + w[0..1] + "ay"
elsif (consonants.include?(w[0]) && consonants.include?(w[1]) && consonants.include?(w[2]) && vowels.include?(w[3]))
w[3..-1] + w[0..2] + "ay"
elsif consonants.include?(w[0]) && w[1] == "q" && w[2] == "u"
w[3..-1] + w[0..2] + "ay"
end
end
#map function returns a new object which you are dismissing.
To save the result you should assign it back to words like this:
words = sentence.split(" ")
words = words.map do |word|
translate_word(word)
end
Or use #map! instead.

Why is length not considered a method in this code?

def longest_word(string)
words = string.split
idx = 0
while idx < words.length
if words[idx].length > words[idx + 1].length
longest = words[idx]
else
longest = words [idx + 1]
end
idx += 1
end
return longest
end
puts(longest_word("peas rambling tattoo") == "rambling")
keep getting error message
longest_word.rb:5:in longest_word': undefined methodlength' for nil:NilClass (NoMethodError)
from longest_word.rb:15:in `'
any information on why this is happening would be great
words.length returns the number of elements, but the index starts with 0.
You need a (words.length - 1)
def longest_word(string)
words = string.split
idx = 0
while idx < (words.length - 1)
if words[idx].length > words[idx + 1].length
longest = words[idx]
else
longest = words [idx + 1]
end
idx += 1
end
return longest
end
puts(longest_word("peas rambling tattoo") == "rambling")
Your code is not 'rubyesk', I would prefer:
def longest_word(string)
longest = ''
string.split.each do |word|
longest = word.length > longest.length ? word : longest
end
return longest
end
puts(longest_word("peas rambling tattoo") == "rambling")
or maybe better:
def longest_word(string)
longest = ''
string.split.each do |word|
longest = word if word.length > longest.length
end
return longest
end
puts(longest_word("peas rambling tattoo") == "rambling")
You can also use max_by to get shorter code:
def longest_word(string)
string.split.max_by{|word| word.length}
end
puts(longest_word("peas rambling tattoo") == "rambling")
Or even shorter with string.split.max_by(&:length)

Resources