Поскольку строки заголовка должны эффективно обрабатываться, как и все остальные строки, существует значительное преимущество для использования методов класса CSV для выполнения этой задачи. (Примечание: я опубликовал этот ответ перед подготовкой второго ответа, в котором используются методы CSV. Несмотря на то, что я предпочитаю свой другой ответ, я решил оставить этот.)
Код
def combine_csv_files(*csv_files, sep, out_file_name)
IO.write(out_file_name,
csv_files.each_with_object([]) do |(file_name, new_header_name), arr|
a = IO.readlines(file_name, chomp: true)
arr.concat(
a.map { |line| line.split(sep) }.
transpose << [new_header_name, *(1..a.size-1).to_a]
)
end.transpose.
map { |a| a.join(sep) }.
join("\n")
)
end
Пример
Давайте сначала создадим два файла CSV 1 .
str =<<-DA_END
dog,cat
woof,purr
devoted,independent
DA_END
FNAME1 = 'dogsandcats.csv'
IO.write(FNAME1, str)
#=> 38
str =<<-DA_END
cow,pig
moo,oink
dumb,smart
DA_END
FNAME2 = 'cowsandpigs.csv'
IO.write(FNAME2, str)
#=> 28
Предположим, мы хотим добавить столбцы к этим двум файлам с заголовками "col1"
и "col2"
соответственно. Тогда
combine_csv_files(*[[FNAME1, "col1"], [FNAME2, "col2"]], ',', 'everything.csv')
puts IO.read('everything.csv')
dog,cat,col1,cow,pig,col2
woof,purr,1,moo,oink,1
devoted,independent,2,dumb,smart,2
Объяснение
Мы можем выполнить вычисления. Пусть
csv_files = [[FNAME1, "col1"], [FNAME2, "col2"]]
sep = ','
out_file_name = 'everything.csv'
Первым шагом является создание перечислителя.
enum = csv_files.each_with_object([])
#=> #<Enumerator: [["dogsandcats.csv", "col1"], ["cowsandpigs.csv", "col2"]]:
# each_with_object([])>
См. Enumerable # each_with_object 2 . Это создает перечислитель. Метод Enumerator # next используется для генерации перечислителем элементов, которые передаются в блок и присваиваются переменным блока.
(file_name, new_header_name), arr = enum.next
#=> [["dogsandcats.csv", "col1"], []]
file_name
#=> "dogsandcats.csv"
new_header_name
#=> "col1"
arr
#=> []
Процесс деления элемента, возвращаемого enum.next
, на компоненты, назначенные блочным переменным, называется декомпозиция массива . Теперь выполним расчет блока.
b = IO.readlines(file_name, chomp: true)
#=> ["dog,cat", "woof,purr", "devoted,independent"]
c = b.map { |line| line.split(sep) }
#=> [["dog", "cat"], ["woof", "purr"], ["devoted", "independent"]]
d = c.transpose
#=> [["dog", "woof", "devoted"], ["cat", "purr", "independent"]]
e = d << [new_header_name, *(1..b.size-1).to_a]
#=> [["dog", "woof", "devoted"], ["cat", "purr", "independent"], ["col1", 1, 2]]
См. IO :: readlines 3 , Enumerable # map и Array # transpose . Продолжая с вычислением блока,
arr.concat(e)
#=> [["dog", "woof", "devoted"], ["cat", "purr", "independent"], ["col1", 1, 2]]
См. Array # concat . Теперь перечислитель генерирует второй элемент, который передается в блок, переменным блока присваиваются значения и выполняется расчет блока.
(file_name, new_header_name), arr = enum.next
#=> [["cowsandpigs.csv", "col2"], []]
file_name
#=> "cowsandpigs.csv"
new_header_name
#=> "col2"
arr
#=> [["dog", "woof", "devoted"], ["cat", "purr", "independent"], ["col1", 1, 2]]
Обратите внимание, что arr
был обновлен.
b = IO.readlines(file_name, chomp: true)
#=> ["cow,pig", "moo,oink", "dumb,smart"]
c = b.map { |line| line.split(sep) }
#=> [["cow", "pig"], ["moo", "oink"], ["dumb", "smart"]]
d = c.transpose
#=> [["cow", "moo", "dumb"], ["pig", "oink", "smart"]]
e = d << [new_header_name, *(1..b.size-1).to_a]
#=> [["cow", "moo", "dumb"], ["pig", "oink", "smart"], ["col2", 1, 2]]
arr.concat(e)
#=> [["dog", "woof", "devoted" ],
# ["cat", "purr", "independent"],
# ["col1", 1, 2],
# ["cow", "moo", "dumb" ],
# ["pig", "oink", "smart" ],
# ["col2", 1, 2 ]]
Теперь enum
пытается создать другой элемент.
enum.next
#=> StopIteration (iteration reached an end)
Это исключение вызывает возврат arr
. Продолжая,
f = arr.transpose
#=> [["dog", "cat", "col1", "cow", "pig", "col2"],
# ["woof", "purr", 1, "moo", "oink", 1 ],
# ["devoted", "independent", 2, "dumb", "smart", 2] ]
g = f.map { |a| a.join(',') }
#=> ["dog,cat,col1,cow,pig,col2",
# "woof,purr,1,moo,oink,1",
# "devoted,independent,2,dumb,smart,2"]
h = g.join("\n")
#=> "dog,cat,col1,cow,pig,col2\nwoof,purr,1,moo,oink,1\ndevoted,independent,2,dumb,smart,2"
IO.write(out_file_name, h)
#=> 83
См. IO :: write .
Давайте подтвердим, что получили желаемый результат.
puts IO.read(out_file_name)
dog,cat,col1,cow,pig,col2
woof,purr,1,moo,oink,1
devoted,independent,2,dumb,smart,2
См. IO :: read .
1. Обратите внимание, что следующие два heredoc были отступом 4 пробела. Если кто-то хочет запустить этот код, сначала он должен быть удален.
2. Знак фунта в Enumerable#each_with_object
указывает, что each_with_object
является методом экземпляра. Напротив, двойное двоеточие в IO::readlines
указывает, что readlines
является (классом) метод класса IO
.
3. IO
методы и методы экземпляра часто пишутся с File
, а не IO
, в качестве получателя (например, File.write(fname)
). Это разрешено, потому что File
является подклассом IO
(File.superclass #=> IO
) и поэтому наследует его методы и методы экземпляра.