Learning Perl 学习笔记 Ch14 字符串与排序

  1. Perl提供一系列强大的字符串处理函数,来处理它90%情况下要处理的和文本相关的工作。其中用于在字符串内查找匹配字串位置的函数就是indexindex函数有三个参数,分别表示待匹配字符串,需要匹配的子串、匹配开始位置,其中第三个参数是可选的。
index ($match_string, $substring, $start_position);

index函数查找子串在待匹配的字符串中第一次出现的位置,从左侧开始数,0表示第一个元素(也可以理解为字符串第一次出现子串前需要跳过的元素个数),如果找不到匹配的位置,则返回-1
demo14-1:

#!/usr/bin/perl
$hello_string = "Hello world";
$pos = index ($hello_string, "l");
while($pos >= 0){
    print "I found 'l' in '$hello_string' at '$pos'.\n";
    $pos = index ($hello_string, "l", $pos + 1);
}
./demo14-1
I found 'l' in 'Hello world' at '2'.
I found 'l' in 'Hello world' at '3'.
I found 'l' in 'Hello world' at '9'.

rindex函数的功能和index功能相同,唯一的区别在于它从右往左(从字符串的结尾向前)寻找匹配字串的位置(但是匹配的顺序和代表匹配的位置的返回值还是从左往右的),第三个参数对应的也指向从右往左开始匹配的位置
demo14-2:

#!/usr/bin/perl
$string = "abc def def ghi";
print $string."\nPlease type in the matching substring: ";
chomp($substring = <STDIN>);
printf "I found '%s' in '%s' at '%d' from left to right and '%d' from right to left.\n",
    $substring,
        $string,
        index ($string, $substring),
    rindex ($string, $substring);
 ./demo14-2
abc def def ghi
Please type in the matching substring: def
I found 'def' in 'abc def def ghi' at '4' from left to right and '8' from right to left.
./demo14-2
abc def def ghi
Please type in the matching substring: abc
I found 'abc' in 'abc def def ghi' at '0' from left to right and '0' from right to left.
  1. substr操作符的主要功能是按需截断字符串,但它实际上能做更多。substr接受三个参数,第一个参数是字符串,第二个是截断子串开始的位置,第三个是子串的长度
$part = substr($string, $initial_position, $length);

第三个参数是可选的,如果没有第三个参数,子串会从开始位置一直截断到字符串结束。另外,如果第三个参数超过了子串的最大长度(从截断开始位置到字符串结束)那么子串就到字符串为止,不会补充字符以填满参数规定的长度。
demo14-3:

#!/usr/bin/perl
$string = "Thomas Jefferson";
$substring  = substr ($string, 6);
print $substring."\n";
$substring = substr($string, 5, 3);
print $substring."\n";
./demo14-3
 Jefferson
s J

有一点不同的是,第二参数标记截断子串开始位置的整数也可以是负数,和在数组角标中的作用相同,负数表示从字符串结尾向前数的位置,-3就表示从从字符串倒数第三个字符开始截断,-1则表示只截取字符串最后一个字符(负数不会改变截断的方向)


substr操作符可以和index联用,实现截断指定子串的内容:
demo14-4:

#!/usr/bin/perl
$name = "Thomas Jefferson";
$last_name = substr ($name, index($name, " ") + 1);
print $last_name."\n";
./demo14-4
Jefferson

另一个特殊的用法是用substr实现替换功能,如果待截断的字符串是变量,那么可以用如下的代码将截断的子串进行替换

substr($string, index($string, "test")) = "success";

需要替换的字符串不必和原来的字符串长度相同
如果需要更复杂的功能还可以结合正则替换和绑定操作符

substr($string, index($string, "name:"), 10) = s/^name(.)*/Name:$1/;

示例demo14-5:

#!/usr/bin/perl
$string = "Name: Max Gender:Male";
print "The original string: '$string'"."\n";
substr($string, 0, index($string, "Gender")) = "Name: Caroline ";
substr($string, index ($string, "Gender")) =~ s/Male/Female/;
print "Modified String: '".$string."'\n";
./demo14-5
The original string: 'Name: Max Gender:Male'
Modified String: 'Name: Caroline Gender:Female'

作为一种简化版的实现,substrindex结合起来通常要比正则表达式快一点。除了上面这种用法,substr也提供传统的函数调用方式实现替换,新增一个参数传递替换后的字符串(这种情况,第三个参数不能省略):
demo14-6:

#!/usr/bin/perl
$string = "Air China CA987 Beijing to Los Angeles";
$return_string = substr($string, 10, 5, "CA887");
print "The function substr return: '$return_string'\n";
print "The original string: '$string'\n";
./demo14-6
The function substr return: 'CA987'
The original string: 'Air China CA887 Beijing to Los Angeles'

  1. sprintf可以和printf一样对字符串进行格式化处理,但会把格式化后的字符串返回而不是打印,所以可以和程序进行更好的结合
    demo14-7:
#!/usr/bin/perl
$year = 2019;
$month = 6;
$day = 22;
$hour = 5;
$minute = 25;
$second = 0;
$result = sprintf "%4d/%02d/%02d %2d:%2d:%2d", $year, $month, $day, $hour, $minute, $second;
print $result."\n";

