I'm trying to parse a csv file with 16 columns to 16 separate arrays. I need each cell to be another object in the array. So the values in column 1 becomes arr1, column 2 becomes arr2, etc. This is the code I have so far:
file = "FS_Email_Test.csv"
arr1 = []
arr2 = []
arr3 = []
list =CSV.foreach(file, :col_sep => ";", :return_headers => false) do |row|
arr1 << row[0].to_i
arr2 << row[1].to_i
arr3 << row[2].to_s
end
puts arr1
This code correctly parses column1 into arr1, but it returns 0 values for arr2 and arr3. I need it to work for each column. Ideas/thoughts? Thanks for the help.
Problem solved. There was an issue with the .to_i and .to_s on the end of the arrays. I took that piece off and the code works just fine. Thanks for the help.
Code:
file = "FS_Email_Test.csv"
arr1 = []
arr2 = []
arr3 = []
list =CSV.foreach(file, :col_sep => ";", :return_headers => false) do |row|
arr1 << row[0]
arr2 << row[1]
arr3 << row[2]
end
puts arr1
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 an array of hashes, all with the same keys, all with a key of id. I need a comma delimited string of ids.
arr_widgets = []
widgets.each do |widget|
arr_widgets.push(widget['id'])
end
str_widgets = arr_widgets.join(',')
Have you tried something like this?
str_widgets = widgets.map { |w| w['id'] }.join(',')
There is no need to create an intermediate array.
widgets = [
{"id"=>"dec"}, {"id"=>21}, {"id"=>2020}, {"id"=>"was"},
{"id"=>"the"}, {"id"=>"shortest"}, {"id"=>"day"}, {"id"=>"of"},
{"id"=>"the"}, {"id"=>"longest"}, {"id"=>"year"}
]
Note that two values are integers.
s = widgets.each_with_object(String.new) do |widget, s|
s << ',' unless s.empty?
s << widget["id"].to_s
end
puts s
#=> "dec,21,2020,was,the,shortest,day,of,the,longest,year"
I am trying to sum 2 matrixes from a CSV file
Currently, I put them into to arrays and then transform the array into matrixes. However, when I print them, I get concatenated strings not summed integers.
require 'csv'
require 'matrix'
matrix1 = "./matrix1.csv"
matrix2 = "./matrix2.csv"
line_count = 0
elements_in_line_count = 0
arr1 = Array.new #=> []
arr2 = Array.new #=> []
CSV.foreach(matrix1) do |row|
arr1 << row
line_count += 1
elements_in_line_count = row.size
end
n1 = elements_in_line_count
m1 = line_count
# find n and m of second matrix
line_count = 0
elements_in_line_count = 0
CSV.foreach(matrix2) do |row|
# print row
arr2 << row
line_count += 1
elements_in_line_count = row.size
end
puts Matrix.rows(arr1) + Matrix.rows(arr2)
For example, CSV 1 is:
1,2
3,4
Same for CSV 2.
The output is
Matrix[[11, 22], [33, 44]]
But I want it to be [2,4],[6,8]
When you read in the CSV, by default it reads in all the rows/columns as strings, the Ruby CSV class can take an optional parameter to foreach and new and similar methods called :converters that it will use to convert each applicable column. One of the converters it can take is
:integer
Converts any field Integer() accepts.
So you can also change your code to look like:
csv_options = { converters: [:integer] }
CSV.foreach(matrix1, csv_options) do |row|
# ...
CSV.foreach(matrix2, csv_options) do |row|
to achieve results similar to calling map(&:to_i) on each row.
[m1, m2].map do |m|
CSV.foreach(m).map { |row| row.map(&:to_i) }
end.reduce do |m1, m2|
m1.map.with_index do |row, idx|
row.zip(m2[idx]).map { |cell1, cell2| cell1 + cell2 }
end
end
When you're reading in the CSV, all columns will be strings, so you'll have to manually do the conversion to a number in the code.
If all of the columns of the CSV are intended to be numbers, you can add .map(&:to_i) to the row line. Like this:
CSV.foreach(matrix1) do |row|
arr1 << row.map(&:to_i) # <-- this will turn everything in the row into a number
line_count += 1
elements_in_line_count = row.size
end
As you want to add matrices, consider using Ruby's built-in Matrix class, and the instance method Matrix#+ in particular.
Let's first construct three CSV files.
fname1 = 't1.csv'
fname2 = 't2.csv'
fname3 = 't3.csv'
File.write(fname1, "1,2\n3,4")
#=> 7
File.write(fname2, "100,200\n300,400")
#=> 15
File.write(fname3, "1000,2000\n3000,4000")
#=> 19
We can sum the underlying matrices as follows.
require 'csv'
require 'matrix'
fnames = [fname1, fname2, fname3]
fnames.drop(1).reduce(matrix_from_CSV(fnames.first)) do |t,fname|
t + matrix_from_CSV(fname)
end.to_a
#=> [[1101, 2202],
# [3303, 4404]]
def matrix_from_CSV(fname)
Matrix[*CSV.read(fname, converters: [:integer])]
end
I borrowed converters: [:integer] from #Simple's answer. I wasn't aware of that.
I generate arrays that I append to a file with:
require 'csv`
CSV.open(FILE_NAME, "a+") do |csv|
csv << array
end
I want to skip the insertion if the index [0] value is identical to the last inserted array's [0].
How can I read the last line/row of a csv file and compare it's values with an array that's still not inserted?
Working around some of what #Patru said:
require 'csv'
csv = CSV.open(FILE_NAME, "a+")
first_row = csv.first
csv.rewind
last_row = csv.reverse_each.first
csv << ary unless ary == first_row or ary == last_row
I want to load a CSV-file with two columns (each with a name and a row of numbers) and save only the numbers of the two columns in two different arrays.
Then I want to make some calculations with the data in those two columns, using two arrays to save the numbers of each column.
This is what I still have:
require 'csv'
filename = 'file.csv'
csv_data = CSV.read(filename, :col_sep => ";")
csv_data.shift
csv_data.each_with_index { |column, index_c|
average = 0
column.each_with_index{ |element, index_e|
csv_data[index_c][index_e] = element.to_i
}
}
csv_data = csv_data.transpose
How can I split the columns of csv_data in two arrays ?
This should do the trick for you creating your two column arrays without wasting storage reading the whole file redundantly into csv_data.
require 'csv'
filename = 'file.csv'
arr1 = []
arr2 = []
CSV.foreach(filename, :col_sep => ";", :return_headers => false) do |row|
arr1 << row[0].to_i
arr2 << row[1].to_i
end