Ruby - Array of Hashes to CSV - arrays

I have collected some data and saved it to an array of hashes in the form of:
info = [
{'Name' => 'Alex', 'Tel' => 999, 'E-mail' => "bla#bla.com"},
{'Name' => 'Ali', 'Tel' => 995, 'E-mail' => "ali#bla.com"}
# ...
]
But not all information is always there:
{'Name' => 'GuyWithNoTelephone', 'E-mail' => "poor#bla.com"}
I want to turn this information into a CSV file. Here is what I tried:
def to_csv (info)
CSV.open("sm-data.csv", "wb") do |csv|
csv << ["Name", "Tel", "E-mail"]
info.each do |person|
csv << person.values
When I try this, the format of the table is not correct, and say, if a telephone number is missing, then the e-mail of that person appears at telephone column.
How can I selectively write this info to the CSV file, i.e how can I tell to skip columns if a value is missing?

But sadly when I try this, the format of the table is not correct, and say, if a telephone number is missing, then the e-mail of that person appears at telephone column.
That's because you are omitting the telephone number in that case, providing just 2 of the 3 values. To fix this, you simply have to provide all 3 values, even if they don't exist in the hash:
csv << ['Name', 'Tel', 'E-mail']
info.each do |person|
csv << [person['Name'], person['Tel'], person['E-Mail']]
end
or via Hash#values_at:
csv << ['Name', 'Tel', 'E-mail']
info.each do |person|
csv << person.values_at('Name', 'Tel', 'E-Mail')
end
For:
{'Name' => 'GuyWithNoTelephone', 'E-mail' => "poor#bla.com"}
this results in:
csv << ['GuyWithNoTelephone', nil, 'poor#bla.com']
which generates this output: (note the two commas, denoting an empty field in-between)
"GuyWithNoTelephone,,poor#bla.com\n"

Try this,
def to_csv(csv_filename="sm-data.csv")
# Get all unique keys into an array:
keys = info.map(&:keys).inject(&:|)
CSV.open(csv_filename, "wb") do |csv|
csv << keys
info.each do |hash|
# fetch values at keys location, inserting null if not found.
csv << hash.values_at(*keys)
end
end
end

Simple as this:
path = "data/backtest-results.csv"
CSV.open(path, "wb") do |csv|
csv << ["Asset", "Strategy", "Profit"]
result_array.each do |p|
csv << p.map { |key, value| value }
end
end
use "(File.file?(path) ? "ab" : "wb")" rather than "wb" if you want to continue adding as the new data comes

Related

putting a hash value into an array in ruby

I am trying to insert a list of account numbers into an array from an json return, I turned the json return into an hash, but I cannot for some reason insert the values into an array. I checked the hash locations on irb, and it gets the account number, for an example the location my_hash["aws_accounts"][0]["owner_id"] will get me the first account number and my_hash["aws_accounts"][0]["status"]["level"] will get me the status of the first account.
I essentially want to iterate through all the accounts and store the account number if its respective status color is "yellow".
HERE IS MY CODE:
require 'json'
require 'rest-client'
j = RestClient.get 'https://chapi.cloudhealthtech.com/v1/aws_accounts?api_key=###&page=1&per_page=100'
my_hash = JSON.parse(j)
accnt_size = my_hash["aws_accounts"].size
intaccntsize = accnt_size.to_i
account_number_array = Array.new
x = 0
for accnt_iteration in x..intaccntsize do
puts accnt_iteration
if my_hash["aws_accounts"][accnt_iteration]["status"]["level"] == "yellow"
account_number_array.push(my_hash["aws_accounts"][accnt_iteration]["owner_id"])
end
end
HERE IS THE ERROR MESSAGE
in `block in <main>': undefined method `[]' for nil:NilClass (NoMethodError)
from C:/Users/----/Desktop/-----/ruby_aws_sdk.rb:12:in `each'
from C:/Users/------/Desktop/-------/ruby_aws_sdk.rb:12:in `<main>'
any suggestions will help. thanks.
The actual solution to your problem is to use the 3 dot range instead of 2 dot (3 dots is end-exclusive and 2 dots is end-inclusive) as seen in the following (this uses the same my_hash as my second code block below):
for x in 0..my_hash["aws_accounts"].size do
puts x
end
# 0
# 1
# 2
=> 0..2
my_hash["aws_accounts"][2]
=> nil
for x in 0...my_hash["aws_accounts"].size do
puts x
end
# 0
# 1
=> 0...2
my_hash["aws_accounts"][1]
=> {"owner_id"=>2, "status"=>{"level"=>"orange"}}
Instead of getting the number of accounts and trying to access them through their index, I would just iterate over the accounts on their own. Here's a quick sample, with what I believe (based on your description) are the relevant pieces of my_hash and your expected output.
my_hash = {
"aws_accounts" => [
{ "owner_id" => 1, "status" => { "level" => "yellow" } },
{ "owner_id" => 2, "status" => { "level" => "orange" } }
]
}
account_number_array = []
my_hash["aws_accounts"].each do |account|
if account["status"]["level"] == "yellow"
account_number_array << account["owner_id"]
end
end
puts account_number_array.inspect
# => [1]

