how to store a Ruby array into a file? - ruby

How to store a Ruby array into a file?

I am not sure what exactly you want, but, to serialize an array, write it to a file and read back, you can use this:
fruits = %w{mango banana apple guava}
=> ["mango", "banana", "apple", "guava"]
serialized_array = Marshal.dump(fruits)
=> "\004\b[\t\"\nmango\"\vbanana\"\napple\"\nguava"
File.open('/tmp/fruits_file.txt', 'w') {|f| f.write(serialized_array) }
=> 33
# read the file back
fruits = Marshal.load File.read('/tmp/fruits_file.txt')
=> ["mango", "banana", "apple", "guava"]
There are other alternatives you can explore, like json and YAML.

To just dump the array to a file in the standard [a,b,c] format:
require 'pp'
$stdout = File.open('path/to/file.txt', 'w')
pp myArray
That might not be so helpful, perhaps you might want to read it back? In that case you could use json. Install using rubygems with gem install json.
require 'rubygems'
require 'json'
$stdout = File.open('path/to/file.txt', 'w')
puts myArray.to_json
Read it back:
require 'rubygems'
require 'json'
buffer = File.open('path/to/file.txt', 'r').read
myArray = JSON.parse(buffer)

There are multiple ways to dump an array to disk. You need to decide if you want to serialize in a binary format or in a text format.
For binary serialization you can look at Marshal
For text format you can use json, yaml, xml (with rexml, builder, ... ) , ...

Some standard options for serializing data in Ruby:
Marshal
YAML
JSON (built-in as of 1.9, various gems available as well)
(There are other, arguably better/faster implementations of YAML and JSON, but I'm linking to built-ins for a start.)
In practice, I seem to see YAML most often, but that may not be indicative of anything real.

Here's a quick yaml example
config = {"rank" => "Admiral", "name"=>"Akbar",
"wallet_value" => 9, "bills" => [5,1,1,2]}
open('store.yml', 'w') {|f| YAML.dump(config, f)}
loaded = open('store.yml') {|f| YAML.load(f) }
p loaded
# => {"name"=>"Akbar", "wallet_value"=>9, \
# "bills"=>[5, 1, 1, 2], "rank"=>"Admiral"}

Example: write text_area to a file where text_area is an array of strings.
File.open('output.txt', 'w') { |f| text_area.each { |line| f << line } }
Don't forget to do error checking on file operations :)

Afaik.. files contain lines not arrays. When you read the files, the data can then be stored in an array or other data structures. I am anxious to know if there is another way.

Related

How to save an array of objects into a file in ruby?

I have an array of objects, where the objects are instances of a class. I would like to save this array into a file in such format that I could read the file back to an array and the objects and its' instance variable values would be as they were before saving. Does someone know how this could be achieved?
The class instance objects that I would like to save to a file are fairly complex containing tens of instance variables that are often other class instance variables themselves.
WHAT I HAVE TRIED:
According to this post I tried the following:
TRIAL1:
Save file:
require 'pp'
$stdout = File.open('path/to/file.txt', 'w')
pp myArray
Load file:
require 'rubygems'
require 'json'
buffer = File.open('path/to/file.txt', 'r').read
myArray = JSON.parse(buffer)
but I got a JSON::ParserError
TRIAL2:
Save file
serialized_array = Marshal.dump(myArray)
File.open('./myArray.txt', 'w') {|f| f.write(serialized_array) }
received Encoding::UndefinedConversionError
TRIAL1 doesn't work because pp "prints arguments in pretty form" and that's not necessarily JSON.
TRIAL2 probably isn't working because Marshal produces binary data (not text) and you're not working with your file in binary mode, that could lead to encoding and EOL problems. Besides, Marshal isn't a great format for persistence since the format is tied to the version of Ruby you're using.
A modification of TRIAL1 to write JSON is probably the best solution these days:
require 'json'
File.open('path/to/file.json', 'w') { |f| JSON.dump(myArray, f) }
Finally managed to find a solution that worked!
dump = Marshal.dump(myArray)
File.write('./myarray', myArray, mode: 'r+b')
dump = File.read('./myarray')
user = Marshal.restore(dump)
Marshall was able to do the trick after changing the encoding to binary mode

How can I convert this CSV to JSON with Ruby?

I am trying to convert a CSV file to JSON using Ruby. I am very, very, green when it comes to working with Ruby (or any language for that matter) so the answers may need to be dumbed down for me. Putting it in JSON seems like the most reasonable solution to me because I understand how to work with JSON when assigning variables equal to the attributes that come in the response. If there is a better way to do it, feel free to teach me.
My CSV is in the following format:
Header1,Header,Header3
ValueX,ValueY,ValueZ
I would like to be able to use the data to say something along the lines of this:
For each ValueX in Row 1 after the headers, check if valueZ is > ValueY. If yes, do this, if no do that. I understand how to do the if statement, just now how to parse out my information into variables/arrays.
Any ideas here?
require 'csv'
require 'json'
rows = []
CSV.foreach('a.csv', headers: true, converters: :all) do |row|
rows << row.to_hash
end
puts rows.to_json
# => [{"Header1":"ValueX","Header":"ValueY","Header3":"ValueZ"}]
Here is a first pointer:
require 'csv'
data = CSV.read('your_file.csv', { :col_sep => ',' }
Now you should have the data in data; you can test in irb.
I don't entirely understand the question:
if z > y
# do this
else
# do that
end
For JSON, you should be able to do JSON.parse().
I am not sure what target format JSON requires, probably a Hash.
You can populate your hash with the dataset from the CVS:
hash = Hash.new
hash[key_goes_here] = value_here

Convert CSV file into array of hashes

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?
Just pass headers: true
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?
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) ] }
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.
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)
Slight variation on Nathan Long's answer
data_file = './sheet.csv'
data = CSV.foreach(data_file, headers: true).map(&:to_h)
Now data is an array of hashes to do your bidding with!
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.
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 })
I guess this is the shortest version:
keys = ["time", ...]
CSV.parse(content, headers: keys).map(&:to_h)
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)

