why does the array elements change after block? - ruby

I am trying to solve this problem
Given a sentence containing multiple words, find the frequency of a given word in that sentence.
Construct a method named 'find_frequency' which accepts two arguments 'sentence' and 'word', both of which are String objects.
Example: The method, given 'Ruby is The best language in the World' and 'the', should return 2 (comparison should be case-insensitive).
Hint: You can use the method Array#count to count the frequency of any element in the given array.
Since the comparison should be case-insensitive. I use these code to help:
word = "the"
word_set = []
word.size.times do |i|
word[i] = word[i].upcase
word_set << word
word[i] = word[i].downcase
end
Inside the block every time after upcase method the word does change and does add to the word_set, however when the block finish the word_set just contain the the the
What is the problem?

I am still confused about that block code
The block runs 3 times with i = 0, 1, 2. Here's what happens:
# word word_set
word[0] = word[0].upcase # 'The' []
word_set << word # 'The' ['The']
word[0] = word[0].downcase # 'the' ['the']
word[1] = word[1].upcase # 'tHe' ['tHe']
word_set << word # 'tHe' ['tHe', 'tHe']
word[1] = word[1].downcase # 'the' ['the', 'the']
word[2] = word[2].upcase # 'thE' ['thE', 'thE']
word_set << word # 'thE' ['thE', 'thE', 'thE']
word[2] = word[2].downcase # 'the' ['the', 'the', 'the']
This is because you are modifying the very same string object. At the end, your array contains the same string instance three times.
You can avoid this by using dup to create a copy of your string, something like:
word = "the"
word_set = []
word.size.times do |i|
new_word = word.dup
new_word[i] = new_word[i].upcase
word_set << new_word
end
word_set #=> ["The", "tHe", "thE"]
Note that you still have to add the, THe, tHE, ThE and THE to your array.

You add the same string to the array over and over again. In the end of the loop, the array will contain the same string n times (where n ist the length of the string). So, you are changing the same string back and forth between uppercase and lowercase, but it's still just one string.

Well, word[i] = word[i].upcase is problematic, because you are setting it to upcase and downcase, the word will change over time. What you should have focused on is the Array#count method, which takes a block as a parameter.
Here is the gist of it:
def find_frequency sentence, word
sentence.split(" ").count{|w| w == word }
end
To finish it off, complete the puzzle by taking into account case sensitivity of word and sentence

You could do something like this:
class Array
def group_and_count
self.map(&:downcase).each_with_object(Hash.new(0)){|k,h|h[k] += 1}
end
end
Then when you want to find the frequency of a given word you could say:
> words = 'Here is a a a list OF OF words words WORDS'
> freq = words.split.group_and_count
> freq['a']
=> 3

Related

How to I get the right output when there is more than vowel in each word? My code only works with one vowel in each word

Aba is a German children’s game where secret messages are exchanged. In Aba,
after every vowel we add “b” and add that same vowel.
Write a method aba_translate that takes in a sentence string and returns a new
sentence representing its Aba translation. Capitalized words of the original sentence
should be properly capitalized in the new sentence.
aba_translate(“Cats and dogs”) #=> “Cabats aband dobogs”
aba_translate(“Everyone can code”) #=> “Ebeveryobonebe caban cobodebe”
aba_translate(“Africa is Africa in German”) #=> “Abafribicaba ibis Abafribicaba ibin
Gebermaban”
My code:
def aba_translate(sentence)
translation = []
words = sentence.split(" ")
vowels = "aeiou"
vowel = ""
before = ""
after = ""
full = ""
words.each do |word|
word.each_char.with_index do |char, idx|
if vowels.include?(char)
vowel = char
before = word[0...idx]
after = word[idx+1..-1]
full = before + vowel + "b" + vowel + after
translation << full
end
end
end
return translation.join(" ")
end
puts aba_translate("Cats and dogs")
puts aba_translate("Everyone can code")
puts aba_translate("Africa is Africa in German")
Your code generates a whole new word every time it sees a vowel. Instead you need to build each word character by character and make changes when it sees a vowel.
def aba_translate(sentence)
translation = []
words = sentence.split(" ")
vowels = "aeiouAEIOU"
words.each do |word|
full = ""
word.each_char.with_index do |char, idx|
full += char
if vowels.include?(char)
full = full + "b" + char.downcase
end
end
translation << full
end
return translation.join(" ")
end
Every time you find a vowel, you take the entire string before the vowel and the entire string after the vowel and add it to the result.
So, for a word like "code", that means you first produce the output c + obo + de and then the output cod + ebe.
However, what you actually need to do is simply keep the part you have processed instead of duplicating it.
You can do this by either changing your logic to keep track of up to which index you have already processed the word, or alternatively by processing it character-by-character instead of chunk-by-chunk.
However for problems like this, Regex are usually a much better solution:
VOWELS = 'aeiou'
def aba_translate(sentence)
sentence.gsub(Regexp.union(*VOWELS.chars), '\0b\0')
end
or just making VOWELS a Regexp in the first place:
VOWELS = /[aeiou]/.freeze
def aba_translate(sentence)
sentence.gsub(VOWELS, '\0b\0')
end
I think the nested loops complicates it since you can solve the problem with just one loop. Here you just need to initialize a string and a string of vowels to check each character. While you iterate through each character you shovel it into the empty string, and then you check if it is a vowel you shovel b + that vowel's lowercase version to account for the uppercase instances. Then you finally return the new string.
def aba_translate(string)
new_string = ""
vowels = "AEIOUaeiou"
string.each_char do |char|
new_string << char
if vowels.include?(char)
new_string << "b" + char.downcase
end
end
return new_string
end
Here's my beginner-friendly solution
def aba_translate(sent)
vowels = "AEIOUaeiou"
aba_sent = ""
sent.each_char do |char|
if vowels.include?(char)
aba_sent += char + "b" + char.downcase
else
aba_sent += char
end
end
return aba_sent
end

