Perl sub строка нажатия оптимизации в csv использование разделения

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

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

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

например, если $string="one,two,,four"; $addthis="three"; $position=2;
затем push_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 и назад представлять в виде строки занимает время, я просто думал о способе ускорить его, где у меня есть больше чем один вызов sub подряд. Разделение однажды, затем продвиньте больше чем одну вещь в массив, затем присоединитесь однажды в конце.

5
задан Zaid 26 June 2010 в 08:13
поделиться

5 ответов

По нескольким причинам вам следует использовать 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
3
ответ дан 14 December 2019 в 08:41
поделиться

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

например:

#!/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
ответ дан 14 December 2019 в 08:41
поделиться

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

  • Используйте 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'
1
ответ дан 14 December 2019 в 08:41
поделиться

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

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

0
ответ дан 14 December 2019 в 08:41
поделиться

Сохраняйте массив в первую очередь как массив, а не как a, разделенная строка?

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

Или попробуйте (непроверено, не протестировано, новые поля не добавляются, как в исходной банке ...)

sub push_csv {
    $_[1] =~ y/,//d;
    $_[0] =~ s/^(?:[^,]*,){$_[2]}\K[^,]*/$_[1]/;
    return;
}
2
ответ дан 14 December 2019 в 08:41
поделиться
Другие вопросы по тегам:

Похожие вопросы: