Output index of hash in array of hashes? - ruby

This might be a silly question, but I'm struggling with outputting the positions of an array of hashes I have.
If I have an array of hashes, we'll call some_array, that looks like this:
some_array =
[{:id=>7, :people=>["Bob B", "Jimmy J"], :product=>"product1"},
{:id=>2, :people=>["Sally S"], :product=>"product1"},
{:id=>5, :people=>["Hank H", "Stan C"], :product=>"product2"},
{:id=>3, :people=>["Sue T"], :product=>"product2"},
{:id=>4, :people=>["Anne W"], :product=>"product3"}]
I then iterate though some_array like so:
some_array.select{|p| p[:product] == "product2"]}.each do |product|
product[:people].join("<br>")
product[:product]
Which outputs like:
Hank K product 2
Stan C
Sue T product 2
How would I go about outputting the index/position of each hash in the array?
Would I be able to do something along the lines of:
some_array.select{|p| p[:product] == "product2"]}.each do |product|
product.index
product[:people].join("<br>")
product[:product]
And get:
2 Hank K product2
Stan C
3 Sue T product2
Thank you!

You can use each_with_index and format to your use case:
some_array.each_with_index do |product, index|
if product[:product] == "product2"
p index
p product
end
end

In Ruby, you can chain methods on Enumerable, which allows you to call with_index before you select to get the original index of the element:
some_array.each_with_index.select do |element, _|
element[:product] == "product2"
end.each do |product, index|
p [index, product[:people].join("<br />"), product[:product]]
end
# outputs:
# [2, "Hank H<br />Stan C", "product2"]
# [3, "Sue T", "product2"]
While you can call select.with_index, and it may be tempting to do so, the index won't carry over into the each, because select returns the elements that matched and doesn't care about the input. When you call each_with_index (or each.with_index), though, you now have a new Enumerable which is each element in your array with its index in that array, and select ends up returning these new array elements:
some_array.each.with_index.select { |element, _| element[:product] == "product2" }
# => [[{:id=>5, :people=>["Hank H", "Stan C"], :product=>"product2"}, 2],
[{:id=>3, :people=>["Sue T"], :product=>"product2"}, 3]]

fmt_str_first = "%-4d%-10s%10s"
fmt_str_rest = "#{' '*4}%-10s"
some_array.each_with_index do |h,i|
next unless h[:product] == "product2"
first, *rest = h[:people]
puts fmt_str_first % [i, first, "product2"]
rest.each { |name| puts fmt_str_rest % name }
puts
end
2 Hank H product2
Stan C
3 Sue T product2
See Kernel#sprintf. Note that %-10s in the format string means that the corresponding entry, a string (s), is to be left-adjusted (-) in a field of width 10. %10s would cause the entry to be right-adjusted.

you can just use each_with_index and skip the item you don't need
some_array.each_with_index do |product, index|
next if product[:product] != "product2"
index
product[:people].join("<br>")
product[:product]
end

Related

How to print all key value pairs in Ruby hash?