Ruby 2d array to csv?

There is an array, how correctly to deduce in csv a file?
arr1 = [["A","B"], ["C","D"], ["E","F"], ["G","H"]]
Expected result in csv:
A,B
C,D
E,F
G,H
I do so:
out_file = File.open('file.csv', 'w')
arr1.each_index do |inx|
arr1[inx].each do |val|
out_file.puts val
end
end
But, Prints all in one column:
A
B
C
D
..
If you output to the console through p val, then in each value is / r:
"A\r"
"B\r"
"C\r"
"D\r"
What do I do wrong?
Edit:
result csv Excel
result csv Vim
You are not writing to file.
require 'csv'
CSV.open('file.csv', 'w') do |csv|
arr1.each { |ar| csv << ar }
end
If all you want is to simply print out the CSV string, then you can do it like this:
csv_string = CSV.generate { |csv| array2d.each { |row| csv << row }
Here's a worked example:
> # array2d contains the raw data
> csv_string = CSV.generate { |csv| array2d.each { |row| csv << row } }
> puts csv_string
5014,"John O""Neill",4295,1,Finance Plus
314,"Thomas, Duncan",436,2,Finance Plus
1930,Fraser Smith,436,12,Finance Plus
5057,Fred McDonald,436,12,Finance Plus
Note that it handles double-quotes and commas inside strings.
See: https://ruby-doc.org/stdlib-2.6.1/libdoc/csv/rdoc/CSV.html
It works,
require 'csv'
CSV.open('file.csv', 'w') do |csv|
arr1.each { |ar| csv << ar }
end
But it was necessary to finish in front of it:
arr1.each_index do |inx|
arr1[inx].each do |val|
val.chop!
end
end
To delete a line break \r
and it works:
File.write('file.csv', [["A","B"], ["C","D"], ["E","F"], ["G","H"]].map { |e| e.join(",") }.join($/))

How to build an array comprised of two others using only particular elements of each?

I writing a little program to generate some bogus top-ten sales numbers for book sales. I'm trying to do this in as compact a fashion as possible and do it without using MySQL or another DB.
I have written out what I want to happen. I've created a bogus catalog array and a bogus sales array corresponding sales to the index of the catalog entries. That part all works great.
I want to create a third array that includes all the titles from the catalog array with the sales numbers from the sales array, like a join in a DB, but without any DB. I can't figure out how to do that part of it though. I think once I have it in there I can sort it the way I want it, but making that third array is killing. I cannot figure out what I'm doing wrong or how to do it right.
So given the following code:
require 'random_word'
class BestOnline
def initialize
#catalog = Array.new
#sales = Array.new
#topten = Array.new
inventory = rand(50) + 10
days = rand(1..50)
now = Time.now
yesterday = now - 86400
saleshistory = now - (days * 86400)
(1..inventory).each do
#catalog << {
:title => "#{RandomWord.adjs.next.capitalize} #{RandomWord.nouns.next.capitalize}",
:price => rand(5.99..29.99).round(2)}
end
(0..days).each do
#sales << {
:id => rand(0..#catalog.count),
:salescount => rand(0..24),
:date => rand(saleshistory..now) }
end
end
def bestsellers
#sales.each do
# THIS DOESNT WORK AND I'M STUCK AS HOW TO FIX IT.
# #topten << {
# :title => #catalog[:id],
# :salescount => #sales[:salescount]
# }
end
puts #topten.group_by{ |tt| tt[:salescount]}.sort_by{ |k,v| -k}.first(10)
end
end
BestOnline.new.bestsellers
How can I create a third array that contains the titles and number of sales and output the result of the top-ten books sold?
Try this out:
def bestsellers
#sales.each do |sale|
#topten << {
title: #catalog[sale[:id]][:title],
salescount: sale[:salescount] }
end
#topten.sort! { |x, y| y[:salescount] <=> x[:salescount] }
puts #topten.first(10)
end
I suggest you write:
def bestsellers(sales)
sales.max_by(10) { |h| h[:salescount][:salescount]] }
end
puts bestsellers(sales)
Enumerable#max_by was permitted to have an argument in Ruby v2.2.
There are several problems with the way you've structured your code. Now that you have running code (by incorporating #fbonds66's answer), I suggest you post it at SO's sister-site Code Review. The purpose of CR is to suggest improvements to working code. If you read through some of the questions and answers there I think you will be impressed.
I was doing the dereferencing wrong trying to build the 3rd array of the 1st two:
#sales.each do |sale|
#topten << {
:title => #catalog[sale[:id]][:title],
:salescount => sale[:salescount]
}
end
I needed to work on the hash returned from .each as |sale| and use correct syntax to get what I was after from the other arrays.

How do I convert this hash to an array of hashes?

