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.

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

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

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

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

tamouse
fonte
2

Lutando com isso sozinho. Esta é a minha opinião:

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
fonte
Btw, cuidado com matrizes multidimensionais em alavanca no JRuby. [ %w(your array), %w(goes here) ]não ficará bonito. github.com/pry/pry/issues/568
Felix Rabe