Having trouble adding new elements to my hash (Ruby) - ruby

new to Ruby, new to coding in general...
I'm trying to add new elements into my hash, incrementing the value when necessary. So I used Hash.new(0) and I'm trying to add new values using the "+=" symbol, but when I do this I get an error message -
"/tmp/file.rb:6:in `+': String can't be coerced into Integer (TypeError)
from /tmp/file.rb:6:in `block in stockList'
from /tmp/file.rb:3:in `each'
from /tmp/file.rb:3:in `each_with_index'
from /tmp/file.rb:3:in `stockList'
from /tmp/file.rb:24:in `<main>'
"
Here's my code:
def stockList(stock, cat)
hash = Hash.new(0)
stock.each_with_index do |word, i|
if cat.include?(word[i])
char = word[i]
hash[char] += num(word)
end
end
new_arr = []
hash.each do |k, v|
new_arr.push(k,v)
end
return new_arr
end
def num(word)
nums = "1234567890"
word.each_char.with_index do |char, i|
if nums.include?(char)
return word[i..-1]
end
end
end
puts stockList(["ABAR 200", "CDXE 500", "BKWR 250", "BTSQ 890", "DRTY 600"], ["A", "B"])
Does anyone know why this is happening?
It's a codewars challenge -- I'm basically given two arrays and am meant to return a string that adds the numbers associated with the word that starts with the letter(s) listed in the second array.
For this input I'm meant to return " (A : 200) - (B : 1140) "

Your immediate problem is that num(word) returns a string, and a string can't be added to a number in the line hash[char] += num(word). You can convert the string representation of a numeric value using .to_i or .to_f, as appropriate for the problem.
For the overall problem I think you've added too much complexity. The structure of the problem is:
Create a storage object to tally up the results.
For each string containing a stock and its associated numeric value (price? quantity?), split the string into its two tokens.
If the first character of the stock name is one of the target values,
update the corresponding tally. This will require conversion from string to integer.
Return the final tallies.
One minor improvement is to use a Set for the target values. That reduces the work for checking inclusion from O(number of targets) to O(1). With only two targets, the improvement is negligible, but would be useful if the list of stocks and targets increase beyond small test-case problems.
I've done some renaming to hopefully make things clearer by being more descriptive. Without further ado, here it is in Ruby:
require 'set'
def get_tallies(stocks, prefixes)
targets = Set.new(prefixes) # to speed up .include? check below
tally = Hash.new(0)
stocks.each do |line|
name, amount = line.split(/ +/) # one or more spaces is token delimiter
tally[name[0]] += amount.to_i if targets.include?(name[0]) # note conversion to int
end
tally
end
stock_list = ["ABAR 200", "CDXE 500", "BKWR 250", "BTSQ 890", "DRTY 600"]
prefixes = ["A", "B"]
p get_tallies(stock_list, prefixes)
which prints
{"A"=>200, "B"=>1140}
but that can be formatted however you like.

The particular issue triggering this error is that your def num(word) is essentially a no-op, returning the word without any change.
But you actually don't need this function: this...
word.delete('^0-9').to_i
... gives you back the word with all non-digit characters stripped, cast to integer.
Note that without to_i you'll still receive the "String can't be coerced into Integer" error: Ruby is not as forgiving as JavaScript, and tries to protect you from results that might surprise you.
It's a codewars challenge -- I'm basically given two arrays and am
meant to return a string that adds the numbers associated with the
word that starts with the letter(s) listed in the second array.
For this input I'm meant to return " (A : 200) - (B : 1140) "
This is one way to get there:
def stockList(stock, cat)
hash = Hash.new(0)
stock.each do |word|
letter = word[0]
if cat.include?(letter)
hash[letter] += word.delete('^0-9').to_i
end
end
hash.map { |k, v| "#{k}: #{v}" }
end
Besides type casting, there's another difference here: always choosing the initial letter of the word. With your code...
stock.each_with_index do |word, i|
if cat.include?(word[i])
char = word[i]
... you actually took the 1st letter of the 1st ticker, the 2nd letter of the 2nd ticker and so on. Don't use indexes unless your results depend on them.

stock = ["ABAR 200", "CDXE 500", "BKWR 250", "BTSQ 890", "DRTY 600"]
cat = ["A", "B"]
I concur with your decision to create a hash h with the form of Hash::new that takes an argument (the "default value") which h[k] returns when h does not have a key k. As a first step we can write:
h = stock.each_with_object(Hash.new(0)) { |s,h| h[s[0]] += s[/\d+/].to_i }
#=> {"A"=>200, "C"=>500, "B"=>1140, "D"=>600}
Then Hash#slice can be used to extract the desired key-value pairs:
h = h.slice(*cat)
#=> {"A"=>200, "B"=>1140}
At this point you have all the information you need to display the result any way you like. For example,
" " << h.map { |k,v| "(#{k} : #{v})" }.join(" - ") << " "
#=> " (A : 200) - (B : 1140) "
If h before h.slice(*cat) is large relative to h.slice(*cat) you can reduce memory requirements and probably speed things somewhat by writing the following.
require 'set'
cat_set = cat.to_set
#=> #<Set: {"A", "B"}>
h = stock.each_with_object(Hash.new(0)) do |s,h|
h[s[0]] += s[/\d+/].to_i if cat_set.include?(s[0])
end
#=> {"A"=>200, "B"=>1140}

Related

How to find count matching characters at the same indes and at an unmatching index

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]

Check whether a string contains all the characters of another string in Ruby

Let's say I have a string, like string= "aasmflathesorcerersnstonedksaottersapldrrysaahf". If you haven't noticed, you can find the phrase "harry potter and the sorcerers stone" in there (minus the space).
I need to check whether string contains all the elements of the string.
string.include? ("sorcerer") #=> true
string.include? ("harrypotterandtheasorcerersstone") #=> false, even though it contains all the letters to spell harrypotterandthesorcerersstone
Include does not work on shuffled string.
How can I check if a string contains all the elements of another string?
Sets and array intersection don't account for repeated chars, but a histogram / frequency counter does:
require 'facets'
s1 = "aasmflathesorcerersnstonedksaottersapldrrysaahf"
s2 = "harrypotterandtheasorcerersstone"
freq1 = s1.chars.frequency
freq2 = s2.chars.frequency
freq2.all? { |char2, count2| freq1[char2] >= count2 }
#=> true
Write your own Array#frequency if you don't want to the facets dependency.
class Array
def frequency
Hash.new(0).tap { |counts| each { |v| counts[v] += 1 } }
end
end
I presume that if the string to be checked is "sorcerer", string must include, for example, three "r"'s. If so you could use the method Array#difference, which I've proposed be added to the Ruby core.
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
str = "aasmflathesorcerersnstonedksaottersapldrrysaahf"
target = "sorcerer"
target.chars.difference(str.chars).empty?
#=> true
target = "harrypotterandtheasorcerersstone"
target.chars.difference(str.chars).empty?
#=> true
If the characters of target must not only be in str, but must be in the same order, we could write:
target = "sorcerer"
r = Regexp.new "#{ target.chars.join "\.*" }"
#=> /s.*o.*r.*c.*e.*r.*e.*r/
str =~ r
#=> 2 (truthy)
(or !!(str =~ r) #=> true)
target = "harrypotterandtheasorcerersstone"
r = Regexp.new "#{ target.chars.join "\.*" }"
#=> /h.*a.*r.*r.*y* ... o.*n.*e/
str =~ r
#=> nil
A different albeit not necessarily better solution using sorted character arrays and sub-strings:
Given your two strings...
subject = "aasmflathesorcerersnstonedksaottersapldrrysaahf"
search = "harrypotterandthesorcerersstone"
You can sort your subject string using .chars.sort.join...
subject = subject.chars.sort.join # => "aaaaaaacddeeeeeffhhkllmnnoooprrrrrrssssssstttty"
And then produce a list of substrings to search for:
search = search.chars.group_by(&:itself).values.map(&:join)
# => ["hh", "aa", "rrrrrr", "y", "p", "ooo", "tttt", "eeeee", "nn", "d", "sss", "c"]
You could alternatively produce the same set of substrings using this method
search = search.chars.sort.join.scan(/((.)\2*)/).map(&:first)
And then simply check whether every search sub-string appears within the sorted subject string:
search.all? { |c| subject[c] }
Create a 2 dimensional array out of your string letter bank, to associate the count of letters to each letter.
Create a 2 dimensional array out of the harry potter string in the same way.
Loop through both and do comparisons.
I have no experience in Ruby but this is how I would start to tackle it in the language I know most, which is Java.

Ruby - Count each word repetition in a string

I'm trying to solve this exercise from Ruby Monk website, which says:
Try implementing a method called occurrences that accepts a string
argument and uses inject to build a Hash. The keys of this hash should
be unique words from that string. The value of those keys should be
the number of times this word appears in that string.
I've tried to do it like this:
def occurrences(str)
str.split.inject(Hash.new(0)) { |a, i| a[i] += 1 }
end
But I always get this error:
TypeError: no implicit conversion of String into Integer
Meanwhile, the solution for this one is quite the same (I think):
def occurrences(str)
str.scan(/\w+/).inject(Hash.new(0)) do |build, word|
build[word.downcase] +=1
build
end
end
Okay so your issue is that you are not returning the correct object from the block. (In your case a Hash)
#inject works like this
[a,b]
^ -> evaluate block
| |
-------return-------- V
In your solution this is what is happening
def occurrences(str)
str.split.inject(Hash.new(0)) { |a, i| a[i] += 1 }
end
#first pass a = Hash.new(0) and i = word
#a['word'] = 0 + 1
#=> 1
#second pass uses the result from the first as `a` so `a` is now an integer (1).
#So instead of calling Hash#[] it is actually calling FixNum#[]
#which requires an integer as this is a BitReference in FixNum.Thus the `TypeError`
Simple fix
def occurrences(str)
str.split.inject(Hash.new(0)) { |a, i| a[i] += 1; a }
end
#first pass a = Hash.new(0) and i = word
#a['word'] = 0 + 1; a
#=> {"word" => 1}
Now the block returns the Hash to be passed to a again. As you can see the solution returns the object build at the end of the block thus the solution works.

Unique frequency of occurence

For a project for class, we are supposed to take a published paper and create an algorithm to create a list of all words in the unit of text while excluding the stop words. I am trying to produce a list of all unique words (in the entire text) along with their frequency of occurrence. This is the algorithm that I created for one line of the text:
x = l[125] #Selecting specific line in the text
p = Array.new() # Assign new array to variable p
p = x.split # Split the array
for i in (0...p.length)
if(p[i] != "the" and p[i] != "to" and p[i] != "union" and p[i] != "political")
print p[i] + " "
end
end
puts
The output of this program is one sentence (from line 125) excluding the stop words. Should I use bubble sort? How would I modify it to sort strings of equal length (or is that irrelevant)?
I'd say you have a good start, considering you are new to Ruby. You asked if you should use a bubble sort. I guess you're thinking of grouping multiple occurrences of a word, then go through the array to count them. That would work, but there are a couple of other approaches that are easier and more 'Ruby-like'. (By that I mean they make use of powerful features of the language and at the same time are more natural.)
Let's focus on counting the unique words in a single line. Once you can do that, you should be able to easily generalize that for multiple lines.
First Method: Use a Hash
The first approach is to use a hash. h = {} creates a new empty one. The hash's keys will be words and its values will be the number of times each word is present in the line. For example, if the word "cat" appears 9 times, we will have h["cat"] = 9, just what you need. To construct this hash, we see if each word w in the line is already in hash. It is in the hash if
h[w] != nil
If it is, we increment the word count:
h[w] = h[w] + 1
or just
h[w] += 1
If it's not in the hash, we add the word to the hash like this:
h[w] = 1
That means we can do this:
if h[w]
h[w] += 1
else
h[w] = 1
end
Note that here if h[w] is the same as if h[w] != nil.
Actually, we can use a trick to make this even easier. If we create the hash like this:
h = Hash.new(0)
then any key we add without a value will be assigned a default value of zero. That way we don't have to check if the word is already in the hash; we simply write
h[w] += 1
If w is not in the hash, h[w] will add it and initialize it to 0, then += 1 will increment it to 1. Cool, eh?
Let's put all this together. Suppose
line = "the quick brown fox jumped over the lazy brown fox"
We convert this string to an array with the String#split method:
arr = line.split # => ["the", "quick", "brown", "fox", "jumped", \
"over", "the", "lazy", "brown", "fox"]
then
h = Hash.new(0)
arr.each {|w| h[w] += 1}
h # => {"the"=>2, "quick"=>1, "brown"=>2, "fox"=>2, "jumped"=>1, "over"=>1, "lazy"=>1}
We're done!
Second Method: use the Enumerable#group_by method
Whenever you want to group elements of an array, hash or other collection, the group_by method should come to mind.
To apply group_by to the quick, brown fox array, we provide a block that contains the grouping criterion, which in this case is simply the words themselves. This produces a hash:
g = arr.group_by {|e| e}
# => {"the"=>["the", "the"], "quick"=>["quick"], "brown"=>["brown", "brown"], \
# "fox"=>["fox", "fox"], "jumped"=>["jumped"], "over"=>["over"], "lazy"=>["lazy"]}
The next thing to do is convert the hash values to the number of occurrences of the word (e.g., convert ["the", "the"] to 2). To do this, we can create a new empty hash h, and add hash pairs to it:
h = {}
g.each {|k,v| h[k] = v.size}
h # => {"the"=>2, "quick"=>1, "brown"=>2, "fox"=>2, "jumped"=>1, "over"=>1, "lazy"=>1
One More Thing
You have this code snippet:
if(p[i] != "the" and p[i] != "to" and p[i] != "union" and p[i] != "political")
print p[i] + " "
end
Here are a couple of ways you could make this a little cleaner, both using the hash h above.
First Way
skip_words = %w[the to union political] # => ["the", "to", "union", "political"]
h.each {|k,v| (print v + ' ') unless skip_words.include?(k)}
Second Way
h.each |k,v|
case k
when "the", "to", "union", "political"
next
else
puts "The word '#{k}' appears #{v} times."
end
end
Edit to address your comment. Try this:
p = "The quick brown fox jumped over the quick grey fox".split
freqs = Hash.new(0)
p.each {|w| freqs[w] += 1}
sorted_freqs = freqs.sort_by {|k,v| -v}
sorted_freqs.each {|word, freq| puts word+' '+freq.to_s}
=>
quick 2
fox 2
jumped 1
The 1
brown 1
over 1
the 1
grey 1
Normally, ypu would not sort a hash; rather you'd first convert it to an array:
sorted_freqs = freqs.to_a.sort_by {|x,y| v}.reverse
or
sorted_freqs = freqs.to_a.sort_by {|x,y| -v}
Now sorted_freqs is an array, rather than a hash. The last line stays the same. In general, it's best not to rely on a hash's order. In fact, before Ruby version 1.9.2, hashes were not ordered. If order is important, use an array or convert a hash to array.
Having said that, you can sort smallest-to-largest on the hash values, or (as I have done), sort largest-to-smallest on the negative of the hash values. Note that there is no Enumerable#reverse or Hash#reverse. Alternatively (always many ways to skin a cat with Ruby), you could sort on v and then use Enumerable#reverse_each:
sorted_freqs.reverse_each {|word, freq| puts word+' '+freq.to_s}
Lastly, you could eliminate the temporary variable sorted_freqs (needed because there is no Enumerable#sort_by! method), by chaining the last two statements:
freqs.sort_by {|k,v| -v}.each {|word, freq| puts word+' '+freq.to_s}
You should really look into Ruby's enumerable class. you very seldom do for x in y in ruby.
word_list = ["the", "to", "union", "political"]
l[125].split.each do |word|
print word + " " unless word_list.include?(word)
end
In order to count, sort and all that stuff look into the group_by method and perhaps the sort_by method of arrays.

Ruby grocery list program

I am currently learning Ruby and I'm trying to write a simple Ruby grocery_list method. Here are the instructions:
We want to write a program to help keep track of a grocery list. It takes a grocery item (like "eggs") as an argument, and returns the grocery list (that is, the item names with the quantities of each item). If you pass the same argument twice, it should increment the quantity.
def grocery_list(item)
array = []
quantity = 1
array.each {|x| quantity += x }
array << "#{quantity}" + " #{item}"
end
puts grocery_list("eggs", "eggs")
so I'm trying to figure out here how to return "2 eggs" by passing eggs twice
To help you count the different items you can use as Hash. A Hash is similar to an Array, but with Strings instead of Integers als an Index:
a = Array.new
a[0] = "this"
a[1] = "that"
h = Hash.new
h["sonja"] = "asecret"
h["brad"] = "beer"
In this example the Hash might be used for storing passwords for users. But for your
example you need a hash for counting. Calling grocery_list("eggs", "beer", "milk", "eggs")
should lead to the following commands being executed:
h = Hash.new(0) # empty hash {} created, 0 will be default value
h["eggs"] += 1 # h is now {"eggs"=>1}
h["beer"] += 1 # {"eggs"=>1, "beer"=>1}
h["milk"] += 1 # {"eggs"=>1, "beer"=>1, "milk"=>1}
h["eggs"] += 1 # {"eggs"=>2, "beer"=>1, "milk"=>1}
You can work through all the keys and values of a Hash with the each-loop:
h.each{|key, value| .... }
and build up the string we need as a result, adding
the number of items if needed, and the name of the item.
Inside the loop we always add a comma and a blank at the end.
This is not needed for the last element, so after the
loop is done we are left with
"2 eggs, beer, milk, "
To get rid of the last comma and blank we can use chop!, which "chops off"
one character at the end of a string:
output.chop!.chop!
One more thing is needed to get the complete implementation of your grocery_list:
you specified that the function should be called like so:
puts grocery_list("eggs", "beer", "milk","eggs")
So the grocery_list function does not know how many arguments it's getting. We can handle
this by specifying one argument with a star in front, then this argument will
be an array containing all the arguments:
def grocery_list(*items)
# items is an array
end
So here it is: I did your homework for you and implemented grocery_list.
I hope you actually go to the trouble of understanding the implementation,
and don't just copy-and-paste it.
def grocery_list(*items)
hash = Hash.new(0)
items.each {|x| hash[x] += 1}
output = ""
hash.each do |item,number|
if number > 1 then
output += "#{number} "
end
output += "#{item}, "
end
output.chop!.chop!
return output
end
puts grocery_list("eggs", "beer", "milk","eggs")
# output: 2 eggs, beer, milk
def grocery_list(*item)
item.group_by{|i| i}
end
p grocery_list("eggs", "eggs","meat")
#=> {"eggs"=>["eggs", "eggs"], "meat"=>["meat"]}
def grocery_list(*item)
item.group_by{|i| i}.flat_map{|k,v| [k,v.length]}
end
p grocery_list("eggs", "eggs","meat")
#=>["eggs", 2, "meat", 1]
def grocery_list(*item)
Hash[*item.group_by{|i| i}.flat_map{|k,v| [k,v.length]}]
end
grocery_list("eggs", "eggs","meat")
#=> {"eggs"=>2, "meat"=>1}
grocery_list("eggs", "eggs","meat","apple","apple","apple")
#=> {"eggs"=>2, "meat"=>1, "apple"=>3}
or as #Lee said:
def grocery_list(*item)
item.each_with_object(Hash.new(0)) {|a, h| h[a] += 1 }
end
grocery_list("eggs", "eggs","meat","apple","apple","apple")
#=> {"eggs"=>2, "meat"=>1, "apple"=>3}
Use a Hash Instead of an Array
When you want an easy want to count things, you can use a hash key to hold the name of the thing you want to count, and the value of that key is the quantity. For example:
#!/usr/bin/env ruby
class GroceryList
attr_reader :list
def initialize
# Specify hash with default quantity of zero.
#list = Hash.new(0)
end
# Increment the quantity of each item in the #list, using the name of the item
# as a hash key.
def add_to_list(*items)
items.each { |item| #list[item] += 1 }
#list
end
end
if $0 == __FILE__
groceries = GroceryList.new
groceries.add_to_list('eggs', 'eggs')
puts 'Grocery list correctly contains 2 eggs.' if groceries.list['eggs'] == 2
end
Here's a more verbose, but perhaps more readable solutions to your challenge.
def grocery_list(*items) # Notice the asterisk in front of items. It means "put all the arguments into an array called items"
my_grocery_hash = {} # Creates an empty hash
items.each do |item| # Loops over the argument array and passes each argument into the loop as item.
if my_grocery_hash[item].nil? # Returns true of the item is not a present key in the hash...
my_grocery_hash[item] = 1 # Adds the key and sets the value to 1.
else
my_grocery_hash[item] = my_grocery_hash[item] + 1 # Increments the value by one.
end
end
my_grocery_hash # Returns a hash object with the grocery name as the key and the number of occurences as the value.
end
This will create an empty hash (called dictionaries or maps in other languages) where each grocery is added as a key with the value set to one. In case the same grocery appears multiple times as a parameter to your method, the value is incremented.
If you want to create a text string and return that instead of the hash object and you can do like this after the iteration:
grocery_list_string = "" # Creates an empty string
my_grocery_hash.each do |key, value| # Loops over the hash object and passes two local variables into the loop with the current entry. Key being the name of the grocery and value being the amount.
grocery_list_string << "#{value} units of #{key}\n" # Appends the grocery_list_string. Uses string interpolation, so #{value} becomes 3 and #{key} becomes eggs. The remaining \n is a newline character.
end
return grocery_list_string # Explicitly declares the return value. You can ommit return.
Updated answer to comment:
If you use the first method without adding the hash iteration you will get a hash object back which can be used to look up the amount like this.
my_hash_with_grocery_count = grocery_list("Lemonade", "Milk", "Eggs", "Lemonade", "Lemonade")
my_hash_with_grocery_count["Milk"]
--> 1
my_hash_with_grocery_count["Lemonade"]
--> 3
Enumerable#each_with_object can be useful for things like this:
def list_to_hash(*items)
items.each_with_object(Hash.new(0)) { |item, list| list[item] += 1 }
end
def hash_to_grocery_list_string(hash)
hash.each_with_object([]) do |(item, number), result|
result << (number > 1 ? "#{number} #{item}" : item)
end.join(', ')
end
def grocery_list(*items)
hash_to_grocery_list_string(list_to_hash(*items))
end
p grocery_list('eggs', 'eggs', 'bread', 'milk', 'eggs')
# => "3 eggs, bread, milk"
It iterates an array or hash to enable building another object in a convenient way. The list_to_hash method uses it to build a hash from the items array (the splat operator converts the method arguments to an array); the hash is created so that each value is initialized to 0. The hash_to_grocery_list_string method uses it to build an array of strings that is joined to a comma-separated string.

Resources