操作数组

在Perl中,数组、列表应用非常广泛,也经常需要对数组和列表进行一些操作。

列表常见操作包括:grep、join、map、reverse、sort、unpack、x操作符执行列表重复,等等。另外,标准库List::Utils中也提供了很多常见的列表操作,如reduce、first、any、sum、uniq、shuffle等。

多数时候,数组可以当作列表来使用,原因是操作列表的地方期待一个列表,即处在列表上下文,perl会隐式地将数组转换为列表。因此上述列表操作多数也适用于数组。

但数组是Perl的内置数据结构,是直接暴露给编程人员的数据类型,它可以转换为列表,但它不是为了转换成列表而存在的,它有自己的角色,有一些操作只能应用于数组而不能应用于列表。

数组常见的操作包括:each、pop、push、shift、unshift、keys、values、splice。

本节将介绍数组的常见操作,列表相关操作将留给后文。

keys、values

  • keys函数在列表上下文返回数组的所有索引或hash的所有key,在标量上下文返回数组或hash的元素数量

  • values函数在列表上下文返回数组的所有元素或hash的所有value,在标量上下文返回数组或hash的元素数量

以数组为例:

my @arr = (11, 22, 33, 44);
# 列表上下文
my @arr_keys = keys @arr;
my @arr_values = values @arr;

say "@arr_keys";    # 0 1 2 3
say "@arr_values";  # 11 22 33 44

可以通过keys或values函数来迭代数组、hash的索引或值。以数组为例:

my @arr = (11, 22, 33, 44);
for(keys @arr){say $_;}   # 0 1 2 3
for(values @arr){ say $_; } # 11 22 33 44

每次调用keys和values都会重置Perl内部维护的迭代指针,因此使用keys或values来迭代数组、hash是安全的,它们总能取得数组或hash的所有元素。

另外,values获取的值是对元素的引用,因此修改values获取的值,也将修改源数据。

my @arr = (11, 22, 33, 44);
my @arr_values = values @arr;
$arr_values[0] = 111;
say "@arr_values";   # 111 22 33 44

pop push shift unshift

  • pop从数组中移除并返回最后一个元素,数组为空则返回undef
  • push向数组尾部追加一个元素或一个列表,返回追加完成后数组长度
  • shift移除并返回数组第一个元素,数组为空则返回undef
  • unshift向数组头部添加一个元素或一个列表,返回追加完成后数组长度

对于pop、shift:

# pop
my @arr1 = (11,22,33);
say pop @arr1;  # 33
say pop @arr1;  # 22
say pop @arr1;  # 11
say pop @arr1;  # undef,警告模式下会给出警告

# shift
my @arr2 = (11,22,33);
say shift @arr2;  # 11
say shift @arr2;  # 22
say shift @arr2;  # 33
say shift @arr2;  # undef,警告模式下会给出警告

对于push、unshift:

# push
my @arr1 = (11,22,33);
push @arr1, 44;      # 追加单个元素
push @arr1, 55, 66;  # 追加列表,列表的小括号被省略
say push @arr1, (77,88); # 输出8,push返回数组长度

say "@arr1";  # 11 22 33 44 55 66 77 88

# unshift
my @arr2 = (11,22,33);
unshift @arr2, 'a', 'b';
unshift @arr2, qw(aa bb);
say "@arr2"; # aa bb a b 11 22 33

push追加在效果上等价于如下代码,但效率更高:

