Annoying hash in perl

感觉Perl里很麻烦的就是hash的变换了,这里给出一些复杂的变换

 散列的数组

如果你有一堆记录,你想顺序访问它们,并且每条记录本身包含一个键字/数值对,那么散列的数组就很有用。在本章中,散列的数组比其他结构用得少一些。

 

1 组成一个散列的数组

你可以用下面方法创建一个匿名散列的数组:

 

   @AoH = (
      {
         husband => "barney",
         wife    => "betty",
         son    => "bamm bamm",   
      },
      {
         husband => "george",
         wife    => "jane",
         son    => "elroy",
      },
      {
         husband => "homer",
         wife    => "marge",
         son    => "bart",
      },
   );

 

 

要向数组中增加另外一个散列,你可以简单地说:

 

push @AoH, { husband => "fred", wife => "wilma", daughter => "pebbles" };

 

 

 

2 生成散列的数组

下面是一些填充散列数组的技巧。要从一个文件中读取下面的格式:

 

   husband=fred friend=barney

你可以使用下面两个循环之一:

 

   while (<>) {
      $rec = {};
      for $field ( split ) {
         ($key, $value) = split /=/, $field;
         $rec->{$key} = $value;
      }
      push @AoH, $rec;
   }

   while (<>) {
      push @AoH, { split /[\s=]+/ };
   }

 

 

如果你有一个子过程 get_next_pair 返回一个键字/数值对,那么你可以用它利用下面两个循环之一来填充 @AoH:

 

   while ( @fields = get_next_pari()) {
      push @AoH, {@fields};
   }

   while (<>) {
      push @AoH, { get_next_pair($_) };
   }

 

 

你可以象下面这样向一个现存的散列附加新的成员:

 

   $AoH[0]{pet} = "dino";
   $AoH[2]{pet} = "santa's little helper";

 

 

 

3 访问和打印散列的数组

你可以用下面的方法设置一个特定散列的数值/键字对:

 

 

   
$AoH[0]{husband} = "fred";
 

 

 

要把第二个数组的丈夫(husband)变成大写,用一个替换:

 

 

 
   $AoH[1]{husband} =~ s/(\w)/\u$1/;
 

 

 

你可以用下面的方法打印所有的数据:

 

 

 
   for $href ( @AoH ) {
      print "{ ";
      for $role ( keys %$href ) {
         print "$role=$href->{$role} ";
      }
      print "}\n";
   }
 

 

以及带着引用打印:

 

 

 
   for $i ( 0 .. $#AoH ) {
      print "$i is { ";
      for $role ( keys %{ $AoH[$i] } ) {
         print "$role=$AoH[$i]{$role} ";
      }
      print "}\n";
   }
 

 

 

 散列的散列

多维的散列是 Perl 里面最灵活的嵌套结构。它就好象绑定一个记录,该记录本身包含其他记录。在每个层次上,你都用一个字串(必要时引起)做该散列的索引。不过,你要记住散列里的键字/数值对不会以任何特定的顺序出现;你可以使用 sort 函数以你喜欢的任何顺序检索这些配对。

 

1 构成一个散列的散列

你可以用下面方法创建一个匿名散列的散列:

 

 

 
   %HoH = (
      flintstones => {
         husband => "fred",
         pal    => "barney",
      },
      jetsons => {
         husband => "george",
         wife    => "jane",
         "his boy" => "elroy",      # 键字需要引号
      },
      simpsons => {
         husband => "homer",
         wife    => "marge",
         kid     => "bart",
      },

   );
 

 

要向 %HoH 中增加另外一个匿名散列,你可以简单地说:

 

 

 
   $HoH{ mash } = {
      captain => "pierce",
      major   => "burns",
      corporal=> "radar",
   }
 

 

 

2 生成散列的散列

下面是一些填充一个散列的散列的技巧。要从一个下面格式的文件里读取数据:

 

flintstones
husband=fred pal=barney wife=wilma pet=dino

 

你可以使用下面两个循环之一:

 

 

 
   while( <> ){
      next unless s/^(.*?):\S*//;
      $who = $1;
      for $field ( split ) {
         ($key, $value) = split /=/, $field;
         $HoH{$who}{$key} = $value;
      }
   }

   while( <> ){
      next unless s/^(.*?):\S*//;
      $who = $1;
      $rec = {};
      $HoH{$who} = $rec;
      for $field ( split ) {
         ($key, $value) = split /=/, $field;
         $rec->{$key} = $value;
      }
   }
 

 

