Matriz de salida a CSV en Ruby


185

Es bastante fácil leer un archivo CSV en una matriz con Ruby, pero no puedo encontrar ninguna buena documentación sobre cómo escribir una matriz en un archivo CSV. ¿Puede alguien decirme cómo hacer esto?

Estoy usando Ruby 1.9.2 si eso importa.


3
La respuesta que tiene es excelente, pero permítame recomendarle que no use CSV. Si no tiene pestañas en sus datos, los archivos delimitados por pestañas son mucho más fáciles de manejar porque no implican tanto citas y escapes. Si debe usar CSV, por supuesto, son los descansos.
Bill Dueber

8
@Bill, el módulo CSV maneja perfectamente los archivos delimitados por tabulaciones, así como los archivos csv reales. La opción: col_sep le permite especificar el separador de columna como "\ t" y todo está bien.
tamouse

1
aquí hay más información sobre CSV docs.ruby-lang.org/en/2.1.0/CSV.html
veeresh yh

Lo que estoy haciendo es usar archivos .tab con este módulo, porque abrir esto en Excel por accidente de otro modo estropearía la codificación ...
MrVocabulary

Respuestas:


326

A un archivo:

require 'csv'
CSV.open("myfile.csv", "w") do |csv|
  csv << ["row", "of", "CSV", "data"]
  csv << ["another", "row"]
  # ...
end

A una cadena:

require 'csv'
csv_string = CSV.generate do |csv|
  csv << ["row", "of", "CSV", "data"]
  csv << ["another", "row"]
  # ...
end

Aquí está la documentación actual sobre CSV: http://ruby-doc.org/stdlib/libdoc/csv/rdoc/index.html


1
@David es el modo de archivo. "w" significa escribir en un archivo. Si no especifica esto, el valor predeterminado será "rb" (modo binario de solo lectura) y obtendrá un error al intentar agregar a su archivo csv. Consulte ruby-doc.org/core-1.9.3/IO.html para obtener una lista de los modos de archivo válidos en Ruby.
Dylan Markow

15
Gotcha Y para futuros usuarios, si desea que cada iteración no sobrescriba el archivo csv anterior, use la opción "ab".
boulder_ruby

1
Vea esta respuesta para los modos de E / S de Ruby File: stackoverflow.com/a/3682374/224707
Nick

38

Tengo esto en una sola línea.

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" 

Haga todo lo anterior y guárdelo en un csv, en una línea.

File.open("ss.csv", "w") {|f| f.write(rows.inject([]) { |csv, row|  csv << CSV.generate_line(row) }.join(""))}

NOTA:

Creo que convertir una base de datos de registros activa a csv sería algo como esto

CSV.open(fn, 'w') do |csv|
  csv << Model.column_names
  Model.where(query).each do |m|
    csv << m.attributes.values
  end
end

Hmm @tamouse, esa esencia es algo confusa para mí sin leer la fuente csv, pero genéricamente, suponiendo que cada hash en su matriz tiene el mismo número de pares k / v y que las claves son siempre las mismas, en el mismo orden (es decir, si sus datos están estructurados), esto debería hacer el hecho:

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

Si sus datos no están estructurados, esto obviamente no funcionará


Extraje un archivo CSV usando CSV.table, hice algunas manipulaciones, me deshice de algunas columnas, y ahora quiero poner en cola la matriz resultante de Hashes nuevamente como CSV (realmente delimitado por tabulaciones). ¿Cómo? gist.github.com/4647196
tamouse

hmm ... esa esencia es algo opaca, pero dada una serie de hashes, todos con el mismo número de pares k / v y las mismas teclas, en el mismo orden ...
boulder_ruby

Gracias, @boulder_ruby. Que funcionará. Los datos son una tabla censal, y esa esencia es bastante opaca si la miramos de nuevo. :) Básicamente está extrayendo ciertas columnas de la tabla censal original en un subconjunto.
tamouse

3
Estás haciendo mal uso injectaquí, realmente quieres usar map. Además, no necesita pasar una cadena vacía join, ya que este es el valor predeterminado. Así que podría reducirlo aún más a esto:rows.map(&CSV.method(:generate_line).join
iGEL

1
Su segundo ejemplo es demasiado complicado, ya que la biblioteca CSV es bastante poderosa. CSV.generate(headers: hsh.first&.keys) { |csv| hsh.each { |e| csv << e } }genera un CSV equivalente.
Amadan

28

Si tiene una matriz de matrices de datos:

rows = [["a1", "a2", "a3"],["b1", "b2", "b3", "b4"], ["c1", "c2", "c3"]]

Luego puede escribir esto en un archivo con lo siguiente, que creo que es mucho más simple:

require "csv"
File.write("ss.csv", rows.map(&:to_csv).join)

20

Si alguien está interesado, aquí hay algunas frases (y una nota sobre la pérdida de información de tipo en 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

Nota: CSV pierde toda la información de tipo, puede usar JSON para preservar la información básica de tipo o ir a YAML detallado (pero más fácilmente editable por humanos) para preservar toda la información de tipo, por ejemplo, si necesita un tipo de fecha, que se convertiría en cadenas en CSV y JSON.


9

Sobre la base de la respuesta de @ boulder_ruby, esto es lo que estoy buscando, suponiendo que us_ecocontenga la tabla CSV desde mi esencia.

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

Se actualizó la esencia en https://gist.github.com/tamouse/4647196


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.