my @arr;
for my $v (@arr) {
  # 向数组尾部追加一个元素
  $arr[++$#arr] = $v;  # 或者:$arr[~~@arr]=$v;
}

注意,Perl的数组结构和其他语言的数组结构有所不同,其他语言中,这四个函数的效率通常会比使用索引操作方式的效率要更低,但在Perl中,这四个函数的效率比使用索引的处理方式更高,这可能会让人难以置信。原因在于perl已经将这几个函数转换为opcode的方式,它们直接通过C数组索引访问底层的C数组,而如果使用Perl的索引,则先在Perl层面找到P数组的索引位置,然后再访问对应的C数组。不过,多数时候不需要考虑这些操作的效率问题。

另外,shift会导致数组元素向前挪位,unshift会导致数组元素先后挪位。在其他语言中,这样的挪位操作效率非常低,但是在Perl中,这两个操作没有任何效率损失。所以,请放心使用它们。

each

在上一小节已经详细介绍each的使用,参考:小心使用each

splice

pop/push、unshift/shift操作的都是数组的开头或者末尾。splice则可以指定操作数组中的哪个位置。

splice ARRAY
splice ARRAY,OFFSET
splice ARRAY,OFFSET,LENGTH
splice ARRAY,OFFSET,LENGTH,LIST

splice在移除元素时,在列表上下文返回被移除的元素列表,标量上下文返回最后一个被移除的元素

1.一个参数时,即splice ARRAY,表示清空ARRAY

use 5.010;
@arr=qw(perl py php shell);

@new_arr=splice @arr;
say "original arr: @arr";   # 输出:空
say "new arr: @new_arr";    # 输出原列表内容

如果splice在标量上下文,则返回最后一个被移除的元素:

use 5.010;
@arr=qw(perl py php shell);

$new_arr=splice @arr;
say "$new_arr";    # 输出:shell

2.两个参数时,即splice ARRAY,OFFSET,表示从OFFSET处开始删除元素直到结尾

注意,OFFSET可以是负数。

例如:

use 5.010;
@arr=qw(perl py php shell);

@new_arr=splice @arr,2;
say "original arr: @arr";   # 输出:perl py
say "new arr: @new_arr";    # 输出:php shell

如果offset为负数,则表示从后向前数第几个元素,-1表示最后一个元素。

use 5.010;
@arr=qw(perl py php shell);

@new_arr=splice @arr,-3;
say "original arr: @arr";    # 输出:perl
say "new arr: @new_arr";     # 输出:py php shell

3.三个参数时,即splice ARRAY,OFFSET,LENGTH,表示从OFFSET处开始向后删除LENGTH个元素

注意,LENGTH可以为负数,也可以为0,它们都有奇效。

例如:

use 5.010;
@arr=qw(perl py php shell ruby);

@new_arr=splice @arr,2,2;
say "original arr: @arr";   # 输出:perl py ruby
say "new arr: @new_arr";    # 输出:php shell

如果length为负数,则表示从offset处开始删除,直到尾部还保留-length个元素(例如length为-3时,表示尾部保留3个元素)。例如:

use 5.010;
@arr=qw(perl py php shell ruby java c c++ js);

@new_arr=splice @arr,2,-2;   # 从php开始删除,最后只保留c++和js两个元素
say "original arr: @arr";    # 输出:perl py c++ js
say "new arr: @new_arr";     # 输出:php shell ruby java c

如果正数length的长度超出了数组边界,则删除至结尾。如果负数length超出了边界,也就是保留的数量比要删除的数量还要多,这时保留优先级更高,也就是不会删除。例如,从某个位置开始删除,后面还有2个元素,但如果length=-2",则这两个元素不会被删除。

如果length为0,则表示不删除,这个在有第4个参数LIST时有用。

4.四个参数时,即splice ARRAY,OFFSET,LENGTH,LIST,表示将LIST插入到删除的位置,也就是替换数组的部分位置连续的元素。

例如:

use 5.010;
@arr=qw(perl py php shell ruby);
@list=qw(java c);

@new_arr=splice @arr,2,2,@list;
say "original arr: @arr";   # 输出:perl py java c ruby
say "new arr: @new_arr";    # 输出:php shell

如果想原地插入新元素,而不删除任何元素,可以将length设置为0,它会将新列表插入到offset的位置。

use 5.010;
@arr=qw(perl py php shell ruby);
@list=qw(java c);

@new_arr=splice @arr,2,0,@list;
say "original arr: @arr";   # 输出:perl py java c php shell ruby
say "new arr: @new_arr";    # 输出:空

注意上面php在新插入元素的后面。