How to search for the longest word in the string

def my_inject(*args)
return yield false if args.empty? && !block_given?
case args.length
when 1 then args.first.is_a?(Symbol) ? sym = args.first : result = args.first
when 2 then result = args.first
sym = args.last
end
result ||= 0
my_each { |x| result = block_given? ? yield(result, x) : result.send(sym, x) }
result
end
What can I add to this code to make it search for the longest word in Array of strings and were add it?
string.split(" ")
.max_by(&:length)
See Enumerable#max_by.
What can I add to this code to make it search for the longest word in a string
"this is a test of the emergency broadcast system".split(' ').sort {|x,y| y.length <=> x.length}.first
To break this down we:
assign a sentence as a string
split that string into words
sort each word by comparing its length with the previous word's length
take the first result
More information on sorting in Ruby at https://apidock.com/ruby/v2_5_5/Enumerable/sort
Some assumptions:
Each array item only contains one word
You only want the longest word returned, not the position
words = %w[tiny humungous antidisestablishmentarianism medium]
puts words.max_by(&:length)

Why does capitalizing the first letter of string elements alter an array?

The following code is intended to capitalize the first letter of each word in a string, and it works:
def capitalize_words(string)
words = string.split(" ")
idx = 0
while idx < words.length
word = words[idx]
word[0] = word[0].upcase
idx += 1
end
return words.join(" ")
end
capitalize_words("this is a sentence") # => "This Is A Sentence"
capitalize_words("mike bloomfield") # => "Mike Bloomfield"
I do not understand why it works. In the while loop, I did not set any element in the words array to anything new. I understand that it might work if I added the following line before the index iteration:
words[idx] = word
I would then be altering the elements of words. However, the code works even without that line.
yet in no place in the while loop that I am using to capitalize the
first letter of each word do I actually set any of the elements in the
"words" array to anything new.
You do, actually, right here:
word = words[idx]
word[0] = word[0].upcase # This changes words[idx][0]!
The upcase method does just that: returns the upcase of a given string. For example:
'example'.upcase
# => "EXAMPLE"
'example'[0].upcase
# => "E"
The method String#[]= that you are using in:
word[0] = ...
is not variable assignment. It alters the content of the receiver string at the given index, retaining the identity of the string as an object. And since word is not a copy but is the original string taken from words, in turn, you are modifying words.
You're doing a lot of work that you don't have to:
def capitalize_words(string)
string.split.map{ |w|
[w[0].upcase, w[1..-1]].join # => "Foo", "Bar"
}.join(' ')
end
capitalize_words('foo bar')
# => "Foo Bar"
Breaking it down:
'foo'[0] # => "f"
'foo'[0].upcase # => "F"
'foo'[1..-1] # => "oo"
['F', 'oo'].join # => "Foo"

Find if all letters in a string are unique

