I'm creating a function that takes a string and creates an acronym but am running into errors.
When I input "Complementary metal-oxide semiconductor" I get "CS" in return when expecting "CMOS". Any suggestions why this might happen? I pass it plenty of other strings and it works, just doesn't work in this case.
class Acronym
def self.abbreviate(phrase)
letters = phrase.split("")
acronym = []
letters.each do |letter|
previous = letters.index(letter) - 1
if previous == -1
acronym.push(letter)
elsif letters[previous] == " " || letters[previous] == "-"
acronym.push(letter)
end
end
acronym.join("").upcase
end
end
Simplifies to
def acronym(str)
str.split(/ |-/).map(&:first).join.upcase
end
The above depends on the Rails activesupport library. Here's a Ruby-only variation:
str.split(/ |-/).map { |s| s[0] }.join.upcase
The issue with your code is that index() returns the first occurrence of the given letter. So, two problems:
The 'm' in 'metal' is not the first occurrence of 'm' in the string. It appears in the word 'complementary'. Thus, whenever it sees an 'm' in the string, previous will always be 'o' and thus not trigger a push().
Anytime the first letter in your string recurs (regardless of position), it will trigger your first condition. You can see the effect if you change the initial 'C' to 'c' in your test string. The result will be CSCC because there are two 'c's in 'semiconductor'.
As an alternative, here is an option that uses regex:
def self.abbreviate(phrase)
phrase.gsub('-', ' ')
.scan(/(\A\w|(?<=\s)\w)/)
.flatten
.join.upcase
end
Step by step:
Borrowing the .gsub from #DollarChills to turn the '-' into a space.
scan() returns an array of all matches. The regex matches the first word in the string and any word that is preceded by a space.
The result of the scan is actually an array of arrays, so flatten unnests them.
Combine into a string and upcase
You could try using gsub to ignore the hyphen.
<%= ('Complementary metal-oxide semiconductor').gsub('-', ' ') %>
Returns: Complementary metaloxide semiconductor
You have a bug in previous = letters.index(letter) - 1
see if you can spot it:
arr = [:a, :b, :c, :a]
previous_indexes = arr.map { |n| arr.index(n) - 1 }
you_are_expecting = [-1, 0, 1, 2]
previous_indexes == you_are_expecting
# => false
arr.index(:a) # => 0
arr.index(:b) # => 1
arr.index(:c) # => 2
arr.index(:a) # => 0
To get indexes with iterating, use with_index:
arr = %i[a b c a]
arr.map.with_index { |x, i| [x, i] }
# => [[:a, 0], [:b, 1], [:c, 2], [:a, 3]]
If you make that fix, your code does what you intended.
A suggestion though: you can often avoid dealing with the details of array indexes. Take a look at how #Mori's answer works by operating at a higher level.
Related
There is a method that accepts a string as an argument. The string will be formatted so every letter is followed by a number. The method should return an "uncompressed" version of the string where every letter is repeated multiple times given based on the number that appears directly after the letter.
def uncompress(str)
new_str = ""
(0 ... str.length - 1).each do |i|
new_str += str[i] * str[i + 1].to_i
end
new_str
end
Result:
uncompress('a2b4c1') # 'aabbbbc'
Why this code does not return an error if str[i] is a number and str[i+ 1] is a letter?
As you're doing substitution in a string, let's use #gsub and toss a block to it.
irb(main):028:0> "h5a2".gsub(/[a-zA-Z]\d+/) { |x| x[0] * x[1..-1].to_i }
=> "hhhhhaa"
The regex is looking for a single character followed by a (potentially) multiple digit number. It then substitutes that with the character multiplied by the number. (Converting to an int with #to_i is critical as attempting to multiply a string by a string will not work.)
Or if you wish to modify the original string, you can use #gsub!.
irb(main):032:0> s = 'a2b4c1'
irb(main):033:0> s.gsub!(/[a-zA-Z]\d+/) { |x| x[0] * x[1..-1].to_i }
=> "aabbbbc"
irb(main):034:0> s
=> "aabbbbc"
If the format is fixed to a single character followed by a single digit multiplier, then this is easily accommodated by a minor modification to the regex without any change to the block.
irb(main):040:0> s = 'a2b4c156'
irb(main):041:0> s.gsub(/[a-zA-Z0-9]\d/) { |x| x[0] * x[1..-1].to_i }
=> "aabbbbc555555"
Using your approach, it's important that we increment the indices up by two, so let's generate an array of indexes.
irb(main):032:0> str = "h5d3"
irb(main):033:0> (0 ... str.length).step(2).to_a
=> [0, 2]
Then we can loop over those:
def uncompress(str)
new_str = ""
(0 ... str.length).step(2) do |i|
new_str += str[i] * str[i + 1].to_i
end
new_str
end
And now:
irb(main):042:0> uncompress("a4d2b1")
=> "aaaaddb"
Scanning, Splitting, and Integer Coercion
Rather than trying to debug your code, I'll just offer what I consider a simpler and more idiomatic solution:
str = "a2b4c1"
str.scan(/\p{Alpha}\d/).map(&:chars).map { _1 * _2.to_i }
#=> ["aa", "bbbb", "c"]
By scanning for your letter + number pattern and then splitting the matched patterns into an array of characters, you create a collection of array elements that are easy to iterate over. You can easily see the interim steps here by putting a comment character after any part of the method chain to inspect what's being returned.
Lastly, you just need to ensure that the second element of each character array is coerced from a string to an integer, as otherwise you are trying to multiply strings with other strings. For example "a" * "2" is not going to accomplish what you want, while "a" * 2 will.
In Ruby You could easily do it like I have shown below. We don't have to write code like C or other language.
Input
a="a2b4c1"
Program
p a.chars
.slice_after(/\d+/)
.map{|x|x.last.to_i.times.map{x.first}.join}
.join
Output
"aabbbbc"
I'm trying to do something like a string analyzer and I need to retrieve the ending of a word and compare it with the keys of an hash
word = "Test"
ending_hash = {"e" => 1, "st" => 2}
output = 2
I need the output to be 2 in this case, but actually I won't know if the length of the ending is of 1 or 2 characters. Is it possible to do?
Initially, assume that you know that word ends with (at least) one of the keys of ending_hash. You can then write:
word = "Test"
ending_hash = {"e" => 1, "st" => 2}
ending_hash.find { |k,v| word.end_with?(k) }.last
#=> 2
See Enumerable#find, String#end_with? and Array#last.
The intermediate calculation is as follows:
ending_hash.find { |k,v| word.end_with?(k) }
#=> ["st", 2]
If you are unsure if any of the keys may match the end of the string, write:
ending_hash = {"e" => 1, "f" => 2}
arr = ending_hash.find { |k,v| word.end_with?(k) }
#=> nil
arr.nil? ? nil : arr.last
#=> nil
or better:
ending_hash.find { |k,v| word.end_with?(k) }&.last
#=> nil
Here & is the Safe Navigation Operator. In a nutshell, if the expression preceding & returns nil, the SNO immediately returns nil for the entire expression, without executing last.
Even if word must end with one of the keys, you may want to write it this way so that you can check the return value and raise an exception if it is nil.
You could alternatively write:
ending_hash.find { |k,v| word.match? /#{k}\z/ }&.last
The regular expression reads, "match the value of k (#{k}) at the end of the string (the anchor \z)".
Note the following:
{"t"=>1, "st"=>2}.find { |k,v| word.end_with?(k) }&.last
#=> 1
{"st"=>1, "t"=>2}.find { |k,v| word.end_with?(k) }&.last
#=> 1
so the order of the keys may matter.
Lastly, as the block variable v is not used in the block calculation, the block variables would often be written |k,_| or |k,_v|, mainly to signal to the reader that only k is used in the block calculation.
If you know there will be only a small number of lengths of endings, it is much faster to check for all possible lengths, than to check for all endings. (It also makes sense to check them from longest to shortest, in case they overlap, otherwise the shorter will never be matched.)
The lazy one-liner:
(-2..-1).lazy.map { |cut| ending_hash[word[cut..]] }.find(&:itself)
The functional version:
(-2..-1).inject(nil) { |a, e| a || ending_hash[word[e..]] }
The blunt but moist version:
ending_hash[word[-2..]] || ending_hash[word[-1..]]
I have built a version of mastermind that checks a user's input and provides feedback based on how close the user's guess was to the winning sequence. If you're not familiar with the game, you get feedback indicating how many of your characters were guessed correctly at the same index and how many characters guessed are in the sequence, but at the wrong index. If there are duplicates in the guess, then you would not count the extra values unless they correspond to the same number of duplicates in the secret code.
Example: If the sequence is ["G","G","G","Y"] and the user guesses ["G", "Y","G","G"] then you'd want to return 2 for items at the same index and 2 for items at different indexes that are included in the secret sequence.
Another example: If the sequence is ["X","R","Y","T"] and the user guesses ["T","T","Y","Y"] then you'd return 1 for items at the same index 1 for the character guessed that is in the sequence but at the wrong index.
Anyway, to me this is not a simple problem to solve. Here's the code I used to get it to work, but it's not elegant. There must be a better way. I was hoping someone can tell me what I'm missing here?? New to Ruby...
def index_checker(input_array, sequence_array)
count = 0
leftover_input = []
leftover_sequence = []
input.each_with_index do |char, idx|
if char == sequence[idx]
count += 1
else
leftover_input << char
leftover_sequence << sequence[idx]
end
end
diff_index_checker(leftover_input, leftover_sequence, count)
end
def diff_index_checker(input, sequence, count)
count2 = 0
already_counted = []
input.each do |char|
if sequence.include?(char) && !already_counted.include?(char)
count2 += 1
already_counted << char
end
end
[count, count2]
end
Here's a clean Ruby solution, written in idiomatic Ruby object-oriented style:
class Mastermind
def initialize(input_array, sequence_array)
#input_array = input_array
#sequence_array = sequence_array
end
def matches
[index_matches, other_matches]
end
def results
[index_matches.size, other_matches.size]
end
private
attr_reader :input_array, :sequence_array
def index_matches
input_array.select.with_index { |e, i| e == sequence_array[i] }
end
def other_matches
non_exact_input & non_exact_sequence
end
def non_exact_input
array_difference(input_array, index_matches)
end
def non_exact_sequence
array_difference(sequence_array, index_matches)
end
# This method is based on https://stackoverflow.com/a/3852809/5961578
def array_difference(array_1, array_2)
counts = array_2.inject(Hash.new(0)) { |h, v| h[v] += 1; h }
array_1.reject { |e| counts[e] -= 1 unless counts[e].zero? }
end
end
You would use this class as follows:
>> input_array = ["G","G","G","Y"]
>> sequence_array = ["G", "Y","G","G"]
>> guess = Mastermind.new(input_array, sequence_array)
>> guess.results
#> [2, 2]
>> guess.matches
#> [["G", "G"], ["G", "Y"]]
Here's how it works. First everything goes into a class called Mastermind. We create a constructor for the class (which in Ruby is a method called initialize) and we have it accept two arguments: input array (the user guess), and sequence array (the answer).
We set each of these arguments to an instance variable, which is indicated by its beginning with #. Then we use attr_reader to create getter methods for #input_array and #sequence_array, which allows us to get the values by calling input_array and sequence_array from any instance method within the class.
We then define two public methods: matches (which returns an array of exact matches and an array of other matches (the ones that match but at the wrong index), and results (which returns a count of each of these two arrays).
Now, within the private portion of our class, we can define the guts of the logic. Each method has a specific job, and each is named to (hopefully) help a reader understand what it is doing.
index_matches returns a subset of the input_array whose elements match the sequence_array exactly.
other_matches returns a subset of the input_array whose elements do not match the sequence_array exactly, but do match at the wrong index.
other_matches relies on non_exact_input and non_exact_sequence, each of which is computed using the array_difference method, which I copied from another SO answer. (There is no convenient Ruby method that allows us to subtract one array from another without deleting duplicates).
Code
def matches(hidden, guess)
indices_wo_match = hidden.each_index.reject { |i| hidden[i] == guess[i] }
hidden_counts = counting_hash(hidden.values_at *indices_wo_match)
guess_counts = counting_hash(guess.values_at *indices_wo_match)
[hidden.size - indices_wo_match.size, guess_counts.reduce(0) { |tot, (k, cnt)|
tot + [hidden_counts[k], cnt].min }]
end
def counting_hash(arr)
arr.each_with_object(Hash.new(0)) { |s, h| h[s] += 1 }
end
Examples
matches ["G","G","G","Y"], ["G", "Y","G","G"]
#=> [2, 2]
matches ["X","R","Y","T"] , ["T","T","Y","Y"]
#=> [1, 1]
Explanation
The steps are as follows.
hidden = ["G","G","G","Y"]
guess = ["G", "Y","G","G"]
Save the indices i for which hidden[i] != guess[i].
indices_wo_match = hidden.each_index.reject { |i| hidden[i] == guess[i] }
#=> [1, 3]
Note that the number of indices for which the values are equal is as follows.
hidden.size - indices_wo_match.size
#=> 2
Now compute the numbers of remaining elements of guess that pair with one of the remaining values of hidden by having the same value. Begin by counting the numbers of instances of each unique element of hidden and then do the same for guess.
hidden_counts = counting_hash(hidden.values_at *indices_wo_match)
#=> {"G"=>1, "Y"=>1}
guess_counts = counting_hash(guess.values_at *indices_wo_match)
#=> {"Y"=>1, "G"=>1}
To understand how counting_hash 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(3), then if h does not have a key k, h[k] returns the default value, here 3 (the hash is not changed).
Now compute the numbers of matches of elements of guess that were not equal to the value of hidden at the same index and which pair with an element of hidden that have the same value.
val_matches = guess_counts.reduce(0) do |tot, (k, cnt)|
tot + [hidden_counts[k], cnt].min
end
#=> 2
Lastly, return the values of interest.
[hidden.size - indices_wo_match.size, val_matches]
#=> [2, 2]
In the code presented above I have substituted out the variable val_matches.
With Ruby 2.4+ one can use Enumerable#sum to replace
guess_counts.reduce(0) { |tot, (k, cnt)| tot + [hidden_counts[k], cnt].min }
with
guess_counts.sum { |k, cnt| [hidden_counts[k], cnt].min }
def judge(secret, guess)
full = secret.zip(guess).count { |s, g| s == g }
semi = secret.uniq.sum { |s| [secret.count(s), guess.count(s)].min } - full
[full, semi]
end
Demo:
> judge(["G","G","G","Y"], ["G","Y","G","G"])
=> [2, 2]
> judge(["X","R","Y","T"], ["T","T","Y","Y"])
=> [1, 1]
A shorter alternative, though I find it less clear:
full = secret.zip(guess).count(&:uniq!)
I prefer my other answer for its simplicity, but this one would be faster if someone wanted to use this for arrays larger than Mastermind's.
def judge(secret, guess)
full = secret.zip(guess).count { |s, g| s == g }
pool = secret.group_by(&:itself)
[full, guess.count { |g| pool[g]&.pop } - full]
end
Demo:
> judge(["G","G","G","Y"], ["G","Y","G","G"])
=> [2, 2]
> judge(["X","R","Y","T"], ["T","T","Y","Y"])
=> [1, 1]
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.
I wondering if there is a way to return the first letter of a word. like if you type in word("hey") it will return just the letter h. or if you wanted to you could return the letter e. individually by themselves. I was considering using the break method or scan but I can't seem to make them work.
another method you can look at is chr which returns the first character of a string
>> 'hey'.chr # 'h'
you can also look at http://www.ruby-doc.org/core-1.9.3/String.html#method-i-slice to see how you can combine regexp and indexes to get a part of a string.
UPDATE: If you are on ruby 1.8, it's a bit hackish but
>> 'hey'[0] # 104
>> 'hey'[0..0] # 'h'
>> 'hey'.slice(0,1) # 'h'
Yes:
def first_letter(word)
word[0]
end
Or, if using Ruby 1.8:
def first_letter(word)
word.chars[0]
end
Use the syntax str[index] to get a specific letter of a word (0 is first letter, 1 second, and so on).
This is a naive implementation, but you could use method_missing to create a DSL that'd allow you to query a word for letters at different positions:
def self.method_missing(method, *args)
number_dictionary = {
first: 1,
second: 2,
third: 3,
fourth: 4,
fifth: 5,
sixth: 6,
seventh: 7,
eighth: 8,
ninth: 9,
tenth: 10
}
if method.to_s =~ /(.+)_letter/ && number = number_dictionary[$1.to_sym]
puts args[0][number - 1]
else
super
end
end
first_letter('hey') # => 'h'
second_letter('hey') # => 'e'
third_letter('hey') # => 'y'
Using your example - the word "hey":
h = "hey"
puts h[0]
This should return h.