Perl sub оптимизация проталкивает строку в csv с использованием split - PullRequest
5 голосов
/ 26 июня 2010

Я хотел бы оптимизировать эту подпрограмму Perl:

push_csv($string,$addthis,$position);

для размещения строк внутри строки CSV.

например, если $string="one,two,,four"; $addthis="three"; $position=2;
thenpush_csv($string,$addthis,$position) изменит значение $string = "one,two,three,four";

sub push_csv {

    my @fields = split /,/, $_[0]; # split original string by commas;
    $_[1] =~ s/,//g;               # remove commas in $addthis
    $fields[$_[2]] = $_[1];        # put the $addthis string into
                                   # the array position $position.
    $_[0] = join ",", @fields;     # join the array with commas back
                                   # into the string.
}

Это узкое место в моем коде, так как его нужно вызывать несколько миллионов раз.

Если вы опытный вPerl, не могли бы вы взглянуть на это и предложить оптимизацию / альтернативы?Заранее спасибо!:)


РЕДАКТИРОВАТЬ: Преобразование в @fields и обратно в строку занимает время, я просто подумал, как ускорить его, когда у меня более одного суб-вызова подряд.Разделите один раз, затем вставьте в массив более одной вещи, затем присоедините один раз в конце.

Ответы [ 5 ]

3 голосов
/ 26 июня 2010

По нескольким причинам вы должны использовать Text :: CSV для обработки этих низкоуровневых деталей CSV. При условии, что вы можете установить версию XS, я понимаю, что она будет работать быстрее, чем все, что вы можете делать в чистом Perl. Кроме того, модуль будет правильно обрабатывать все виды крайних случаев, которые вы, вероятно, пропустите.

use Text::CSV;
my $csv = Text::CSV->new;

my $line = 'foo,,fubb';
$csv->parse($line);

my @fields = $csv->fields;
$fields[1] = 'bar';

$csv->combine(@fields); 
print $csv->string;      # foo,bar,fubb
2 голосов
/ 26 июня 2010

Во-первых, сохранить массив как массив, а не как строку, разделенную?

Возможно, вы захотите взглянуть на Data :: Locations.

Или попробовать (непроверенный, без привязки, не добавляет новые поля, как ваша оригинальная банка ...)

sub push_csv {
    $_[1] =~ y/,//d;
    $_[0] =~ s/^(?:[^,]*,){$_[2]}\K[^,]*/$_[1]/;
    return;
}
1 голос
/ 26 июня 2010

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

например:

#!/usr/bin/env perl

use strict;
use warnings;

# Start with some CSV-ish data
my $initial_data = 'foo,bar,baz';

# Split it into an arrayref
my $data = [ split /,/, $initial_data ];

for (1 .. 1_000_000) {
  # Pointless call to push_csv, just to make it run
  push_csv($data, $_, $_ % 3);
}

# Turn it back into a string and display it
my $final_data = join ',', @$data;
print "Result: $final_data\n";

sub push_csv {
  my ($data_ref, $value, $position) = @_;
  $$data_ref[$position] = $value;
  # Alternately:
  # $data_ref->[$position] = $value;
}

Обратите внимание, что это упрощает все настолько, что push_csv становится единственной, довольно простой строкой обработки, так что выможет потребоваться просто выполнить внутреннее изменение, а не вызывать для него сабвуфер, особенно если ключевым критерием является эффективность времени выполнения - в этом тривиальном примере избавление от push_csv и выполнение этого в строке сокращают время выполнения примерно на 70% (с 0,515).с 0,167 с).

1 голос
/ 26 июня 2010

Несколько предложений:

  • Используйте tr/,//d вместо s/,//g, поскольку это быстрее. По сути, это то же самое, что предложение YSTH использовать y/,//d
  • Выполняйте split только столько, сколько нужно. Если $position = 1, и у вас есть 10 полей, то вы тратите впустую вычисления, выполняя ненужные разбиения и объединения. Необязательный третий аргумент split может быть использован в ваших интересах здесь. Однако это зависит от того, сколько последовательных пустых полей вы ожидаете. Это может не стоить того, если вы заранее не знаете, сколько из них у вас есть
  • Вы совершенно правы, когда хотите выполнить несколько добавлений с помощью одного дополнительного вызова. Нет необходимости выполнять несколько сплитов и объединений, когда один будет делать то же самое

Вы действительно должны использовать Text::CSV, но вот как я бы пересмотрел реализацию вашей сабвуфера в чистом Perl (предполагая максимум одно последовательное пустое поле):

sub push_csv {

    my ( $items, $positions ) = @_[1..2];

    # Test inputs
    warn "No. of items to add & positions not equal"
      and
        return unless @{$items} == @{$positions};

    my $maxPos;  # Find the maximum position number

    for my $position ( @{$positions} ) {

        $maxPos ||= $position;
        $maxPos = $position if $maxPos < $position;
    }

    my @fields = split /,/ , $_[0], $maxPos+2;  # Split only as much as needed

    splice ( @fields, $positions->[$_], 1, $items->[$_] ) for 0 .. $#{$items};
    $_[0] = join ',' , @fields;
    print $_[0],"\n";
}

Использование

use strict;
use warnings;

my $csvString = 'one,two,,four,,six';
my @missing = ( 'three', 'five' );
my @positions = ( 2, 4 );

push_csv ( $csvString, \@missing, \@positions );
print $csvString;   # Prints 'one,two,three,four,five,six'
0 голосов
/ 26 июня 2010

Не думаете ли вы, что может быть проще использовать массивы и splice, и использовать только join для создания разделительной запятой в конце?

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...