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.

Jason Swett
fuente
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

Dylan Markow
fuente
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á

boulder_ruby
fuente
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)
jwadsack
fuente
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.

Kanat Bolazar
fuente
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

tamouse
fuente
2

Luchando con esto yo mismo. Esta es mi opinión:

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) ]
Felix Rabe
fuente
Por cierto, tenga cuidado con las matrices multidimensionales en palanca en JRuby. [ %w(your array), %w(goes here) ]No se verá bonito. github.com/pry/pry/issues/568
Felix Rabe