`
bbsunchen
  • 浏览: 231535 次
  • 性别: Icon_minigender_1
  • 来自: 天朝帝都
社区版块
存档分类
最新评论

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 镜象!

 

 

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

分享到:
评论
1 楼 bbsunchen 2010-05-18  
自己坐沙发

相关推荐

    Python库 | django-annoying-0.7.1.tar.gz

    **Python库 | django-annoying-0.7.1** `django-annoying` 是一个非常实用的Python库,专为Django框架设计,它包含了一系列方便开发者的小工具和辅助函数,能够提高开发效率并简化代码。在这个0.7.1版本中,我们能...

    Most Annoying Quiz in the world-crx插件

    【标题】"Most Annoying Quiz in the world-crx插件"是一款专为英语使用者设计的浏览器扩展,其目标是让用户体验到世界上最烦人的测验挑战。这款插件的特别之处在于,它增强了用户在参与BuzzFeed等流行在线测验时的...

    poj 3363 Annoying painting tool.md

    poj 3363 Annoying painting tool.md

    世界上最烦人的测验「Most Annoying Quiz in the world」-crx插件

    在世界上最恼人的Quize,你应该感到不好意思采取BuzzFeed问题 扩展了功能,可让您在进行嗡嗡声测验时告诉您 支持语言:English (United States)

    PyPI 官网下载 | django-annoying-0.4.linux-i686.tar.gz

    标题 "PyPI 官网下载 | django-annoying-0.4.linux-i686.tar.gz" 提供了几个关键信息。首先,`PyPI` 是 Python 的包索引(Python Package Index),它是 Python 开发者发布和下载开源软件库的主要平台。这个标题表明...

    VBA and Macros: Microsoft Excel 2010 PDF

    Most beginners struggle with the macro security issue and the book deals with these annoying issues in the very first chapter itself. Lots of Short, simple, and extremely powerful examples: This ...

    PyPI 官网下载 | django_annoying-0.10.1-py2-none-any.whl

    标题"PyPI 官网下载 | django_annoying-0.10.1-py2-none-any.whl" 提供的信息表明,这是一个从Python的官方包索引服务PyPI上下载的软件包。"django_annoying"是这个包的名字,而"0.10.1"则是它的版本号。"py2-none-...

    Network Security: Private Communication in a Public World, Second Edition

    Network Security: Private Communication in a Public World, Second Edition By Charlie Kaufman, Radia Perlman, Mike Speciner ............................................... Publisher: Prentice Hall ...

    Use the PtInRect API function to create an annoying program.

    标题中的“Use the PtInRect API function to create an annoying program”指的是利用Windows API中的PtInRect函数来编写一个烦人的程序。这个API函数是Windows图形设备接口(GDI)的一部分,用于判断一个点是否在...

    Learn CSS In A DAY

    You also have to pay the high fees, month to month, and what is even more annoying is this: you will probably have to go to a special place in order to practice the CSS techniques! You see, when it ...

    imx-audmux.rar_The Number

    There is an annoying discontinuity in the SSI numbering with regard to the Linux number of the devices.

    Annoying TalkBot for DC-开源

    《VB6打造的DC聊天机器人:Annoying TalkBot开源解析》 在IT领域,开源软件一直是推动技术创新和发展的重要力量。今天我们将深入探讨一个由VB6(Visual Basic 6)编程语言开发的开源项目——"Annoying TalkBot for ...

    Learn.Cplusplus.In.A.DAY.1519318588.epub

    You also have to pay the high fees, month to month, and what is even more annoying is this: you will probably have to go to a special place in order to practice the new programming language!...

    [BVKER6487]AnnoyingVillagerv0.42.zip

    "Annoying Villager"可能是一个模组,它可能会修改游戏中村民的行为,使其变得更烦人,或者以某种方式增加了游戏的挑战性或互动性。然而,没有更多的信息,我无法提供更深入的解释或教程。 如果你能提供关于这个...

    annoying-game:不要做。 真的。 别

    这个名为"annoying-game"的项目可能是一个基于JavaScript的小游戏,旨在提供一种互动体验,但根据标题和描述,它似乎有意设计得令人不悦,可能是为了某种幽默或者挑战用户的忍耐力。 JavaScript作为Web开发的核心...

    annoying-cloud:Javascript 运动检测应用程序

    annoying-cloud 1.取得视讯装置来源:getUserMedia 使用navigator.getUserMedia()这个web API,让浏览器跳出取得webcam source权限的东东,取得权限后把webcam stream流向 具体做法就是这个 navigator . ...

    Very Annoying High Pitch Dog Whistle Sound-crx插件

    【标题解析】:“Very Annoying High Pitch Dog Whistle Sound-crx插件”是一个专为浏览器设计的扩展程序,其核心功能是发出一种极高频的声音,这种声音类似于狗哨,但频率更高,人耳也能察觉。 【描述详解】:描述...

    Python Introduction

    Python是一种广泛使用的高级编程语言,它以其易于阅读的代码、强大的库支持、跨平台兼容性以及在数据分析、人工智能、科学计算和网络开发等领域的广泛应用而闻名。Python的引入是为了提供一种比C或C++等系统编程语言...

    Avoid:Annoying GUI

    在IT行业中,GUI(图形用户界面)是软件与用户交互的主要方式,但有时设计不当的GUI可能会给用户带来困扰。本篇文章将详细探讨如何在使用C#开发GUI应用程序时,避免设计出令人厌烦的界面,提升用户体验。...

Global site tag (gtag.js) - Google Analytics