Как эффективно анализировать CSV-файл в Perl?

Я работаю над проектом, который включает в себя синтаксический анализ большого файла csv в формате Perl, и я хочу сделать что-то более эффективным.

Мой подход состоял в том, чтобы сначала split() файл по строкам, а затем split() каждую строку запятыми, чтобы получить поля. Но это субоптимально, так как требуется не менее двух проходов по данным. (один раз разделить по линиям, затем еще раз для каждой строки). Это очень большой файл, поэтому обработка обработки пополам будет значительным улучшением для всего приложения.

Мой вопрос в том, что является наиболее эффективным временем для синтаксического анализа большого CSV-файла с использованием только встроенных инструментов?

Примечание. Каждая строка имеет различное количество токенов, поэтому мы не можем просто игнорировать строки и делиться только запятыми. Также мы можем предположить, что поля будут содержать только буквенно-цифровые данные ascii (никаких специальных символов или других трюков). Кроме того, я не хочу проходить параллельную обработку, хотя она может работать эффективно.

редактировать

Он может включать только встроенные инструменты, поставляемые с Perl 5.8. По бюрократическим причинам я не могу использовать сторонние модули (даже если они размещены на cpan)

другое редактирование

Предположим, что нашему решению разрешено иметь дело только с файловыми данными, когда оно полностью загружено в память.

еще одно редактирование

Я просто понял, насколько глупый этот вопрос. Извините за то, что потратил ваше время. Голосование закрывается.

Правильный способ сделать это – на порядок – использовать Text :: CSV_XS . Это будет намного быстрее и гораздо более надежным, чем все, что вы, вероятно, сделаете сами. Если вы намерены использовать только основные функции, у вас есть несколько вариантов в зависимости от скорости и надежности.

О самом быстром, что вы получите для pure-Perl, это прочитать файл по строкам, а затем наивно разделить данные:

 my $file = 'somefile.csv'; my @data; open(my $fh, '<', $file) or die "Can't read file '$file' [$!]\n"; while (my $line = <$fh>) { chomp $line; my @fields = split(/,/, $line); push @data, \@fields; } 

Это не удастся, если какие-либо поля содержат встроенные запятые. Более надежным (но медленным) подходом было бы использование Text :: ParseWords. Для этого замените split следующим образом:

  my @fields = Text::ParseWords::parse_line(',', 0, $line); 

Вот версия, которая также учитывает кавычки (например, foo,bar,"baz,quux",123 -> "foo", "bar", "baz,quux", "123" ).

 sub csvsplit { my $line = shift; my $sep = (shift or ','); return () unless $line; my @cells; $line =~ s/\r?\n$//; my $re = qr/(?:^|$sep)(?:"([^"]*)"|([^$sep]*))/; while($line =~ /$re/g) { my $value = defined $1 ? $1 : $2; push @cells, (defined $value ? $value : ''); } return @cells; } 

Используйте его так:

 while(my $line = ) { my @cells = csvsplit($line); # or csvsplit($line, $my_custom_seperator) } 

Как Text::CSV_XS другие люди, правильный способ сделать это – с помощью Text :: CSV , а также с Text::CSV_XS стороны Text::CSV_XS (для чтения FASTEST) или Text::CSV_PP (если вы не можете скомпилировать модуль XS ) ,

Если вам разрешено получать дополнительный код локально (например, ваши собственные персональные модули), вы можете взять Text::CSV_PP и поместить его где-нибудь локально, а затем получить доступ к нему через обходной путь use lib :

 use lib '/path/to/my/perllib'; use Text::CSV_PP; 

Кроме того, если нет альтернативы чтению всего файла в памяти и (я полагаю), хранящемуся в скаляре, вы все равно можете прочитать его как дескриптор файла, открыв дескриптор скаляра:

 my $data = stupid_required_interface_that_reads_the_entire_giant_file(); open my $text_handle, '<', \$data or die "Failed to open the handle: $!"; 

А затем прочитайте через интерфейс Text :: CSV:

 my $csv = Text::CSV->new ( { binary => 1 } ) or die "Cannot use CSV: ".Text::CSV->error_diag (); while (my $row = $csv->getline($text_handle)) { ... } 

или неоптимальный раскол на запятые:

 while (my $line = <$text_handle>) { my @csv = split /,/, $line; ... # regular work as before. } 

С помощью этого метода данные копируются только за раз в скалярном режиме.

Вы можете сделать это за один проход, если вы читаете файл по строкам. Не нужно сразу читать все это в памяти.

 #(no error handling here!) open FILE, $filename while () { @csv = split /,/ # now parse the csv however you want. } 

Не совсем уверен, что это значительно более эффективно, но Perl довольно быстро работает при обработке строк.

ВАМ НЕОБХОДИМО УКАЗАТЬ ВАШЕ ИМПОРТ, чтобы узнать, что вызывает замедление. Если, например, вы делаете вставку db, которая занимает 85% времени, эта оптимизация не будет работать.

редактировать

Хотя это похоже на кодовый гольф, общий алгоритм состоит в том, чтобы прочитать весь файл или часть fie в буфер.

Повторяйте байты по байту через буфер, пока не найдете разделитель csv, или новую строку.

  • Когда вы найдете разделитель, увеличьте количество столбцов.
  • Когда вы найдете, что новая строка увеличивает ваш счетчик строк.
  • Если вы нажмете конец своего буфера, прочитайте больше данных из файла и повторите.

Вот и все. Но чтение большого файла в память на самом деле не самый лучший способ, см. Мой оригинальный ответ для обычного способа, которым это делается.

Предположим, что ваш файл CSV загружен в переменную $csv и вам не нужен текст в этой переменной после того, как вы успешно проанализировали его:

 my $result=[[]]; while($csv=~s/(.*?)([,\n]|$)//s) { push @{$result->[-1]}, $1; push @$result, [] if $2 eq "\n"; last unless $2; } 

Если вам нужно иметь $csv нетронутым:

 local $_; my $result=[[]]; foreach($csv=~/(?:(?<=[,\n])|^)(.*?)(?:,|(\n)|$)/gs) { next unless defined $_; if($_ eq "\n") { push @$result, []; } else { push @{$result->[-1]}, $_; } } 

Отвечая на ограничения, налагаемые этим вопросом, вы все равно можете вырезать первый раскол, разбивая ваш входной файл на массив, а не на скаляр:

 open(my $fh, '<', $input_file_path) or die; my @all_lines = <$fh>; for my $line (@all_lines) { chomp $line; my @fields = split ',', $line; process_fields(@fields); } 

И даже если вы не можете установить (версия pure-Perl) Text::CSV , вы можете уйти с подтягиванием исходного кода на CPAN и скопировать / вставить код в свой проект …

  • Как читать и анализировать CSV-файлы на C ++?
  • Чтобы нарисовать подчеркивание под TextView в Android
  • Подчеркивание текста в UIButton
  • Ограниченный выбор в JTextField / JTextComponent?
  • Форматирование текста в ярлыке WinForm
  • Java отрицательный int для шестнадцатеричного и обратного сбоев
  • андроидный эллипсовый многострочный текст
  • Извлечение текста OpenCV
  • Как я могу определить кодировку / кодовую страницу текстового файла
  • Можно ли написать вертикально в текстовом виде в android?
  • Эффективность анализа (если, TryParse, Try-Catch)
  • Давайте будем гением компьютера.