0

I have one hash

h1 = {"Cust1"=>500, "Cust4"=>400, "Cust2"=>100, "Cust3"=>100}

I want to insert this hash into a CSV file with the ranking of keys according to their value.

The sample output should look like this

ID,Sales,Rank Cust1,500,1 Cust4,400,2 Cust2,100,3 Cust3,100,3

I have to write the program in Ruby.

Sayandip Ghatak
  • 191
  • 2
  • 14
  • I suggest that you: 1) state whether `h1`'s key-value pairs are guaranteed to be ordered by decreasing value; 2) clarify that equal values are to have equal ranks (though it is implied by your "sample output"); and 3) display `"ID,Sales,Rank"`, `"Cust1,500,1"`, `"Cust4,400,2"`, `"Cust2,100,3"` and `"Cust3,100,3"` on separate lines if that is what the CSV file is to look like. – Cary Swoveland Oct 21 '20 at 19:41

3 Answers3

2

You can use each_with_index method of hash

arr = [['ID', 'Sales', 'Rank']]

h1.each_with_index do |(key, value), index|
  arr << [key, value, index + 1]
end

arr #[["ID", "Sales", "Rank"], ["Cust1", 500, 1], ["Cust4", 400, 2], ["Cust2", 100, 3], ["Cust3", 100, 4]]

Code for CSV

h1 = {"Cust1"=>500, "Cust4"=>400, "Cust2"=>100, "Cust3"=>100}
# You can skip following `sort_by` line if your hash is already ordered by desc value
h1 = h1.sort_by {|_k,v| v}.reverse.to_h
h2 = h1.group_by {|k,v| v }
require 'csv'
CSV.open("myfile.csv", "w") do |csv|
  csv << ['ID', 'Sales', 'Rank']
  h2.each_with_index do |(key, values), index|
    values.each do |value|
      csv << [value[0], key, index + 1]
    end
  end
end
Salil
  • 46,566
  • 21
  • 122
  • 156
  • With Index I can achieve this . But I am not able to calculate the Rank . If two of them have same value , then they should have same rank – Sayandip Ghatak Oct 21 '20 at 10:24
  • 1
    @SayandipGhatak:- Check my edited answer, if your hash is sorted according to the `sales` i.e. 500, 400 etc then you can use `h2 = h1.group_by {|k,v| v }` to group by the `sales` – Salil Oct 21 '20 at 10:35
  • @CarySwoveland:- Thanks for the comment, I have updated the answer with a comment. – Salil Oct 22 '20 at 07:56
  • You don't need `to_h`. If desired you could write `h1.sort_by(&:last).reverse`. – Cary Swoveland Oct 22 '20 at 15:51
  • Actually I wanted `h1` to be` hash` only, without `to_h` it will be `[["Cust1", 500], ["Cust4", 400], ["Cust3", 100], ["Cust2", 100]]` – Salil Oct 22 '20 at 16:01
0

The OP asks to calculate the rank; since 1.9 Ruby retains the insertion sequence of the hash, and in this instance, hash h1 is already in the correct order. But if the data came from a REST call or was not already sorted in ranked sequence the solution above would produce incorrect output. To fix this

require 'csv'

h1 = {"Cust1"=>500, "Cust2"=>100, "Cust4"=>400, "Cust3"=>100}

ranked_customers = h1.sort_by { |k, v| [-v, k] }.each_with_index { |e, i| e.push i + 1 }
ranked_customers.unshift %w[ID Sales Rank]

CSV { |csv_out| ranked_customers.each { |e| csv_out << e } }

produces

ID,Sales,Rank
Cust1,500,1
Cust4,400,2
Cust2,100,3
Cust3,100,4

Cust2 and Cust3 both have the same sales value, but Cust2 sorts ahead of Cust3 alphabetically.

nullTerminator
  • 396
  • 1
  • 6
0

I assume the key-value pairs of h1 are not necessarily in order of decreasing values.

We need to make two calculations to construct the CSV file.

order = h1.sort_by { |_,v| -v }
  #=> [["Cust1", 500], ["Cust4", 400], ["Cust2", 100], ["Cust3", 100]]
nbr_ranks = order.map(&:last).uniq.size
  #=> 3

The file can be constructed as follows.

require 'csv'

CSV.open('t.csv', 'w') do |csv|
  csv << ['ID', 'Sales', 'Rank']
  last_val = order.first.last
  order.each do |name, val|
    unless val == last_val
      nbr_ranks -= 1
      last_val = val
    end
    csv << [name, val, nbr_ranks]
  end
end
puts File.read('t.csv')
ID,Sales,Rank
Cust1,500,1
Cust4,400,2
Cust2,100,3
Cust3,100,4

If the key-value pairs of h1 are guaranteed to be in order of decreasing values (as in the example), there is no need to compute the array order.

nbr_ranks = order.map(&:last).uniq.size
  #=> 3

CSV.open('t.csv', 'w') do |csv|
  csv << ['ID', 'Sales', 'Rank']
  last_val = h1[h1.keys.first]
  h1.each do |name, val|
    unless val == last_val
      nbr_ranks -= 1
      last_val = val
    end
    csv << [name, val, nbr_ranks]
  end
end

Note:

last_val = h1[h1.keys.first]
  #=> 500
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100