Быстрое чтение CSV в Common Lisp
Каков самый быстрый способ чтения csv-файла в CL таким образом, что: 1) все поля в первой строке идут в один массив, называемый column-names 2) первое поле каждой из следующих строк переходит в другое массив называют строк-имена 3) все остальные поля переходят в другой массив, называемый значениями ?
Мой файл имеет следующий вид, только с большим количеством столбцов и строк:
"";"ES1 Index";"VG1 Index";"TY1 Comdty";"RX1 Comdty";"GC1 Comdty"
"1999-01-04";1391.12;3034.53;66.515625;86.2;441.39
"1999-01-05";1404.86;3072.41;66.3125;86.17;440.63
"1999-01-06";1435.12;3156.59;66.4375;86.32;441.7
"1999-01-07";1432.32;3106.08;66.25;86.22;447.67
И результат, который я хотел бы получить:
#("1999-01-04" "1999-01-05" "1999-01-06" "1999-01-07" )
#("" "ES1 Index" "VG1 Index" "TY1 Comdty" "RX1 Comdty" "GC1 Comdty")
#(1391.12 3034.53 66.515625 86.2 441.39 1404.86 3072.41 66.3125 86.17 440.63
1435.12 3156.59 66.4375 86.32 441.7 1432.32 3106.08 66.25 86.22 447.67)
Известно ли вам о какой-то библиотеке CL, которая делает это уже? Есть ли какие-либо общие проблемы, касающиеся производительности ввода-вывода, возможно, специфичные для компилятора, о которых я должен знать?
Вот как я делаю это сейчас:
(with-open-file (stream "my-file.csv" :direction :input)
(let* ((header (read-line stream nil))
(columns-list (mapcar #'read-from-string
(cl-ppcre:split ";" header)))
(number-of-columns (length columns-list))
(column-names (make-array number-of-columns
:initial-contents columns-list))
(rownames (make-array 1 :adjustable t :fill-pointer 0))
(values (make-array 1 :adjustable t :fill-pointer 0)))
(set-syntax-from-char #; # )
(loop
:for reader = (read stream nil stream)
:until (eq reader stream)
:do (progn (vector-push-extend reader row-names)
(loop
:for count :from 2 :upto number-of-columns
:do (vector-push-extend (read stream nil)
values)))
:finally (return (values row-names
column-names
values)))))
Примечание: Я бы не стал использовать set-syntax-from-char в реальном коде, я использую его только ради этого примера.
2 ответа:
Я подозреваю, что ввод-вывод здесь самая медленная часть. Вероятно, вы можете получить более быстрый ввод / вывод, если будете использовать последовательность чтения , а не вызывать строку чтения повторно. Поэтому ваш код может выглядеть примерно так:
(with-open-file (s "my-file.csv") (let* ((len (file-length s)) (data (make-array len))) (read-sequence data s) data))
Затем разбейте
data
на новые строки и добавьте свою логику.Независимо от того, помогает это или нет, вам было бы полезно профилировать свой код, например, с помощью
:sb-sprof
, чтобы увидеть, где тратится большая часть времени.
Для чтения csv-файлов я нахожу очень полезным и быстрым пакет cl-csv (https://github.com/AccelerationNet/cl-csv например, для решения вашей проблемы можно использовать следующий код:
(let ((data (cl-csv:read-csv #P"my-file.csv" :separator #\;))) (values (apply #'vector (first data)) (apply #'vector (rest (mapcar #'first data))) (apply #'vector (mapcar #'read-from-string (loop :for row :in (rest data) :append (rest row))))))
cl-csv:read-csv
возвращает список, содержащий для каждой строки список строк, являющихся содержимым ячеек.