如果你有一个子过程 get_family 返回一个键字/数值列表对,那么你可以拿下面三种方法的任何一种,用它填充 %HoH:

 

 

 
   for $group ("simpsons", "jetsons", "flintstones" ) {
      $HoH{$group} = {get_family($group)};
   }

   for $group ( "simpsons", "jetsons", "flintstones" ) {
      @members = get_family($group);
      $HoH{$group} = {@menbers};
   }

   sub hash_families {
      my @ret;
      for $group (@_) {
         push @ret, $group, {get_family($group)};
      }
      return @ret;
   }

   %HoH = hash_families( "simpsons", "jetsons", "flintstones" );
 

 

你可以用下面的方法向一个现有的散列附加新的成员:

 

 

 
   %new_floks = (
      wife => "wilma",
      pet  => "dino",
   );

   for $what (keys %new_floks) {
      $HoH{flintstones}{$what} = $new_floks{$what};
   }
 

 

 

3 访问和打印散列的散列

你可以用下面的方法设置键字/数值对:

 

 

 
  $HoH{flintstones}{wife} = "wilma";
 

 

 

要把某个键字/数值对变成大写,对该元素应用一个替换:

 

 

  
 $HoH{jetsons}{'his boy'} =~ s/(\w)/\u$1/;
 

 

 

你可以用先后遍历内外层散列键字的方法打印所有家族:

 

 

 
   for $family ( keys %HoH ) {
      print "$family: ";
      for $role ( keys %{ $HoH{$family} } ){
         print "$role=$person ";
      }
      print "\n";
   }
 

 

在非常大的散列里,可能用 each 同时把键字和数值都检索出来会略微快一些(这样做可以避免排序):

 

 

 
   while ( ($family, $roles) = each %HoH ) {
      print "$family: ";
      while ( ($role, $person) = each %$roles ) {
         print "$role=$person";
      }
      print "\n";
   }
 

 

(糟糕的是,需要存储的是那个大的散列,否则你在打印输出里就永远找不到你要的东西.)你可以用下面的方法先对家族排序然后再对脚色排序:

 

 

 
   for $family ( sort keys %HoH ) {
      print "$family:  ";
      for $role ( sort keys %{ $HoH{$family} } ) {
         print "$role=$HoH{$family}{$role} ";
      }
      print "\n";
   }
 

 

要按照家族的编号排序(而不是 ASCII 码(或者 utf8 码)),你可以在一个标量环境里使用 keys:

 

 

 
   for $family ( sort { keys %{$HoH{$a} } <=> keys %{$HoH{$b}}} keys %HoH ) {
      print "$family: ";
      for $role ( sort keys %{ $HoH{$family} } ) {
         print "$role=$HoH{$family}{$role};
      }
      print "\n";
   }
 

 

要以某种固定的顺序对一个家族进行排序,你可以给每个成员赋予一个等级来实现:

 

 

 
   $i = 0;
   for ( qw(husband wife son daughter pal pet) ) { $rank{$_} = ++$i }

   for $family ( sort { keys %{$HoH{$a} } <=> keys %{$HoH{$b}}} keys %HoH ) {
      print "$family: ";
      for $role ( sort { $rank{$a} <=> $rank{$b} } keys %{ $HoH{$family} }) {
         print "$role=$HoH{$family}{$role} ";
      }
   print "\n";
   }
 

 

 

 函数的散列

在使用 Perl 书写一个复杂的应用或者网络服务的时候,你可能需要给你的用户制作一大堆命令供他们使用。这样的程序可能有象下面这样的代码来检查用户的选择,然后采取相应的动作:

 

 

 
if    ($cmd =~ /^exit$/i)     { exit }
elsif ($cmd =~ /^help$/i)     { show_help() }
elsif ($cmd =~ /^watch$/i)    { $watch = 1 }
elsif ($cmd =~ /^mail$/i)     { mail_msg($msg) }
elsif ($cmd =~ /^edit$/i)     { $edited++; editmsg($msg); }
elsif ($cmd =~ /^delete$/i)   { confirm_kill() }
else {
    warn "Unknown command: `$cmd'; Try `help' next time\n";
}
 

 

