将Perl数组排序到位?

Dan*_*age 19 arrays sorting perl

我有一个数组的引用(被调用$intervals),我想对这个数组中的值进行排序.数组中可能存在大量值,因此我不希望复制值.我目前的做法是这样的.

sub by_position
{
  $a->start <=> $b->start ||
  $a->end   <=> $b->end
}
my @sorted_intervals = sort by_position (@$intervals);
Run Code Online (Sandbox Code Playgroud)

但是,如果我正确理解Perl,这确实会复制数组中的所有值.是对的吗?如果是这样,有没有办法可以对数组进行就地排序(使用对该数组的引用)?

amo*_*mon 25

Perl允许使用成语对数组进行就地排序@arr = sort @arr.与赋值运算符的正常行为相反,在这种情况下不会复制.但是,这种优化仅限于正常的数组变量; 它不适用于数组引用:

让我们看一下使用该-MO=Concise选项.首先,我们进行正常的就地排序,看看我们期望的是什么:

$ perl -E'say $^V'
v5.18.2
$ perl -MO=Concise -e'my @arr; @arr = sort @arr'
8  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 1 -e:1) v:{ ->3
3     <0> padav[@arr:1,2] vM/LVINTRO ->4
4     <;> nextstate(main 2 -e:1) v:{ ->5
-     <1> ex-aassign vKS/64 ->8
-        <1> ex-list lK ->-
5           <0> pushmark s ->6
7           <@> sort lK/INPLACE ->8
6              <0> padrange[@arr:1,2] l/1 ->7
-              <0> padav[@arr:1,2] lRM* ->7
-        <1> ex-list lK ->-
-           <0> ex-pushmark s ->-
-           <0> ex-padav lRM* ->-
-e syntax OK
Run Code Online (Sandbox Code Playgroud)

有趣的是:<@> sort lK/INPLACE ->8这似乎很合适.现在让我们用引用做同样的事情:

$ perl -MO=Concise -e'my $ref; @$ref = sort @$ref'
e  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 1 -e:1) v:{ ->3
3     <0> padsv[$ref:1,2] vM/LVINTRO ->4
4     <;> nextstate(main 2 -e:1) v:{ ->5
d     <2> aassign[t4] vKS/COMMON ->e
-        <1> ex-list lK ->a
5           <0> pushmark s ->6
9           <@> sort lK ->a
6              <0> pushmark s ->7
8              <1> rv2av[t3] lK/1 ->9
7                 <0> padsv[$ref:1,2] s ->8
-        <1> ex-list lK ->d
a           <0> pushmark s ->b
c           <1> rv2av[t2] lKRM*/1 ->d
b              <0> padsv[$ref:1,2] sM/DREFAV ->c
-e syntax OK
Run Code Online (Sandbox Code Playgroud)

我没有看到里面的旗帜<@> sort lK ->a.因此,优化仅在使用相同变量时才起作用,而不是在使用相同数组时.但这意味着如果我们将数组变量别名为某个标量引用的数组(使用Data :: Alias),我们就可以对数组引用进行排序:

perl -MData::Alias -MO=Concise -e'my $ref; alias my @arr = @$ref; @arr = sort @arr'
e  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 1 -e:1) v:{ ->3
3     <0> padsv[$ref:1,3] vM/LVINTRO ->4
4     <;> nextstate(main 2 -e:1) v:{ ->5
-     <1> entersub vKS/INARGS ->a
         ...
a     <;> nextstate(main 3 -e:1) v:{ ->b
-     <1> ex-aassign vKS/64 ->e
-        <1> ex-list lK ->-
b           <0> pushmark s ->c
d           <@> sort lK/INPLACE ->e
c              <0> padrange[@arr:2,3] l/1 ->d
-              <0> padav[@arr:2,3] lRM* ->d
-        <1> ex-list lK ->-
-           <0> ex-pushmark s ->-
-           <0> ex-padav lRM* ->-
-e syntax OK
Run Code Online (Sandbox Code Playgroud)

......并且inplace-flag再次出现<@> sort lK/INPLACE ->e:-)

这意味着Eric Strom的回答是不正确的.

  • 提示:`-MO =简洁,-exec`以`exec`顺序显示,但也隐藏了优化的操作码(第一列中带有`-`的操作码),因此可以更容易阅读. (2认同)

Eri*_*rom 17

从perl 5.8.4开始,@a = sort @a优化了就地排序.请参阅以下链接了解详情:

perl584delta中的性能增强

http://perl5.git.perl.org/perl.git/commit/fe1bc4cf71e7b04d33e679798964a090d9fa7b46?f=pp_sort.c

+    /* optimiser converts "@a = sort @a" to "sort \@a";
+     * in case of tied @a, pessimise: push (@a) onto stack, then assign
+     * result back to @a at the end of this function */
Run Code Online (Sandbox Code Playgroud)

所以你应该写:

@$intervals = sort by_position @$intervals
Run Code Online (Sandbox Code Playgroud)

在perl的5.8.3以后,你会看到内存使用量减少(并且在极少数情况下保留别名).

  • 虽然就地优化存在,但仅在参数和左值是相同的数组变量时才应用.从perl 5.18.2开始,它不适用于references_ - 参见[我的答案](http://stackoverflow.com/a/24960868/1521179). (7认同)