I'm learning Ruby in my spare time and I have installed a Linux Mint 10 Virtual Machine on my laptop.
I've gotten most of the basic Ruby structures down and now I'd like to actually build something with it to test it out.
For my first project, I want to parse a CSV file and save the information into an array in my code.
Simple enough right?
After some Googling, I found this library that seems to be what I need to use.
https://github.com/fauna/ccsv
My question is, how do I use this? I'm coming from the C#/.Net world where I would download a .dll (more recently just use NuGet) and it would be referenced into the project.
What do I need to do in Ruby? Can anyone walk me through it? I'm brand new to this language so try not to assume anything, I just may not know it yet. Thanks for your time.
I'm afraid that, in ruby, that isn't much of a project.
Suppose you have a file 'test.csv' with this content
letters,numbers
a,3
b,2
d,4
You can parse it like this:
require 'csv'
data = CSV.read('test.csv')
p data
#=> [["letters", "numbers"], ["a", "3"], ["b", "2"], ["d", "4"]]
Somewhat more complicated:
data = CSV.read('test.csv',{:headers=>true})
puts data['numbers'][0] #=> 3
This {:headers=>true} looks somewhat like a block, but it is a hash. CSV takes all kinds of parameters in a hash, a common pattern.
Ruby has a default csv library, and I use a function I found at http://snippets.dzone.com/posts/show/3899 to parse the csv.
require 'csv'
def parse(file)
csv_data = CSV.read file
headers = csv_data.shift.map {|i| i.to_s }
string_data = csv_data.map {|row| row.map {|cell| cell.to_s } }
string_data.map {|row| Hash[*headers.zip(row).flatten] }
end
myHash = parse('myfile')
If you are doing ruby without OOP, the function definitions must go before the code that calls it.
To answer your initial question, you would do in the terminal:
gem install ccsv
Then in your code:
require 'ccsv'
Related
I want something that can parse a ruby file to give me the file positions of comments. Ranked by desirability:
Ideally, there would be some command-line arg I could pass to "ruby" since of course "ruby" would know. But there doesn't seem to be one for this?
Does anyone know if/where in "ruby" I could hook in and use its methods to know where the comments are?
Some well-known regular expression?
Thanks!
Found: https://github.com/ruby/ruby/tree/trunk/ext/ripper
Example:
require 'ripper'
require 'pp'
class CommentRipper < Ripper::SexpBuilder
def on_comment(token)
super.tap { |result| pp result }
end
end
contents = File.read("file.rb")
pp CommentRipper.new(contents).parse
Helped me understand Ripper better: http://svenfuchs.com/2009/7/5/using-ruby-1-9-ripper
In Ruby 2.0.0, I want to write an array to json:
require 'json'
File.open('test.json', 'w') do |f2|
f2.puts ["£2M worth of wine"].to_json
end
This gives writes a file looking like this:
["£2M worth of wine"]
Obviously, not what I am looking for. Is this a bug in to_json? How can I make it work?
You might want to force each element in the array to be encoded UTF-8 before calling to_json
e.g:
["£2M worth of wine"].map { |str| str.encode("utf-8") }.to_json
I create an empty hash chickens, and key-value pairs are then added over time.
chickens = {}
chickens.merge!("Davison"=>"plucky")
chickens.merge!("Mortimer"=>"sullen")
chickens.merge!("Chauncey"=>"forlorn")
for name,mood in chickens do puts "#{name}: #{mood}" end
produces
Mortimer: sullen
Chauncey: forlorn
Davison: plucky
but I don't desire this. How do I cycle through the chickens in the order they were added?
The short answer is: update your Ruby. Ruby 1.9 is years old and works the way you want (as does current Ruby, 2.0). If you insist on ancient, unsupported Ruby, there's an OrderedHash class available via a gem:
gem install orderedhash
Your code would then become:
require 'rubygems'
require 'orderedhash'
chickens = OrderedHash.new
# The rest stays the same
Again, I recommend instead just upgrading your Ruby installation.
P.S. Your code would be much more Ruby-like if you iterate over the Hash like this:
chickens.each do |name, mood|
puts "#{name}: #{mood}"
end
#DarshanComputing began talking about making the code more Ruby-like, but here's how I would do the sample code in its entirety:
chickens = {}
chickens["Davison"] = "plucky"
chickens["Mortimer"] = "sullen"
chickens["Chauncey"] = "forlorn"
chickens.each do |name, mood|
puts "#{name}: #{mood}"
end
Using merge is fine, but it's also verbose. You'll see a lot of Ruby programmers take the more simple path and add the key/value pair directly, rather than rely on a mutating method.
Using for to loop over something is definitely not Ruby-like. Though the language supports it, its use is eschewed by Ruby developers consistently. The reason is, for name, mood ... adds name and mood to the local variables, unlike each which confines them inside the do block. Littering the variable space with temporary variables is bad-form. For instance, after running the original code I can do:
[7] (pry) main: 0> name
"Chauncey"
[8] (pry) main: 0> mood
"forlorn"
I have this simple code to generate a lazy array:
lazy_arr = Enumerator.new { |y|
i = 1
loop {
y << i
i+=1
}
}
p lazy_arr.take(5)
In official Ruby 1.9.3, the output is [1,2,3,4,5], which is what I want.
But in Rubinius, it gives error and tells me cannot find Enumerator constant.
So I looked it up, and find Enumerator defined in Enumerable module instead of kernel, and when it is generated, it needs a few arguments in the brackets:
http://rubydoc.info/github/evanphx/rubinius/master/Enumerable/Enumerator
I tried to change Enumerator.new to Enumerable::Enumerator.new, or include Enumerable, neither works because it needs more arguments.
How can I do the example above in Rubinius? Is there any way around to make the code work in both official and Rubinius?
You're using Rubinius in 1.8 mode, which doesn't have Enumerator in the global namespace. Please use Rubinius in 1.9 mode and the example works fine then. You can use 1.9 by passing -X19 when starting Rubinius, or setting RBXOPT=-X19 for example.
It's also possible to make 1.9 mode the default with configure during compile time.
Sounds like a bug/missing class in Rubinius. Open up an issue on github and it will get added. Or dig in and send a pull request!
I'm wanting to use the &method(:method_name) idiom when there's more than one object required by method_name. Can I do this under Ruby 1.9?
For example, if I've got
def move_file(old_filename, new_filename)
STDERR.puts "Moving #{old_filename.inspect} to #{new_filename.inspect}"
# Implementation for careful moving goes here
end
old_filenames = ["foo.txt", "bar.txt", "hoge.ja.txt"]
new_filenames = ["foo_20110915.txt", "bar_20110915.txt", "hoge_20110915.ja.txt"]
the code
old_filenames.zip(new_filenames).each(&method(:move_file))
works under Ruby 1.8, but not under Ruby 1.9. Under Ruby 1.9, it's trying to do move_file(["foo.txt", "foo_20110915.txt"]) instead of move_file("foo.txt", "foo_20110915.txt").
How do I splattify it so it has the correct arity?
Workarounds I'm aware of:
Replace def move_file(old_filename, new_filename) with def move_file(*arguments)
Replace each(&method(:move_file)) with
each{|old_filename, new_filename| move_file(old_filename, new_filename)}
Instead
each{|old_filename, new_filename| move_file(old_filename, new_filename)}
you should be able to do
each{|pair| move_file(*pair)}
But I don't know how you'd pull off blockless variant (I needed it couple of times as well). I guess &-shorthand was made to make the syntax simpler, and is not meant to be clogged much (whether it will be passed an array as an array, or splatted, for example). :)
How do I splattify it so it has the correct arity?
I don't think there is a way to do this while being compatible to both Ruby versions. What you could do is wrap it into a lambda
move_from_to = Proc.new {|*both| move_files(*both) }
The thing is - block and proc arity is something that got addressed in Ruby 1.9 so there might be a difference in behavior there. Also see prc.lambda? here http://www.ruby-doc.org/core/classes/Proc.html for info on what it does to the arity.
This question is also related to what you want to do (the solution there is to resplat and unsplat manually): Inconsistency of arity between Hash.each and lambdas