Parsing a .cfg file using Ruby?

I have a .cfg file with the following data:
*.*.key_val = {
key1= "value1";
key2 = "value2";
key3 = "value3";
};
I want to read this file and store the key value pairs in a hash #var[key][val].
How it can be done?
THIS cfg you may parse in such way:
read file using File#read
convert text into 2-dimentional array using String#scan and regex
convert array into hash using Hash[]
text = File.read('your.cfg')
# => "*.*.key_val = {\n key1= \"value1\";\n key2 = \"value2\";\n key3 = \"value3\";\n};"
data = text.scan(/(\S+)\s*=\s*"([^"]+)/)
# => [["key1", "value1"], ["key2", "value2"], ["key3", "value3"]]
#var = Hash[data]
# => {"key1"=>"value1", "key2"=>"value2", "key3"=>"value3"}
Or just:
#var = Hash[File.read('your.cfg').scan(/(\S+)\s*=\s*"([^"]+)/)]
I'd strongly recommend transferring the configuration to something like YAML. It's made to be easy to understand, flexible, universally implemented, well documented, both as a standard and as a part of the core library, and easy to understand. (Yes, I said it twice on purpose.)
My YAML files load into Ruby as a Hash when I do something like:
require 'yaml'
config = YAML.load_file('/path/to/config/file')
I'll create the initial template for the configuration file in Ruby, as a Hash, then serialize it and write it to disk. That way I know what's on the disk is exactly the way YAML wants it to be and helps me avoid that "it won't load because either the data is wrong or the code is wrong" quandary.
# A simple round-trip (load and dump) of an object.
require 'yaml'
test_obj = {
'foo' => 'bar',
'one_two_three' => [1, 2, 3],
'hash' => {'another' => 'hash'}
} #=> {"foo"=>"bar", "one_two_three"=>[1, 2, 3], "hash"=>{"another"=>"hash"}}
File.open('./config.yaml', 'w') { |fo| fo.puts YAML::dump( test_obj ) } #=> nil
ruby_obj = YAML::load_file( './config.yaml' ) #=> {"foo"=>"bar", "one_two_three"=>[1, 2, 3], "hash"=>{"another"=>"hash"}}
ruby_obj == test_obj #=> true
require 'pp'
pp ruby_obj
{"foo"=>"bar", "one_two_three"=>[1, 2, 3], "hash"=>{"another"=>"hash"}}
pp test_obj
{"foo"=>"bar", "one_two_three"=>[1, 2, 3], "hash"=>{"another"=>"hash"}}
You should try out the 'parseconfig' gem: https://rubygems.org/gems/parseconfig/
gem install parseconfig
Here's a sample how to use this gem:
require 'rubygems'
require 'parseconfig'
my_config = ParseConfig.new('your_file.cfg')
puts my_config.get_value('key_val')
Good luck and have fun learning Ruby. :)
EDIT
As Glenux said this is only for simple configuration files. I'll check if I can find anything else.
EDIT 2
I can't find a gem or something to parse a cfg file like in your example. I guess your only option is to write a parser yourself (like Nakilon did) or use something like YAML instead. Good luck anyway. :)
The parseconfig class is only intended for simple configuration files !
It accepts files of the format "param = value" (cf http://www.5dollarwhitebox.org/drupal/projects#rb-parseconfig ) but it will not parse the *.*.key_val = { and } thing.
Is the configuration file yours or generated/used by a third-party software? If it is yours, it may be wiser to use an other configuration file format (JSON, Ini, YAML, etc).
Wanted to mention this ruby library that can help you transfer configuration between JSON, YAML or Windows Ini file formats:
https://github.com/kigster/dupervisor
The use case is to move the configuration to YAML, but be able to generate whatever the format is needed by the software – in the case of this gem – it's supervisord's INI file format.

how to convert the yaml to spreadsheet?

how can i use ruby to convert a yaml file and keep on the indent format over cells to spreadsheet file.
the yaml file like this:
https://github.com/rails/rails/blob/v2.3.10/activesupport/lib/active_support/locale/en.yml
You haven't clearly stated what you want this spreadsheet to look like so I can't be specific but you can use the YAML library to read the file into a data structure, then convert the data structure into one like a table (array of arrays of strings) then use the CSV library to output it to a file.
require 'yaml'
require 'csv'
yaml_txt = File.read 'input.yaml'
yaml_data = YAML.load yaml_txt
csv_table = [
[1,'hello world', true],
['a', 'b', 3.14159, 'c', 2, 3e8],
[nil, 'another row', 'bla']
]
#replace this^ with something that converts the yaml_data into a 2D array
File.open 'output.csv', 'w' do |f|
f.puts( csv_table.map do |row|
CSV.generate_line row
end.join "\n" )
end
The current example will produce:
1,hello world,true
a,b,3.14159,c,2,300000000.0
,another row,bla
in output.csv.
You can then open the CSV spreadsheet with the following options:
think, it's better to join rows with an empty string rather than "\n"

Resources