Изменение строк внутри компаратора сортировки
У меня есть следующий код, который в компараторе сортировки, он удаляет строку префикса перед выполнением сравнения.
print for sort {
$a =~ s/^STRING//;
$b =~ s/^STRING//;
foo($a) cmp foo($b)
} @a;
Хотя сравнение и порядок верны, префиксная строка удаляется из выходных данных.
Следующее сохраняет префикс строки (как я хотел).
print for sort {
$x = a;
$y = b;
$x =~ s/^STRING//;
$y =~ s/^STRING//;
foo($x) cmp foo($y)
} @a;
Но я запутался, как вторая часть держит префикс.
Делает ли он копию строки, а в случае массива удаляет исходную ссылку?
Также я делаю что-то не так в первом фрагменте и в конечном итоге проблема?
2 ответа
Один из способов: использовать /r
модификатор
print for sort {
foo( $a =~ s/^STRING//r ) cmp foo( $b =~ s/^STRING//r )
} @ary
с помощью которого оператор подстановки s/
возвращает измененную строку и оставляет оригинал без изменений. Если совпадений нет, возвращается исходная строка, которая соответствует цели. Смотрите конец для оптимизации, если это используется на больших массивах, или если вызов функции занимает время.
Другим способом было бы заменить замену на совпадение и захват, когда это возможно.
Существует обширное обсуждение этого в своем роде. Первый, $a
а также $b
являются (пакет) глобалы
$a
а также$b
устанавливаются как глобальные переменные в пакете, из которого вызывается sort()
Блок { }
обозначает анонимную подпрограмму и
... сравниваемые элементы передаются в подпрограмму как глобальные переменные пакета
$a
а также$b
таким образом, псевдонимы подразумевают, что их изменение влияет на элементы. таким образом
Сравниваемые значения всегда передаются по ссылке и не должны изменяться.
что происходит когда $a
, $b
меняются, поэтому элементы меняются.
Во втором случае вы копируете $a
а также $b
в (что должно быть!) лексики $x
а также $y
и связь с @ary
сломан, поэтому элементы не изменены.
Пожалуйста всегда имейте use warnings;
а также use strict;
в начале программы. Это отличный, хотя и экстремальный пример - может иметь большое значение, являются ли переменные, которые вы вводите, чтобы опробовать вещи, являются глобальными ($x
) или лексический (my $x
).
Код, обрабатывающий элементы для использования результирующего значения для сравнения сортировки, имеет недостаток эффективности. Сравнение выполняется по двум элементам одновременно, поэтому элементы обрабатываются несколько раз. И каждый раз мы делаем одну и ту же обработку и вычисляем одно и то же значение для элемента.
Эта неэффективность характерна только для достаточно больших наборов данных, и большую часть времени не нужно беспокоиться. Но в этом случае запускается механизм регулярных выражений, а также задействуется вызов функции, и это не совсем дешево в Perl. Кроме того, накладные расходы на вызов не определены, и я предполагаю, что в этом есть какая-то работа.
Для оптимизации этого можно предварительно обработать ввод, а затем отсортировать
my @sorted_ary =
map { $_->[1] }
sort { $a->[0] cmp $b->[0] }
map { [ foo( s/^STRING//r ), $_ ] }
@ary;
map
который принимает вход @ary
применяет регулярное выражение и вызов функции и сохраняет этот результат вместе с исходным элементом в двухэлементном массиве ref для каждого элемента @ary
, Этот список arrayrefs тогда sort
ed, используя первый элемент arrayrefs для сравнения. Последний map
затем извлекает второй элемент из отсортированных массивов, возвращая, таким образом, исходные элементы, отсортированные по мере необходимости.
Это называется преобразованием Шварца. См. "Примеры" в сортировке, например.
Следует иметь в виду, что выигрыш заметен только для достаточно больших данных, в то время как этот маневр также связан с накладными расходами (и в гораздо более сложном коде). Поэтому используйте его только тогда, когда есть очевидная проблема такого рода с сортировкой.
Пожалуйста, рассмотрите этот код, который развивает мой предыдущий комментарий
use strict;
use warnings;
use v5.14;
my @array=qw(a b c d foo bar baz); #This creates the array
my @prefixed = map { "STRING$_"} @array;
#This is the sorting part we're interested in
say for sort { sans_prefix($a) cmp sans_prefix($b) } @prefixed;
# This sub does the extraction
sub sans_prefix {
my ($no_prefix) = ($_[0] =~ /STRING(\w+)/);
return $no_prefix;
}
Логика, которая извлекает часть, которая не является префиксом, спрятана в sans_prefix
, Мы не делаем подстановку, но в любом случае мы действуем по лексическому ($_[0]) и не модифицируем его, поэтому это возвращает
STRINGa
STRINGb
STRINGbar
STRINGbaz
STRINGc
STRINGd
STRINGfoo