Matriz de saída para CSV em Ruby


185

É bastante fácil ler um arquivo CSV em uma matriz com Ruby, mas não consigo encontrar nenhuma boa documentação sobre como gravar uma matriz em um arquivo CSV. Alguém pode me dizer como fazer isso?

Estou usando o Ruby 1.9.2, se isso importa.


3
A resposta que você tem é ótima, mas permita-lhe que você não use CSV. Se você não possui guias em seus dados, os arquivos delimitados por tabulações são muito mais fáceis de lidar, porque eles não envolvem tantas citações e escapações assustadoras e tal. Se você deve usar o CSV, é claro, são as quebras.
Bill Dueber

8
@ Bill, o módulo CSV manipula ordenadamente arquivos delimitados por tabulações, bem como arquivos csv reais. A opção: col_sep permite especificar o separador de colunas como "\ t" e está tudo bem.
tamouse

1
aqui está mais informações sobre CSV docs.ruby-lang.org/pt/2.1.0/CSV.html
veeresh yh

Usar arquivos .tab com este módulo é o que estou fazendo, porque abrir isso no Excel acidentalmente atrapalhava a codificação ...
MrVocabulary

Respostas:


326

Para um arquivo:

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

Para uma sequência:

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

Aqui está a documentação atual em CSV: http://ruby-doc.org/stdlib/libdoc/csv/rdoc/index.html


1
@ David é o modo de arquivo. "w" significa gravar em um arquivo. Se você não especificar isso, o padrão será "rb" (modo binário somente leitura) e você receberá um erro ao tentar adicionar ao seu arquivo csv. Veja ruby-doc.org/core-1.9.3/IO.html para obter uma lista dos modos de arquivo válidos no Ruby.
Dylan Markow

15
Peguei vocês. E para usuários futuros, se você quiser que cada iteração não substitua o arquivo csv anterior, use a opção "ab".
boulder_ruby

1
Veja esta resposta for File Rubi IO Modos: stackoverflow.com/a/3682374/224707
Nick

38

Eu tenho isso em apenas uma linha.

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" 

Faça tudo o que precede e salve em um csv, em uma linha.

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

NOTA:

Para converter um banco de dados de registro ativo para CSV seria algo parecido com isto, eu acho

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

Hmm @tamouse, essa essência é um pouco confusa para mim sem ler a fonte csv, mas genericamente, assumindo que cada hash em sua matriz tem o mesmo número de pares k / v e que as chaves são sempre as mesmas, na mesma ordem (por exemplo, se seus dados estiverem estruturados), isso deve fazer a ação:

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

Se seus dados não estiverem estruturados, isso obviamente não funcionará


Peguei um arquivo CSV usando CSV.table, fiz algumas manipulações, me livrei de algumas colunas e agora quero colocar em spool o Array of Hashes resultante novamente como CSV (realmente delimitado por tabulações). Como? gist.github.com/4647196
tamouse

hmm ... que essência é um pouco opaco, mas dado um array de hashes, todos com o mesmo número de k / v pares e as mesmas chaves, na mesma ordem ...
boulder_ruby

Obrigado, @boulder_ruby. Isso vai funcionar. Os dados são uma tabela do censo, e essa essência é bastante opaca olhando para trás. :) Basicamente, extrai determinadas colunas da tabela de censo original em um subconjunto.
tamouse

3
Você está injectusando mal aqui, você realmente quer usar map. Além disso, você não precisa passar uma string vazia para join, pois esse é o padrão. Para que você possa encolher ainda mais isso:rows.map(&CSV.method(:generate_line).join
iGEL

1
Seu segundo exemplo é muito complicado, pois a biblioteca CSV é bastante poderosa. CSV.generate(headers: hsh.first&.keys) { |csv| hsh.each { |e| csv << e } }gera um CSV equivalente.
Amadan

28

Se você tiver uma matriz de matrizes de dados:

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

Então você pode escrever isso em um arquivo com o seguinte, que eu acho que é muito mais simples:

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

20

Se alguém estiver interessado, aqui estão algumas orientações (e uma observação sobre a perda de informações de tipo no 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: O CSV perde todas as informações de tipo, você pode usar JSON para preservar informações básicas de tipo ou ir para o YAML detalhado (mas mais facilmente editável por humanos) para preservar todas as informações de tipo - por exemplo, se você precisar de um tipo de data, que se tornaria strings em CSV e JSON.


9

Com base na resposta de @ boulder_ruby, é isso que estou procurando, supondo que us_ecocontenha a tabela CSV a partir da minha essência.

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

Atualizado o gist em https://gist.github.com/tamouse/4647196


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.