你还可以在你的数据结构里保存指向函数的引用,就象你可以存储指向数组或者散列的引用一样:

 

 

 
%HoF = (                           # Compose a hash of functions
    exit    =>  sub { exit },
    help    =>  \&show_help,
    watch   =>  sub { $watch = 1 },
    mail    =>  sub { mail_msg($msg) },
    edit    =>  sub { $edited++; editmsg($msg); },
    delete  =>  \&confirm_kill,
);

if   ($HoF{lc $cmd}) { $HoF{lc $cmd}->() }   # Call function
else { warn "Unknown command: `$cmd'; Try `help' next time\n" }
 

 

在倒数第二行里,我们检查了声明的命令名字(小写)是否在我们的“遣送表”%HoF 里存在。如果是,我们调用响应的命令,方法是把散列值当作一个函数进行解引用并且给该函数传递一个空的参数列表。我们也可以用 &{ $HoF{lc $cmd} }( ) 对散列值进行解引用,或者,在 Perl 5.6 里,可以简单地是 $HoF{lc $cmd} ()。

 

 更灵活的记录

到目前为止,我们在本章看到的都是简单的,两层的,同质的数据结构:每个元素包含同样类型的引用,同时所有其他元素都在该层。数据结构当然可以不是这样的。任何元素都可以保存任意类型的标量,这就意味着它可以是一个字串,一个数字,或者指向任何东西的引用。这个引用可以是一个数组或者散列引用,或者一个伪散列,或者是一个指向命名或者匿名函数的引用,或者一个对象。你唯一不能干的事情就是向一个标量里填充多个引用物。如果你发现自己在做这种尝试,那就表示着你需要一个数组或者散列引用把多个数值压缩成一个。

在随后的节里,你将看到一些代码的例子,这些代码设计成可以演示许多你想存储在一个记录里的许多可能类型的数据,我们将用散列引用来实现它们。这些键字都是大写字串,这是我们时常使用的一个习惯(有时候也不用这个习惯,但只是偶然不用)——如果该散列被用做一个特定的记录类型。

 

1 更灵活的记录的组合,访问和打印

下面是一个带有六种完全不同的域的记录:

 

 

 
   $rec = {
      TEXT       => $string,
      SEQUENCE    => [ @old_values ],
      LOOKUP    => { %some_table },
      THATCODE   => sub { $_[0] ** $_[1] },
      HANDLE   => \*STDOUT,
   };
 

 

TEXT 域是一个简单的字串。因此你可以简单的打印它:

 

   print $rec->{TEXT};

 

SEQUENCE 和 LOOKUP 都是普通的数组和散列引用:

 

 

 
   print $rec->{SEQUENCE   }[0];
   $last = pop @{ $rec->{SEQUENCE} };

   print $rec->{LOOKUP}{"key"};
   ($first_k, $first_v) = each %{ $rec->{LOOKUP} };
 

 

 

THATCODE 是一个命名子过程而 THISCODE 是一个匿名子过程,但是它们的调用是一样的:

 

   $that_answer = $rec->{THATCODE}->($arg1, $arg2);
   $this_answer = $rec->{THISCODE}->($arg1, $arg2);

 

再加上一对花括弧,你可以把 $rec->{HANDLE} 看作一个间接的对象:

 

   print { $rec->{HANDLE} } "a string \n";

如果你在使用 FileHandle? 模块,你甚至可以把该句柄看作一个普通的对象:

 

 

 
   use FileHandle;
   $rec->{HANDLE}->autoflush(1);
   $rec->{HANDLE}->print("a string\n");
 

 

 

 

2 甚至更灵活的记录的组合,访问和打印

自然,你的数据结构的域本身也可以是任意复杂的数据结构:

 

 

 
%TV = (
    flintstones => {
        series   => "flintstones",
        nights   => [ "monday", "thursday", "friday" ],
        members  => [
            { name => "fred",    role => "husband", age  => 36, },
            { name => "wilma",   role => "wife",    age  => 31, },
            { name => "pebbles", role => "kid",     age  =>  4, },
        ],
    },


    jetsons     => {
        series   => "jetsons",
        nights   => [ "wednesday", "saturday" ],
        members  => [
            { name => "george",  role => "husband", age  => 41, },
            { name => "jane",    role => "wife",    age  => 39, },
            { name => "elroy",   role => "kid",     age  =>  9, },
        ],
    },

    simpsons    => {
        series   => "simpsons",
        nights   => [ "monday" ],
        members  => [
            { name => "homer", role => "husband", age => 34, },
            { name => "marge", role => "wife",    age => 37, },
            { name => "bart",  role => "kid",     age => 11, },
        ],
    },
);
 

 

 

