哈希
哈希是一个聚合数据结构,初衷是为了实现将一组数据对应到另外一组数据。
哈希对数据的格式有点小小的要求:键必须是字符串,值是标量值。
其他编程语言也称这种数据结构为哈希表,关联数组,或字典什么的。
哈希最重要的特性就是:键是唯一的;键与键之间是无序的。
声明哈希
哈希使用记号%。声明一个哈希(词法作用域的):
my %favorite_flavors;
访问哈希单个元素使用记号$,并且用{ }包围键。
赋值:
my %favorite_flavors;
$favorite_flavors{Gabi} = 'Dark chocolate raspberry';
$favorite_flavors{Annette} = 'French vanilla';
my %favorite_flavors = (
'Gabi', 'Dark chocolate raspberry',
'Annette', 'French vanilla',
);
使用胖箭头会更清晰:
my %favorite_flavors = (
Gabi => 'Dark chocolate raspberry',
Annette => 'French vanilla',
);
胖箭头自动对前面的(裸字)字符进行引号操作:
sub name { 'Leonardo' }
my %address = (
name => '1123 Fib Place'
);
#并不会调用name()函数
#指明要调用name()函数
my %address = (
name() => '1123 Fib Place'
);
访问单个值:
my $address = $addresses{$name};
# 自动引起
my $address = $addresses{Victor};
# 不是有效的裸字,需要手动引起
my $address = $addresses{'Sue-Linn'};
# 使用“()”明确要函数调用,消除歧义
my $address = $addresses{get_name()};
哈希键必须是字符串,任何其他类型的都会被强制转成字符串:
for my $isbn (@isbns)
{
my $book = Book->fetch_by_isbn( $isbn );
$books{$book} = $book->price; # 引用转换成字符串,然后作为键。这可能不是你想要的
}
哈希键的存在性
exists操作符用来侦测给定的键是否存在,存在则返回真,否则返回假:
my %addresses =
(
Leonardo => '1123 Fib Place',
Utako => 'Cantor Hotel, Room 1',
);
say "Have Leonardo's address"
if exists $addresses{Leonardo};
say "Have Warnie's address"
if exists $addresses{Warnie};
使用exists操作符有个优势:返回值只跟键有关,和键对应的值无关。
my %false_key_value = ( 0 => '' );
ok( %false_key_value,
'hash containing false key & value
should evaluate to a true value' );
访问哈希
访问键
for my $addressee (keys %addresses)
{
say "Found an address for $addressee!";
}
访问值
for my $address (values %addresses)
{
say "Someone lives at $address";
}
访问键值对
while (my ($addressee, $address) = each %addresses)
{
say "$addressee lives at $address";
}
注意:键值对与键值对之间没有特定次序。
每个哈希只有一个迭代记录,所以不要多次使用each。有些情况下,你可能需要重置记录器,那就在空语境下使用keys或values:
# 重置记录器
keys %addresses;
while (my ($addressee, $address) = each %addresses)
{
...
}
哈希切片
类似数组切片,就是一系列的键和值。
一次性赋值多个元素:
# %cats already contains elements
@cats{qw( Jack Brad Mars Grumpy )} = (1) x 4;
等同于:
my %cats = map { $_ => 1 }
qw( Jack Brad Mars Grumpy );
大括号明确表示你使用的是哈希切片:
my @buyer_addresses = @addresses{ @buyers };
合并2个哈希非常简单:
my %addresses = ( ... );
my %canada_addresses = ( ... );
@addresses{ keys %canada_addresses }
= values %canada_addresses;
如果有相同的key,会重写已经存在的。
另外,只有操作同一个哈希,并且在keys和values操作之间没有对哈希进行任何修改才能保证对应的顺序。
空哈希
空哈希就是指没有元素的哈希。在布尔语境中为假值。只要有元素就为真值(不管键或值的真假)。
use Test::More;
my %empty;
ok( ! %empty, 'empty hash should evaluate false' );
my %false_key = ( 0 => 'true value' );
ok( %false_key, 'hash containing false key
should evaluate to true' );
my %false_value = ( 'true key' => 0 );
ok( %false_value, 'hash containing false value
should evaluate to true' );
done_testing();
在标量语境中,哈希返回一个字符串,这个字符表示哈希内部的一些细节信息(哈希桶的信息),这与真假值是一致的,所以不用在意这些细节。
在列表语境中,返回的就是键值对列表。
习惯用法
由于哈希的特性,我们经常这样用:
去重一个列表
my %uniq;
undef @uniq{ @items };
my @uniques = keys %uniq;
# @items是需要去重的数据,@uniques是去重后的
# 对哈希切片使用undef就是将所有的切片值设置为undef。
计数
my %ip_addresses;
while (my $line = <$logfile>)
{
chomp $line;
my ($ip, $resource) = analyze_line( $line );
$ip_addresses{$ip}++;
...
}
缓存
{
my %user_cache;
sub fetch_user
{
my $id = shift;
$user_cache{$id} //= create_user($id); # 如果没定义就赋值
return $user_cache{$id};
}
}
赋值默认值:
sub make_sundae
{
my %parameters = @_;
$parameters{flavor} //= 'Vanilla';
$parameters{topping} //= 'fudge';
$parameters{sprinkles} //= 100;
...
}
sub make_sundae
{
my %parameters =
(
flavor => 'Vanilla',
topping => 'fudge',
sprinkles => 100,
@_,
);
...
}
锁定哈希
有些时候你可能想要对哈希进行一些保护,比如不让别人修改你的哈希键或值;想让整个哈希为只读等。如果有这些需求,推荐你试试Hash::Util模块,肯定能满足你的要求。
强制转换
Perl中有些情形下会发生强制转换,比如以前提过的语境就可能会导致发生类型的强制转换。
布尔强制
比较典型的就是在条件表达式中,会强制转换成布尔类型。
字符串强制
使用字符串操作函数如eq,cmp,就会强制转换成字符串类型。
数字强制
使用数字操作符如==、 <、>就会强制转换成数字类型。
引用强制
使用解引用操作符会导致强制转换为引用类型。
my %users;
$users{Brad}{id} = 228;
$users{Jack}{id} = 229;
#创建哈希引用,并且赋值
缓存强制
Perl存储值时内部有2个插槽:字符串部分和数字部分。字符串化一个数值,并不会修改内部的数字部分,而是在内部字符串部分添加一个字符串值。相似的,数字化一个字符串,就会填充内部表示的数字部分而不会修改内部的字符串部分。
Perl的操作肯定有自己的倾向,到底优先使用哪一部分呢?布尔类型就更倾向字符串。比如,一个值可能已经缓存了内部表示(不是你期望的形式),这时候如果你使用隐式转换,可能结果不是你期望的。这是个极其罕见情形,几乎不会遇到。但是知道这个细节可能有一天能帮助到你。
双变量
Perl中的值在内部天然就不是由单一部分组成,通过上文我们已经知道了。通过双变量这种形式,用户能直接使用这个特性。系统模块Scalar::Util提供了函数dualvar(),通过这个函数我们能绕过Perl的强制策略(隐式转换),直接操作变量的字符串和数字部分:
use Scalar::Util 'dualvar';
my $false_name = dualvar 0, 'Sparkles & Blue';
say 'Boolean true!' if !! $false_name;
say 'Numeric false!' unless 0 + $false_name;
say 'String true!' if '' . $false_name;