I have a list of items and a script which generates two lines of csv for each item.
May I add two lines at once to csv generator? I want something like this:
CSV.generate do |csv|
items.each do |item|
csv << rows(item)
end
end
def rows(item)
return \
['value1', 'value2', 'value2'],
['value3', 'value4', 'value5']
end
But csv << can't receive two lines at once.
Now my the best code is:
CSV.generate do |csv|
items.each do |item|
rows(item).each { |row| csv << row }
end
end
Update: Now the best code without adding two line at once looks like:
CSV.generate do |csv|
items.
flat_map(&method(:rows)).
each(&csv.method(:<<))
end
CSV.generate do |csv|
csv << items.flat_map(&method(:rows))
end
Array#push or Array#append work the same way, and can take multiple arguments. Edit: As it turns out, CSV.generate yields a CSV object which has neither of those methods.
You can also do it like this:
CSV.generate do |csv|
items.each do |item|
r = rows(item)
csv << r[0] << r[1]
end
end
Related
I know how to do it with CSV.read, but CSV.open and enumerator I'm not sure how. Or how do I omit those specific row before loading them in the new_csv[] ?
Thanks!
new_csv = []
CSV.open(file, headers:true) do |unit|
units = unit.each
units.select do |row|
#delete row [0][1][2][3]
new_csv << row
end
Code Example
If you want to skip the first four rows plus the header, this are some options.
Get pure array:
new_csv = CSV.read(filename)[5..]
or keep the csv object
new_csv = []
CSV.open(filename, headers:true) do |csv|
csv.each_with_index do |row, i|
new_csv << row if i > 3
end
end
or using Enumerable#each_with_object:
csv = CSV.open(filename, headers:true)
new_csv = csv.each_with_index.with_object([]) do |(row, i), ary|
ary << row if i > 3
end
Let's begin by creating a CSV file:
contents =<<~END
name,nickname,age
Robert,Bobbie,23
Wilma,Stretch,45
William,Billy-Bob,72
Henrietta,Mama,53
END
FName = 'x.csv'
File.write(FName, contents)
#=> 91
We can use CSV::foreach without a block to return an enumerator.
csv = CSV.foreach(FName, headers:true)
#=> #<Enumerator: CSV:foreach("x.csv", "r", headers: true)>
The enumerator csv generates CSV::ROW objects:
obj = csv.next
#=> #<CSV::Row "name":"Robert" "nickname":"Bobbie" "age":"23">
obj.class
#=> CSV::Row
Before continuing let me Enumerator#rewind csv so that csv.next will once again generate its first element.
csv.rewind
Suppose we wish to skip the first two records. We can do that using Enumerator#next:
2.times { csv.next }
Now continue generating elements with the enumerator, mapping them to an array of hashes:
loop.map { csv.next.to_h }
#=> [{"name"=>"William", "nickname"=>"Billy-Bob", "age"=>"72"},
# {"name"=>"Henrietta", "nickname"=>"Mama", "age"=>"53"}]
See Kernel#loop and CSV::Row#to_h. The enumerator csv raises a StopInteration exception when next invoked after the enumerator has generated its last element. As you see from its doc, loop handles that exception by breaking out of the loop.
loop is a very versatile method. I generally use it in place of while and until, as well as when I need it to handle a StopIteration exception.
If you just want the values, then:
csv.rewind
2.times { csv.next }
loop.with_object([]) { |_,arr| arr << csv.next.map(&:last) }
#=> [["William", "Billy-Bob", "72"],
# ["Henrietta", "Mama", "53"]]
I have created csv file with values.I am able to read rows but don't know how to access individual values of a column.
require "csv"
CSV.open("file.csv", "w")
do |csv|
csv << ["val1", "val2","mul"]
csv << ["53", "27"]
csv<<["32","20"]
end
You probably need to ignore the header row if you have one. But the general idea is this:
CSV.open('dest.csv', 'w') do |csv|
csv << ["val1", "val2","mul"]
CSV.foreach('source.csv') do |row|
c1 = row[0]
c2 = row[1]
csv << [c1, c2, c1*c2]
end
end
If you have headers, you could do this:
CSV.open('dest.csv', 'w') do |csv|
csv << ["val1", "val2", "mul"]
CSV.foreach('source.csv', headers: true) do |row|
c1 = row['val1']
c2 = row['val2']
csv << [c1, c2, c1*c2]
end
end
You can use the one below for a non-ruby solution too:
awk -F "," '{print $1,$2,$1*$2}' source.csv > dest.csv
Code:
doc = Nokogiri::HTML(html)
showings = []
doc.css('.ok-product').each do |showing|
showing_id = showing['data-cart-id'].to_i
price = showing.at_css('.ok-product__price-main').text.gsub(/[\u0440\u0443\u0431.]/, '').strip
showings.push(
id: showing_id,
price: price
)
end
CSV.open("file.csv", "wb") do |csv|
csv << showings
end
I get the data in csv in cell A1:
{:id=>26999, :price=>"395,00"},"{:id=>26963, :price=>""254,00""}"...
Need break the data into cells and remove unnecessary symbols.
CSV.open("file.csv", "wb") do |csv|
showings.each do |id_price|
csv << [id_price[:id], id_price[:price]]
end
end
I have a CSV named test.csv, whose headers look like this:
"Fruit","Weight","Color"
In my ruby script, I have an array that looks like this:
["Banana","Yellow"]
How do I use Ruby's csv class to put "Banana" in column "Fruit" and "Yellow" in column "Color"?
For your example:
CSV.open("path/to/file.csv", "wb") do |csv|
fruit, color = ["Banana","Yellow"]
csv << [fruit, nil, color]
end
And if you have multiple rows with the same structure:
CSV.open("path/to/file.csv", "wb") do |csv|
rows.each do |row|
# Assuming row looks like ["Banana", "Yellow"]
fruit, color = row
csv << [fruit, nil, color]
end
end
And if in your real life example your arrays have many more columns (not just two) and you want to add a few empty columns sporadically, you can use the Array#insert method:
CSV.open("path/to/file.csv", "wb") do |csv|
rows.each do |row|
# Run one line like this for every empty column you want to add
# Note that it changes the value of `row`, so if you'd like to
# keep it untouched, consider using `dup`
row.insert(1, nil)
csv << row
end
end
It's easy enough to read a CSV file into an array with Ruby but I can't find any good documentation on how to write an array into a CSV file. Can anyone tell me how to do this?
I'm using Ruby 1.9.2 if that matters.
To a file:
require 'csv'
CSV.open("myfile.csv", "w") do |csv|
csv << ["row", "of", "CSV", "data"]
csv << ["another", "row"]
# ...
end
To a string:
require 'csv'
csv_string = CSV.generate do |csv|
csv << ["row", "of", "CSV", "data"]
csv << ["another", "row"]
# ...
end
Here's the current documentation on CSV: http://ruby-doc.org/stdlib/libdoc/csv/rdoc/index.html
If you have an array of arrays of data:
rows = [["a1", "a2", "a3"],["b1", "b2", "b3", "b4"], ["c1", "c2", "c3"]]
Then you can write this to a file with the following, which I think is much simpler:
require "csv"
File.write("ss.csv", rows.map(&:to_csv).join)
I've got this down to just one line.
rows = [['a1', 'a2', 'a3'],['b1', 'b2', 'b3', 'b4'], ['c1', 'c2', 'c3'], ... ]
csv_str = rows.inject([]) { |csv, row| csv << CSV.generate_line(row) }.join("")
#=> "a1,a2,a3\nb1,b2,b3\nc1,c2,c3\n"
Do all of the above and save to a csv, in one line.
File.open("ss.csv", "w") {|f| f.write(rows.inject([]) { |csv, row| csv << CSV.generate_line(row) }.join(""))}
NOTE:
To convert an active record database to csv would be something like this I think
CSV.open(fn, 'w') do |csv|
csv << Model.column_names
Model.where(query).each do |m|
csv << m.attributes.values
end
end
Hmm #tamouse, that gist is somewhat confusing to me without reading the csv source, but generically, assuming each hash in your array has the same number of k/v pairs & that the keys are always the same, in the same order (i.e. if your data is structured), this should do the deed:
rowid = 0
CSV.open(fn, 'w') do |csv|
hsh_ary.each do |hsh|
rowid += 1
if rowid == 1
csv << hsh.keys# adding header row (column labels)
else
csv << hsh.values
end# of if/else inside hsh
end# of hsh's (rows)
end# of csv open
If your data isn't structured this obviously won't work
If anyone is interested, here are some one-liners (and a note on loss of type information in CSV):
require 'csv'
rows = [[1,2,3],[4,5]] # [[1, 2, 3], [4, 5]]
# To CSV string
csv = rows.map(&:to_csv).join # "1,2,3\n4,5\n"
# ... and back, as String[][]
rows2 = csv.split("\n").map(&:parse_csv) # [["1", "2", "3"], ["4", "5"]]
# File I/O:
filename = '/tmp/vsc.csv'
# Save to file -- answer to your question
IO.write(filename, rows.map(&:to_csv).join)
# Read from file
# rows3 = IO.read(filename).split("\n").map(&:parse_csv)
rows3 = CSV.read(filename)
rows3 == rows2 # true
rows3 == rows # false
Note: CSV loses all type information, you can use JSON to preserve basic type information, or go to verbose (but more easily human-editable) YAML to preserve all type information -- for example, if you need date type, which would become strings in CSV & JSON.
Building on #boulder_ruby's answer, this is what I'm looking for, assuming us_eco contains the CSV table as from my gist.
CSV.open('outfile.txt','wb', col_sep: "\t") do |csvfile|
csvfile << us_eco.first.keys
us_eco.each do |row|
csvfile << row.values
end
end
Updated the gist at https://gist.github.com/tamouse/4647196
Struggling with this myself. This is my take:
https://gist.github.com/2639448:
require 'csv'
class CSV
def CSV.unparse array
CSV.generate do |csv|
array.each { |i| csv << i }
end
end
end
CSV.unparse [ %w(your array), %w(goes here) ]