3 复杂记录散列的生成

因为 Perl 分析复杂数据结构相当不错,因此你可以把你的数据声明作为 Perl 代码放到一个独立的文件里,然后用 do 或者 require 等内建的函数把它们装载进来。另外一种流行的方法是使用 CPAN 模块(比如 XML::Parser)装载那些用其他语言(比如 XML)表示的任意数据结构。

你可以分片地制作数据结构:

 

   $rec = {};
   $rec->{series} = "flintstones";
   $rec->{nights} = [ find_days()];

 

或者从文件里把它们读取进来(在这里,我们假设文件的格式是 field=value 语法):

 

 

 
   @members = ();
   while (<>) {
      %fields = split /[\s=]+/;
      push @members, {%fields};
   }
   $rec->{members} = [ @members ];
 

 

然后以一个子域为键字,把它们堆积到更大的数据结构里:

$TV{ $rec->{series} } = $rec;

你可以使用额外的指针域来避免数据的复制。比如,你可能需要在一个人的记录里包含一个“kids” (孩子)数据域,这个域可能是一个数组,该数组包含着指向这个孩子自己记录的引用。通过把你的数据结构的一部分指向其他的部分,你可以避免因为在一个地方更新数据而没有在其他地方更新数据造成的数据倾斜:

 

 

 
   for $family (keys %TV) {
      my $rec = $TV{$family};      # 临时指针
      @kids = ();
      for $person ( @{$rec->{members}}) {
         if ($person->{role} =~ /kid|son|daughter/) {
            push @kids, $person;
         }
      }
      # $rec 和 $TV{$family} 指向相同的数据!
      $rec->{kids} = [@kids];
   }
 

 

这里的 $rec->{kids} = [@kids] 赋值拷贝数组内容——但它们只是简单的引用,而没有拷贝数据。这就意味着如果你给 Bart 赋予下面这样的年龄:

$TV{simpsons}{kids}[0]{age}++; # 增加到 12

那么你就会看到下面的结果,因为 $TV{simpsons}{kids}[0] 和 $TV{simpsons}{members}[2] 都指向相同的下层匿名散列表:

print $TV{simpsons}{members}[2]{age}; # 也打印 12

现在你打印整个 %TV 结构:

 

 

 
for $family ( keys %TV ) {
    print "the $family";
    print " is on ", join (" and ", @{ $TV{$family}{nights} }), "\n";
    print "its members are:\n";
    for $who ( @{ $TV{$family}{members} } ) {
        print " $who->{name} ($who->{role}), age $who->{age}\n";
    }
    print "children: ";
    print join (", ", map { $_->{name} } @{ $TV{$family}{kids} } );
    print "\n\n";
}
 

 

 

 保存数据结构

如果你想保存你的数据结构以便以后用于其他程序,那么你有很多方法可以用。最简单的方法就是使用 Perl 的 Data::Dumper 模块,它把一个(可能是自参考的)数据结构变成一个字串,你可以把这个字串保存在程序外部,以后用 eval 或者 do 重新组成:

 

   use Data::Dumper;
   $Data::Dumper::Purity = 1;      # 因为 %TV 是自参考的
   open (FILE, "> tvinfo.perldata")    or die "can't open tvinfo: $!";
   print FILE Data::Dumper->Dump([\%TV], ['*TV']);
   close FILE         or die "can't close tvinfo: $!";

 

 

 

其他的程序(或者同一个程序)可以稍后从文件里把它读回来:

 

 

   open (FILE, "< tvinfo.perldata")   or die "can't open tvinfo: $!";
   undef $/;            # 一次把整个文件读取进来
   eval ;            # 重新创建 %TV
   die "can't recreate tv data from tvinfo.perldata: $@" if $@;
   close FILE         or die "can't close tvinfo: $!";
   print $TV{simpsons}{members}[2]{age};

 

 

 

或者简单的是:

 

   do "tvinfo.perldata"      or die "can't recreate tvinfo: $! $@";
   print $TV{simpsons}{members}[2]{age};

 

还有许多其他的解决方法可以用,它们的存储格式的范围从打包的二进制(非常快)到 XML(互换性非常好)。检查一下靠近你的 CPAN 镜象!

 

 

文章的部分内容转自【传送门】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值