My apologies for the potentially stupid question, I'm an absolute beginner to Ruby and code in general.
I have set up a hash with some predetermined values. I want to ask the user for input, if that input matches an existing key, I want the corresponding value to be updated (+ 1, in this case). Then I want to print all the current up-to-date values.
hash = {"apple": 6, "banana": 2, "carrot": 3}
order = gets.chomp.downcase
hash.each do |key, value|
if key.to_s == order
value += 1
puts "Your order includes: #{value} #{key}."
end
end
My problem is that I only know how to print a single key value pair.
E.g. if the user inputs "apple", I'd like the output to say "Your order includes: 7 apple, 2 banana, 3 carrot."
hash = {apple: 6, banana: 2, carrot: 3}
order = gets.chomp.downcase.to_sym
hash[order] = hash.fetch(order, 0) + 1
puts "Your order includes: " + hash.map { |k, v| "#{v} #{k}" }.join(", ")
Some notes:
your hash initialization hash = {"apple": 6, "banana": 2, "carrot": 3}. the keys of your hash seem strings, but if you use that syntax with the colon, they become symbols. So, you have two choice. this syntax:
hash = {"apple" => 6, "banana" => 2, "carrot" => 3}
or you can use symbols as I did and convert the user input in a symbol
what's really cool about hash is that you don't need to iterate through the elements to find what you're looking for. There's a mapping between keys and values, so it's easy find and update a value
in the third row, I'm dealing with the fact that the key could not be in the hash, I used fetch to have 0 in that case. then, I increment and I assign back to that key
The question does not specify if you want to mutate the initial hash, so I suppose you do. Then the following will do.
hash = Hash.new(0).merge(apple: 6, banana: 2, carrot: 3)
hash[gets.chomp.downcase.to_sym] += 1
puts "Your order includes: " <<
hash.map { |k, v| [v, k].join(' ') }.join(', ')
or:
puts hash.reduce("Your order includes: ") { |acc, (k, v)|
acc << "#{v} #{k}, "
}[0..-3]
Consider to initialize the hash providing a default value (Hash#default)
basket = {'apple' => 6, 'banana' => 2, 'carrot' => 3}
basket.default = 0 # <-- set default 0 for start counting new keys
Define a method to present the data:
def show(basket)
puts "Your order includes:"
basket.each{ |key, value| puts "#{value}: #{key}" }
end
Capture user input in a loop (explanation in comments):
loop do
puts "Place your order:"
order = gets.downcase.chomp # <-- format the input
break if order == '0' # <-- breaks the input loop if this contition is matched
next unless basket.has_key? order # <-- skip to next loop no matches with hash keys or remove this line if you want to count also non initialised keys
basket[order] += 1 # <-- increment by one the key
show(basket) # <-- call the metod to show the basket
end
show(basket)

Some help on using index where an object appears multiple times in an array (Ruby)

I am learning how to use the .index function to show a string's position inside an array. It is pretty useful. However, I am wondering how I can use it (or some other method) to show ALL the places a string exists in an array?
So for example if I have the code:
array= ['cat', 'dog', 'eagle', 'moose', 'pets', 'animal', 'eagle', 'hawk']
puts 'At position ' + array.index('eagle').to_s + ' in the array is eagle!'
puts array.index('eagle')
each time, it tells me that the position is 2, even though it is 2 and also position 6 in the array. Is there a simple way to get back all the instances of a particular string within an array? Or would I need to write a loop to do it manually?
Another way:
array.each_index.select { |i| array[i] == value } #=> [2, 6]
I would get the array elements into pairs of [obj, index] using each_with_index, then select pairs where obj is 'eagle', and finally return all the index values (last in a pair, so use the last method):
array.each_with_index.select { |x| x[0] == 'eagle' }.map(&:last)
# => [2, 6]
As a convenience method in Array:
class Array
def indices_of(obj)
each_with_index.select { |x| x[0] == obj }.map(&:last)
end
end
['cat', 'dog', 'eagle', 'moose', 'pets', 'animal', 'eagle', 'hawk'].indices_of 'eagle'
# => [2, 6]
You can try this:
value = "eagle"
array.each_with_index { |item, index| puts index if item == value }
In your question you just seemed to want to print the index value. you can do what ever you want in this block.
value = "eagle"
array.each_with_index do |item, index|
if item == value
# do whatever you like
puts index
end
end

Counting the number of times a value is repeated in a Hash

I am pulling a hash from mashable.com, and I need to count instances of author names (author is the key, and the value is the author name). mashable's api
{
new: [
{
other_keys: 'other_values'...
author: 'Author's Name'
}
]
I want to iterate over the hash and pull out the author's name, and then count the amount of times it is repeated in the entire list from the mashable api.
Here is what I have; it turns the hash into an array, iterates over it, adding the count to each author name as the key, and then adds the number of repeats as the value.
This would be great, but I can't get it back into my original hash from mashable to add all of the other hash items I want to display.
all_authors = []
all_stories.each do |story|
authors = story['author']
all_authors << authors
end
counts = Hash.new(0)
all_authors.each do |name|
counts[name] += 1
end
counts.each do |key, val|
puts "#{key}: " "#{val}"
end
That does what it is supposed to, but I try to put it back into the original hash from mashable:
all_stories.each do |com|
plorf = com['comments_count'].to_i
if plorf < 1
all_stories.each do |story|
puts "Title:\n"
puts story['title']
puts "URL:\n"
puts story['short_url']
puts "Total Shares:\n"
puts story['shares']['total']
end
end
end
When I drop the code back in to that iteration, all it does is iterate of the initial has, and after each entry, I get a list of all authors and the number of stories they have written, instead of listing each author connected to the other information about each story and the number of stories they have written.
Any help is greatly appreciated.
Here's a simplified version:
h = { a: 1, b: 2, c: 1, d: 1 }
h.count { |_, v| v == 1 } #=> 3
h.values.count(1) #=> 3
Alternatively you can also group by key and then count:
h.group_by(&:last).map { |v, a| [v, a.count] }.to_h #=> {1=>3, 2=>1}
This groups the hash by its values, the counts the times elements in the array of key/value pairs. Here's a more explicit version:
grouped = h.group_by(&:last) #=> {1=>[[:a, 1], [:c, 1], [:d, 1]], 2=>[[:b, 2]]}
grouped.map { |v, a| [v, a.count] #=> [[1, 3], [2, 1]]
Then the final to_h turns the array of 2 element arrays into a hash.
#Michael Kohl it was a good answer, I think I was asking the question wrong. I wound up doing this:
author = story['author']
puts "Number of stories by #{story['author']}: #{author_count['author']}"
inside my "all_stories" loop...
yeah I am pretty sure I was trying to "re-inject" the values to the original hash, and that was way wrong...
Thanks so much for your help though

Why does this use of the Hash#each method work only when I remove the splat operator from the parameter?

I'm going through a problem on Ruby Monk, https://rubymonk.com/learning/books/1-ruby-primer/problems/155-restaurant#solution4804
Their solution is great; I like it and it's more compact than mine. Problem is for mine, I just don't understand why it only works when I remove the splat operator from the cost parameter orders. Even if I shouldn't be doing it this way, I'm struggling to figure out what's up. I know sometimes it's unnecessary to understand everything, and it's best to just move on.. but curious.
Here is mine:
class Restaurant
def initialize(menu)
#menu = menu
end
def cost(*orders)
total_cost = 0
orders.each do |item, number|
total_cost += #menu[item] * number
end
end
menu = {:rice => 3, :noodles => 2}
orders = {:rice => 1, :noodles => 1}
eat = Restaurant.new(menu)
puts eat.cost(orders)
Edit:
To include their suggested solution below
class Restaurant
def initialize(menu)
#menu = menu
end
def cost(*orders)
orders.inject(0) do |total_cost, order|
total_cost + order.keys.inject(0) {|cost, key| cost + #menu[key]*order[key] }
end
end
end
Edit:
To clear up and answer my own question in the comment
I tried these experiments and it shows inject "removing" the array brackets that splat "put on". Perhaps not the most proper way to think about it? It does help clear up my confusion.
order = { :rice => 1, :noodles => 1 }
menu = { :rice => 3, :noodles => 2 }
[order].inject(0) do |bla, blu|
p bla #=> 0
p blu #=> {:rice=>1, :noodles=>1}
p blu.keys #=> [:rice, :noodles]
end
When you write:
def cost(*orders)
end
then all the parameters passed to the cost method will be put into a single array named orders. These two are thus equivalent:
def cost(*orders)
p orders.class #=> Array
p orders #=> [1,2,3]
end
cost(1,2,3)
def cost(orders)
p orders.class #=> Array
p orders #=> [1,2,3]
end
cost( [1,2,3] ) # note the array literal brackets
In your case, when you remove the "splat" you are saying "set orders to reference whatever was passed in directly". In this case you're passing it a Hash, and when you iterate a hash you get key/value pairs for each entry. This is just what you want.
When you do have the splat, though, you're getting this:
def cost(*orders)
p orders.class #=> Array
p orders #=> [{:rice=>1, :noodles=>1}]
end
orders = {:rice=>1, :noodles=>1}
cost(orders)
So you're wrapping your hash in an array, and then iterating over the elements of the array. Thus, the first value passed to the block is the entire hash, and there is no second parameter.
def cost(*orders)
p orders.class #=> Array
p orders #=> [{:rice=>1, :noodles=>1}]
orders.each do |item,number|
p item #=> {:rice=>1, :noodles=>1}
p number #=> nil
end
end
orders = {:rice=>1, :noodles=>1}
cost(orders)
At this point you can't multiply anything by nil and so your code breaks.

Ruby code to iterate through two array simulataneoulsy

How can I iterate two array in ruby simultaneously , I don't want to use for loop.
for e.g this are my array=
array 1=["a","b","c","d"]
array 2=[1,2,3,4]
You can use zip function for example like this :
array1.zip(array2).each do |array1_var, array2_var|
## whatever you want to do with array_1var and array_2 var
end
You can use Array#zip (no need to use each because zip accept optional block):
array1 = ["a","b","c","d"]
array2 = [1,2,3,4]
array1.zip(array2) do |a, b|
p [a,b]
end
Or, Array#transpose:
[array1, array2].transpose.each do |a, b|
p [a,b]
end
You can zip them together and then iterate through the pairs using each.
array1.zip(array2).each do |pair|
p pair
end
if both arrays are of the same size, you can do:
array1=["a","b","c","d"]
array2=[1,2,3,4]
for i in 0..arr1.length do
//here you do what you want with array1[i] and array2[i]
end
Assuming both arrays are the same size, you can use each_with_index to iterate through them, using the index for the second array:
array1.each_with_index do |item1, index|
item2 = array2[index]
# do something with item1, item2
end
Like so:
irb(main):007:0> array1.each_with_index do |item1, index|
irb(main):008:1* item2 = array2[index]
irb(main):009:1> puts item1, item2
irb(main):010:1> end
a
1
b
2
c
3
d
4
When both of the array are of having same size,you could do as below :
array1=["a","b","c","d"]
array2=[1,2,3,4]
array2.each_index{|i| p "#{array2[i]},#{array1[i]} at location #{i}"}
# >> "1,a at location 0"
# >> "2,b at location 1"
# >> "3,c at location 2"
# >> "4,d at location 3"
And if there is a chance that one array is larger than other array,then larger_array#each_index has to be called.

Resources