I have this hash below called disciplines:
disciplines = {"Architecture"=>"architecture", "Auditing"=>"auditing", "Consulting"=>"consulting", "Delivery"=>"delivery", "Development"=>"development", "Engineering"=>"engineering", "Environment / IT"=>"environment", "Graphic Design"=>"graphic_design", "Management"=>"management", "Requirements"=>"requirements", "Research"=>"research", "Support"=>"support", "System Design"=>"system_design", "Test & Eval"=>"test_and_evaluation", "Writing"=>"writing"}
And I want to convert it into an array of hashes that looks like this:
[{"name"=>"Architecture", "value"=>"architecture"}, {"name"=>"Auditing", "value"=>"auditing"}, {"name"=>"Consulting", "value"=>"consulting"}, {"name"=>"Delivery", "value"=>"delivery"}, {"name"=>"Development", "value"=>"development"}, {"name"=>"Engineering", "value"=>"engineering"}, {"name"=>"Environment / IT", "value"=>"environment"}, {"name"=>"Graphic Design", "value"=>"graphic_design"}, {"name"=>"Management", "value"=>"management"}, {"name"=>"Requirements", "value"=>"requirements"}, {"name"=>"Research", "value"=>"research"}, {"name"=>"Support", "value"=>"support"}, {"name"=>"System Design", "value"=>"system_design"}, {"name"=>"Test & Eval", "value"=>"test_and_evaluation"}, {"name"=>"Writing", "value"=>"writing"}]
So I just want to take each key-value pair in the first hash and map it to a new hash where the key is now a value for name and the value is now a value for value and put them all in an array of hashes
You can simply do:
disciplines.map{ |k, v| { 'name' => k, 'value' => v } }
to achieve that.
Here's a demo: http://ideone.com/DBU3Ck
You can also do in this way:
array_of_hashes = disciplines.keys.inject([]) do |arr_of_hsh, item|
arr_of_hsh << ({name: item.downcase,value: item.capitalize})
end
Output will be look like this:
# array_of_hashes => [{:name=>"architecture", :value=>"Architecture"}, {:name=>"auditing", :value=>"Auditing"}, {:name=>"consulting", :value=>"Consulting"}, {:name=>"delivery", :value=>"Delivery"}, {:name=>"development", :value=>"Development"}, {:name=>"engineering", :value=>"Engineering"}, {:name=>"environment / it", :value=>"Environment / it"}, {:name=>"graphic design", :value=>"Graphic design"}, {:name=>"management", :value=>"Management"}, {:name=>"requirements", :value=>"Requirements"}, {:name=>"research", :value=>"Research"}, {:name=>"support", :value=>"Support"}, {:name=>"system design", :value=>"System design"}, {:name=>"test & eval", :value=>"Test & eval"}, {:name=>"writing", :value=>"Writing"}]

ruby, tk lib. Output from getopenfile

I am trying to load multiple files via ruby/tk lib and put them into array:
def openFiles
return Tk.getOpenFile( 'title' => 'Select Files',
'multiple' => true,
'defaultextension' => 'csv',
'filetypes' => "{{Comma Seperated Values} {.csv}} {TXT {.txt}} {All files {.*}}")
end
and then in code
filess = TkVariable.new()
button1 = TkButton.new(root){
text 'Open Files'
command (proc {filess.value = openFiles; puts filess; puts filess.class; puts filess.inspect})
}.grid(:column => 1, :row => 1, :sticky => 'we')
The problem is that I can not manage to get the output as array and I do not know if it is possible or I will have to somehow parse the output. Hm? Please help. Thank you.
this is the output, when I click on the button:
C:\file1
C:\file2
TkVariable
#<TkVariable: v00000>
I think it should be: (for the array part)
['C:\file1','C:\file2']
TkVariable implements #to_a, which you can use to convert its value into the Array you want.
button1 = TkButton.new(root) {
text 'Open Files'
command (proc do
filess.value = openFiles
puts filess.to_a.class
puts filess.to_a.inspect
end)
}.grid(:column => 1, :row => 1, :sticky => 'we')
Array
["C:\file1", "C:\file2"]
This worked for me using Ruby 2.2.5 (with Tk 8.5.12) on Windows 7:
require 'tk'
def extract_filenames_as_ruby_array(file_list_string)
::TkVariable.new(file_list_string).list
end
def files_open
descriptions = %w[
Comma\ Separated\ Values
Text\ Files
All\ Files
]
extensions = %w[ {.csv} {.txt} * ]
types = descriptions.zip(extensions).map {|d,e| "{#{d}} #{e}" }
file_list_string = ::Tk.getOpenFile \
filetypes: types,
multiple: true,
title: 'Select Files'
extract_filenames_as_ruby_array file_list_string
end
def lambda_files_open
#lambda_files_open ||= ::Kernel.lambda do
files = files_open
puts files
end
end
def main
b_button_1
::Tk.mainloop
end
# Tk objects:
def b_button_1
#b_button_1 ||= begin
b = ::Tk::Tile::Button.new root
b.command lambda_files_open
b.text 'Open Files'
b.grid column: 1, row: 1, sticky: :we
end
end
def root
#root ||= ::TkRoot.new
end
main
For reference, Tk.getOpenFile is explained in the Tk Commands and Ruby documentation.

Resources