注意到%02d这种格式应用了前置零,格式化结果会在不足两位时,用0填补,可以对比下面展示的6月和5点,一个是有前置零的,另一个则没有

./demo14-7
2019/06/22  5:25: 0

下面这个例子将对财务数据进行格式化,格式化的要求有:

  1. 四舍五入到两位小数
  2. 每三位用逗号隔开
    四舍五入可以用格式化字符串%.2f实现精确到小数点后两位,每三位用逗号隔开则需要使用s///进行替换,特别需要注意的是,需要从个位数向前数三个数,保证不足三个数的情况只发生在最高位。
    demo14-8:
#!/usr/bin/perl
sub big_money{
  $number = sprintf "%.2f", shift @_;  #对数据进行第一部处理:精确到小数点后两位
  1 while $number =~ s/(-?\d+)(\d\d\d)/$1,$2/;  #添加逗号
  $number =~ s/^(-?)/$1\$/;  #在开始位置添加美元$符号
  $number;   #返回
}

print "Please type in the number:";
chomp(my $input = <STDIN>);
my $result = &big_money($input);
print "The result is: $result\n";
./demo14-8
Please type in the number:-12569853200.369
The result is: -$12,569,853,200.37

这个程序的特殊之处在于1 while $number =~ s/(-?\d+)(\d\d\d)/$1,$2/;这一行,循环体的内容是1,所以没有任何意义,循环的作用体现在循环条件判断语句上:$number =~ s/(-?\d+)(\d\d\d)/$1,$2/,利用正则表达式量词匹配的贪婪特性,每次总会在最右侧合适的位置加上逗号,直到没有更多合适的位置,返回false。因为负数符号是可选的,所以符号的量词是“0或1次”


4 Perl中的sort函数可以实现对列表进行排序,但是默认只能按照ASCII码序进行排序,Perl支持扩展sort函数,实现自定义排序规则,被称为高级排序
所谓扩展,实际是可以自定义排序时比较两个值的子程序,排序本身的算法仍有Perl完成。要实现高级排序,首先要写一个子程序,子程序的返回值只有-1, 0, 1,用来表示待比较的两个值的“小于、等于或大于”关系。然后在调用sort函数时,把子程序的名字插在sort关键字和参数列表之间。
demo14-9:

#!/usr/bin/perl
sub by_number{
  if($a < $b) {
    1
  } elsif($a > $b) {
    -1
  } else {
    0
  }
}

my @array = qw /1 3 5 7 9/;
my @result = sort by_number @array;
print "The result is [@result]\n";
./demo14-9
The result is [9 7 5 3 1]

注意到这里子程序使用了两个未声明的变量$a, $b,Perl会把原始列表中需要比较的元素自动放在$a$b,而且这里放的就是元素本身而非拷贝,所以不能对$a$b进行修改。
sort默认的规则是按数字升序和字符的ASCII码序,如果要使用这种默认规则,可以用简化的操作符<=>cmp分别对应着数值比较操作和字符串比较操作,而且Perl会尝试把由数值组成的字符串按照数值而非字符串进行比较。

sub default_ASCII {$a cmp $b;}
sort default_ASCII @array;

sub default_ASCEND {$a <=> $b;}
sort default_ASCEND @array;

看起来并没有什么意义,因为sort函数本来也是那样做的,但是我们可以稍微变通一下操作符两边的内容,

sub default_ASCII_ignore_cases { '\L$a' cmp '\L$b';}

可以实现忽略大小写的字符串比较,注意这里只改变了cmp操作符两边的内容,但并没有改变$a$b的值,那种操作是不被Perl允许的。
同样我们可以用

sub default_DESCEND {$b <=> $a;}

实现降序排序,<=>cmp操作符都是短视的,它们并不能认出$a$b,他们只是用左边的值去比较右边的值,然后返回1-10
如果使用操作符,还可以把代码进一步简化,略过不必要的子程序定义

my @descend =  sort {$b <=> $a} @array;

利用高级排序的特性,我们还可以对哈希进行排序,传统的方式只能对哈希的键进行排序,但我们可以自定义比较程序,来实现对哈希的值进行排序:
demo14-10

#!/usr/bin/perl
my %score = ("barney" => 195,
        "fred" => 205,
        "dino" => 30
    );


sub by_score{
    $score{$b} <=> $score{$a};
}
@result = sort by_score (keys %score);
print "The sorting result is\n";
foreach (@result){
    print " $_ => $score{$_}\n";
}

这里再比较子程序中,比较了哈希表中的值,实现了按值排序,注意这里并没有修改$a$b的内容。


对哈希表按值排序会引入一个问题,不同于哈希表键的唯一性,哈希表的值可能会有相同的项目。Perl支持多值排序,允许对多个条件进行比较

sub by_score_and_name{
 $score{$b} <=> $score{$a} #按分数降序,如果同分则为0
  or #如果前式为0,继续判断后式
  $a cmp $b # 分数相同,再按ASCII码排序
}

这个规则可以无限扩展,实现多值比较排序:

@patron = sort {
  &fines($b) <=> &fines($a) or
  $items{$b} <=> $items{$a} or
  $family_name{$a} cmp $family_name{$b} or
  $personal_name{$a} cmp $personal_name{$b} or
  $a cmp $b} @patron;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容