I need to know if all letters in a string are unique. For a string to be unique, a letter can only appear once. If all letters in a string are distinct, the string is unique. If one letter appears multiple times, the string is not unique.
"Cwm fjord veg balks nth pyx quiz."
# => All 26 letters are used only once. This is unique
"This is a string"
# => Not unique, i and s are used more than once
"two"
# => unique, each letter is shown only once
I tried writing a function that determines whether or not a string is unique.
def unique_characters(string)
for i in ('a'..'z')
if string.count(i) > 1
puts "This string is unique"
else
puts "This string is not unique"
end
end
unique_characters("String")
I receive the output
"This string is unique" 26 times.
Edit:
I would like to humbly apologize for including an incorrect example in my OP. I did some research, trying to find pangrams, and assumed that they would only contain 26 letters. I would also like to thank you guys for pointing out my error. After that, I went on wikipedia to find a perfect pangram (I wrongly thought the others were perfect).
Here is the link for reference purposes
http://en.wikipedia.org/wiki/List_of_pangrams#Perfect_pangrams_in_English_.2826_letters.29
Once again, my apologies.
s = "The quick brown fox jumps over the lazy dog."
.downcase
("a".."z").all?{|c| s.count(c) <= 1}
# => false
Another way to do it is:
s = "The quick brown fox jumps over the lazy dog."
(s.downcase !~ /([a-z]).*\1/)
# => false
I would solve this in two steps: 1) extract the letters 2) check if there are duplicates:
letters = string.scan(/[a-z]/i) # append .downcase to ignore case
letters.length == letters.uniq.length
Here is a method that does not convert the string to an array:
def dupless?(str)
str.downcase.each_char.with_object('') { |c,s|
c =~ /[a-z]/ && s.include?(c) ? (return false) : s << c }
true
end
dupless?("Cwm fjord veg balks nth pyx quiz.") #=> true
dupless?("This is a string.") #=> false
dupless?("two") #=> true
dupless?("Two tubs") #=> false
If you want to actually keep track of the duplicate characters:
def is_unique?(string)
# Remove whitespaces
string = string.gsub(/\s+/, "")
# Build a hash counting all occurences of each characters
h = Hash.new { |hash, key| hash[key] = 0 }
string.chars.each { |c| h[c] += 1 }
# An array containing all the repetitions
res = h.keep_if {|k, c| c > 1}.keys
if res.size == 0
puts "All #{string.size} characters are used only once. This is unique"
else
puts "Not unique #{res.join(', ')} are used more than once"
end
end
is_unique?("This is a string") # Not unique i, s are used more than once
is_unique?("two") # All 3 characters are used only once. This is unique
To check if a string is unique or not, you can try out this:
string_input.downcase.gsub(/[^a-z]/, '').split("").sort.join('') == ('a' .. 'z').to_a.join('')
This will return true, if all the characters in your string are unique and if they include all the 26 characters.
def has_uniq_letters?(str)
letters = str.gsub(/[^A-Za-z]/, '').chars
letters == letters.uniq
end
If this doesn't have to be case sensitive,
def has_uniq_letters?(str)
letters = str.downcase.gsub(/[^a-z]/, '').chars
letters == letters.uniq
end
In your example, you mentioned you wanted additional information about your string (list of unique characters, etc), so this example may also be useful to you.
# s = "Cwm fjord veg balks nth pyx quiz."
s = "This is a test string."
totals = Hash.new(0)
s.downcase.each_char { |c| totals[c] += 1 if ('a'..'z').cover?(c) }
duplicates, uniques = totals.partition { |k, v| v > 1 }
duplicates, uniques = Hash[duplicates], Hash[uniques]
# duplicates = {"t"=>4, "i"=>3, "s"=>4}
# uniques = {"h"=>1, "a"=>1, "e"=>1, "r"=>1, "n"=>1, "g"=>1}

How to make sure certain elements not get into arrays in Ruby

I have an array lets say
array1 = ["abc", "a", "wxyz", "ab",......]
How do I make sure neither for example "a" (any 1 character), "ab" (any 2 characters), "abc" (any 3 characters), nor words like "that", "this", "what" etc nor any of the foul words are saved in array1?
This removes elements with less than 4 characters and words like this, that, what from array1 (if I got it right):
array1.reject! do |el|
el.length < 4 || ['this', 'that', 'what'].include?(el)
end
This changes array1. If you use reject (without !), it'll return the result and not change array1
You can open and add a new interface to the Array class which will disallow certain words. Example:
class Array
def add(ele)
unless rejects.include?(ele)
self.push ele
end
end
def rejects
['this', 'that', 'what']
end
end
arr = []
arr.add "one"
puts arr
arr.add "this"
puts arr
arr.add "aslam"
puts arr
Output would be:
one one one aslam
And notice the word "this" was not added.
You could create a stop list. Using a hash for this would be more efficient than an array as lookup time will be consistant with a hash. With an array the lookup time is proportional to the number of elements in the array. If you are going to check for stop words a lot, I suggest using a hash that contains all the stop words. Using your code, you could do the following
badwords_a = ["abc", "a", "wxyz", "ab"] # Your array of bad words
badwords_h = {} # Initialize and empty hash
badwords_a.each{|word| badwords_h[word] = nil} # Fill the hash
goodwords = []
words_to_process = ["abc","a","Foo","Bar"] # a list of words you want to process
words_to_process.each do |word| # Process new words
if badwords_h.key?(word)
else
goodwords << word # Add the word if it did not match the bad list
end
end
puts goodwords.join(", ")

Resources