Convert CSV file into array of hashes
RubyCsvMultidimensional ArrayRuby Problem Overview
I have a csv file, some hockey stats, for example:
09.09.2008,1,HC Vitkovice Steel,BK Mlada Boleslav,1:0 (PP)
09.09.2008,1,HC Lasselsberger Plzen,RI OKNA ZLIN,6:2
09.09.2008,1,HC Litvinov,HC Sparta Praha,3:5
I want to save them in an array of hashes. I don't have any headers and I would like to add keys to each value like "time" => "09.09.2008"
and so on. Each line should by accessible like arr[i]
, each value by for example arr[i]["time"]
. I prefer CSV
class rather than FasterCSV
or split
. Can you show the way or redirect to some thread where a similar problem was solved?
Ruby Solutions
Solution 1 - Ruby
headers: true
Just pass CSV.foreach(data_file, headers: true) do |row|
puts row.inspect # hash
end
From there, you can manipulate the hash however you like.
(Tested with Ruby 2.0, but I think this has worked for quite a while.)
Edit
You say you don't have any headers - could you add a header line to the beginning of the file contents after reading them?
Solution 2 - Ruby
You can use the Ruby CSV parser to parse it, and then use Hash[ keys.zip(values) ]
to make it a hash.
Example:
test = '''
09.09.2008,1,HC Vitkovice Steel,BK Mlada Boleslav,1:0 (PP)
09.09.2008,1,HC Lasselsberger Plzen,RI OKNA ZLIN,6:2
09.09.2008,1,HC Litvinov,HC Sparta Praha,3:5
'''.strip
keys = ['time', etc... ]
CSV.parse(test).map {|a| Hash[ keys.zip(a) ] }
Solution 3 - Ruby
This is a fantastic post by Josh Nichols which explains how to do what you're asking.
To summarize, here his code:
csv = CSV.new(body, :headers => true, :header_converters => :symbol, :converters => [:all, :blank_to_nil])
csv.to_a.map {|row| row.to_hash }
=> [{:year=>1997, :make=>"Ford", :model=>"E350", :description=>"ac, abs, moon", :price=>3000.0}, {:year=>1999, :make=>"Chevy", :model=>"Venture \"Extended Edition\"", :description=>nil, :price=>4900.0}, {:year=>1999, :make=>"Chevy", :model=>"Venture \"Extended Edition, Very Large\"", :description=>nil, :price=>5000.0}, {:year=>1996, :make=>"Jeep", :model=>"Grand Cherokee", :description=>"MUST SELL!\nair, moon roof, loaded", :price=>4799.0}]
So, you could save the body of your CSV file into a string called body
.
body = "09.09.2008,1,HC Vitkovice Steel,BK Mlada Boleslav,1:0 (PP)
09.09.2008,1,HC Lasselsberger Plzen,RI OKNA ZLIN,6:2
09.09.2008,1,HC Litvinov,HC Sparta Praha,3:5"
And then run his code as listed above on it.
Solution 4 - Ruby
A little shorter solution
Parse string:
CSV.parse(content, headers: :first_row).map(&:to_h)
Parse file:
CSV.open(filename, headers: :first_row).map(&:to_h)
Solution 5 - Ruby
Slight variation on Nathan Long's answer
data_file = './sheet.csv'
data = []
CSV.foreach(data_file, headers: true) do |row|
data << row.to_hash
end
Now data
is an array of hashes to do your bidding with!
Solution 6 - Ruby
The headers
option to the CSV module accepts an array of strings to be used as the headers, when they're not present as the first row in the CSV content.
CSV.parse(content, headers: %w(time number team_1 team_2 score))
This will generate an enumerable of hashes using the given headers as keys.
Solution 7 - Ruby
You can try the following gem also
require 'csv_hasher'
arr_of_hashes = CSVHasher.hashify('/path/to/csv/file')
The keys of the returned hashes will be the header values of the csv file.
If you want to pass your own keys then
keys = [:key1, :key2, ... ]
arr_of_hashers = CSVHasher.hashify('/path/to/csv/file', { keys: keys })
Solution 8 - Ruby
I guess this is the shortest version:
keys = ["time", ...]
CSV.parse(content, headers: keys).map(&:to_h)
Solution 9 - Ruby
you could also use the SmarterCSV gem, which returns data from CSV files as Ruby hashes by default.
It has a lot of features, including processing the data in chunks, which is very benefitial for huge data files.
require 'smarter_csv'
options = {} # see GitHub README
data = SmarterCSV.process(your_file_name, options)