第六章:
什么是哈希?
哈希是一种数据结构,和数组的相同之处在于:可以容纳很多值(没有上限),并能随机存取。而区别在于:不像数组是以数字来检索,哈希是以名字来检索。也就是说检索用的键不是数字,而是保证唯一的字符串。
哈希值得一提的是:虽然这些键是唯一的,但是它们对应的值可以重复。哈希的值可以是数字、字符串、undef,或是这些类型的组合。但是哈希键则全都必须是唯一的字符串。
为什么使用哈希?
可以将一组数据对应到另一组数据。
按名字找姓:
名字可以作为键,而姓可以成为值。这当然需要限定名字是唯一的,如果出现了两个叫做randal的人就行不通了。通过哈希可以按任何人的名字找到相应的姓。例如以tom为值取得值phoenix。
用主机名找IP地址
你也许知道在因特网上,每台计算机同时拥有一个主机名如www.stonehenge.com,以及一个IP地址如123.45.67.89。这因为机器喜欢和数字打交通,但是一般人比较记得名字。主机名是唯一的字符串,可以用来作为哈希的键。通过哈希可以按主机名找到相应的IP地址。
另一种思考方式是将哈希当成极其简单的数据库,其中每个键的“名下”都可以存储相应的数据,事实上只要问题中带有找出重复、唯一、交叉引用、查表之类的字眼,就很有可能会用到哈希。
访问哈希元素:
$hash{$some_key}
这和访问数组的做法类似,只是使用了花括号而非方括号来引出索引,而且现的键表达式是字符串,而非数字:
$famil_name{"fred"} = "flintstone";
$famil_name{"barney"} = "rubble";
上面两个也可以整形成下面这个样子:
foreach $person (qw< barney fred >) {
print "I've heard of $person $family_name{$person}.\n";
}
$foo = "bar";
print $family_name{ $foo . "ney" }; #打印“rubble"
$family_name{"fred"} = "astaire"; #组已有的元素赋上新值
$bedrock = $family_name{"fred"}; #得到"astaire";早先的值已不存在
$family_name{"wilma"} = "flintstone"; #增加一个新的键值对
$family_name{"betty"} .= $family_name{"barney"};
|
访问整个哈希:
要指代整个哈希,可以用百分号(%)作为前缀。因此前面我们使用的哈希应该称为%family_name。
哈希可以被转换成列表,反过来也行。对哈希赋值会带来列表赋值的上下文,列表的元素应该应该是键/值对。
%some_hash = ("foo", 35, "bar", 12.4, 2.5, "hello",
"wilma", 1.72e30, "betty", "bye\n");
|
在列表上下文中,哈希会自动变成一些简单的键/值对:
@any_array = %some_hash;
我们把这个变换叫做哈希松绑,将它变成键/值对的列表。当然键/值对也不一定按照当初赋值的顺序松绑。
print "@any_array\n";
#可能会给出下面的结果:
#betty bye (以及一个换行) wilma 1.72e+30 foo 35 2.5 hello bar 12.4
哈希赋值:
%new_hash = %old_hash;
更常见的是以某种方式转换哈希,如建立一个反转哈希:
%inverse_hash = reverse %any_hash;
|
胖箭头:
在哈希赋值时常常会发现列表中的键/值对并不易区分开来。例如在下面的赋值中,任何人都要逐个扫描列表成员,同时默念着:
键、值,键、值等等
如:
%some_hash = ("foo", 35, "bar", 12.4, 2.5, "hello",
"wilma", 1.72e30, "betty", "bye\n");
如果用胖箭头的方式就很简单了。
my %last_name = ( #哈希也可以是词法变量
"fred" => "flintstone",
"dino" => undef,
"barney" => "rubble",
"betty" => "rubble",
);
|
#还有一个瘦箭头->,它是用来服务于引用(reference)的,可以参考perlreftut或者Perlfre了解这个高级话题。
#胖箭头左边的任何祼词都能自动引用,因此胖箭头左边的课词不需要引号。另外哈希键所在的花括号也有类似的自动引用能力。
#裸词的定义是字母、数字和下划线的序列,可以用加号或减号开头,但不能用数字开头。
哈希函数就是围绕哈希很多有用的函数。
keys 和 values 函数:
keys函数能返回哈希的皱列表,而values函数能返回值列表。如果哈希没有任何成员,则两个函数都返回空列表。
my %hash = ("a" => 1, "b" => 2, "c" => 3);
my @k = keys %hash;
my @v = values %hash;
|
这样@k会包含"a","b"和"c",而@v则会包含1、2和3,当然顺序有所不同,但是可以确定返回的键列表和值列表的顺序是一样的。
my $count = keys %hash; #得到3,也就是说有三对键值
偶尔也能看到别人的程序里把哈希当成布尔表达式来判断真假。
if (%hash) {
print "That was a true value!\n";
}
each 函数:
如果需要罗列哈希的每个键/值对,常见的写法就是使用each函数,它能用两个元素的列表形式返回键/值对。
每次对同一个哈希调用此函数,它就会返回下一组键/值对,直到所有的元素都被访问过,也就是再没有任何新的键/值对,些时
each会返回空列表。
实际使用时,唯一适合使用each的地方就是while循环中,如下所示:
while ( ($key, $value) = each %hash ) {
print "$key => $value\n";
}
依次处理哈希的方法:
foreach $key (sort keys %hash) {
$value = $hash{$key};
print "$key => $value\n";
#或者,可以略去额外的$value变量;
#print "$key => $hash{$key}\n";
}
哈希的典型应用:
Bedrock图书馆的借阅信息程序以一个哈希记录每个人借出了几本书:
$books{"fred"} = 3;
$books{"wilma"} = 1;
判断某项哈希元素的真假:
if ( $books{$someone}) {
print "$someone has at least one book checked out.\n";
}
$books{"barney"} = 0; #现在没有借阅图书
$books{"pebbles"} = undef; #从未借阅过图书,这是张新办的借书卡
exists函数:
检查哈希中是否有某个键(也就是某人是否有借书证)可以使用exists函数,它能返回真或假,分别表示键存在与否。和键的值无关:
if (exists $books{"dino"}) {
print "Hey, there's a library card for dino!\n";
}
delete 函数:
delete函数能从哈希中删除指定的键及其相对应的值。假如没有这样的键,它就会直接结束,而不会出现任何警告或错误信息。
my $person = "betty";
delete $books{$person}; #撤回$person的借书卡
哈希值内插:
可以将单一哈希元素内插到双引号引起的字符串中:
foreach $person (sort keys %books) { #按次序访问每位借问者
if ($books{$person}) {
print "$person has $books{person} items\n"; #fred借了3本书
}
%ENV哈希:
Perl程序既然运行在某个环境中,就需要周围的影响有所感知。Perl获取这些信息的方法是存取%ENV哈希。
print "PATH is $ENV{PATH}\n";
习题:
1、
=======================================================
#!/usr/bin/perl -w
%given_for_first = (
"fred" => flintstone,
"barney" => rubble,
"wilma" => flinstone);
print "Input given_name:\n";
$given_name = <STDIN>;
chomp($given_name); #这里一定要加,开始我没有加,测试了好久都报:Use of uninitialized value in concatenation (.) or string at hash2 line 9, <STDIN> line 1.
$output = $given_for_first{$given_name} ;
printf "output firstname is $output";
#my @k = keys %given_for_first;
#my @v = values %given_for_first;
========================================================
#!/usr/bin/perl -w
my $last_name = qw{
fred flintstone
barney rubble
wilma flintstone
};
print "Please enter a first name:";
chomp(my $name = <STDIN>);
print "That's $name $last_name{$name}.\n";
=========================================================
2、
#!/usr/bin/perl -w
my(@words, %count, $word); #声明变量(可以省略)
chomp(@words = <STDIN>);
foreach $word (@words) {
$count{$word} += 1; #或者$count{$word} = $count{$word} + 1;
}
foreach $word (keys %count) { #或者sort keys %count
print "$word has seen $count{$word} times.\n";
}
==========================================================
3、
#!/usr/bin/perl -w
my $longest = 0;
foreach my $key ( keys %ENV ) {
my $key_length = length{ $key };
$longest = $key_length if $key_lenth > $longest;
}
foreach my $key ( sort keys %ENV ) {
printf "%-${longest)s %s\n", $key, $ENV{$key};
}
|