Я хотел бы оптимизировать этот 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 подряд. Разделение однажды, затем продвиньте больше чем одну вещь в массив, затем присоединитесь однажды в конце.
По нескольким причинам вам следует использовать 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
Если вы попадаете в узкое место путем разделения
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 с).
Несколько предложений:
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'
Вам не кажется, что было бы проще использовать массивы и splice
и использовать только join
для разделения запятой в конце?
Я действительно не думаю, что многократное использование s ///
является хорошей идеей, если это серьезное узкое место в вашем коде.
Сохраняйте массив в первую очередь как массив, а не как a, разделенная строка?
Возможно, вы захотите взглянуть на Data :: Locations.
Или попробуйте (непроверено, не протестировано, новые поля не добавляются, как в исходной банке ...)
sub push_csv {
$_[1] =~ y/,//d;
$_[0] =~ s/^(?:[^,]*,){$_[2]}\K[^,]*/$_[1]/;
return;
}