加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

Perl笔记:12、字符串与排序

发布时间:2020-12-16 00:07:18 所属栏目:大数据 来源:网络整理
导读:index 函数 使用方法: index ( 待检索的字符串 , "检索什么" , 从哪开始检索 ) ; 如下: my $string = "hello world!"; my $where = index($string,"wor"); $where 的结果为6,即wor中的w首次出现的位置,这个位置是从零开始算起的,如果你非要从1算起也可

index 函数

使用方法:index(待检索的字符串,"检索什么",从哪开始检索);
如下:
my $string = "hello world!";
my $where = index($string,"wor");

$where 的结果为6,即wor中的w首次出现的位置,这个位置是从零开始算起的,如果你非要从1算起也可以,但解释就有些不同了,如果从1开始算起,你可以理解为“到达此字符之前经过了多少个字符数”。如果没有找到子串,返回-1。

index还有一个可选的第三个参数,即指定开始搜索的地方,这样index就不会从开始的地方开始查找了:

my $stuff = "Howdy world!" ;
my $where1 = index ( $stuff , "w" ) ; # $where1 得到 2
my $where2 = index ( $stuff , "w" , $where1 + 1 ) ; # $where2 得到 6
my $where3 = index ( $stuff , $where2 + 1 ) ; # $where3 得到 ?1 (没有找到)

与index函数相对应的是rindex函数,它是查找字符串中子串最后出现的位置
my $last_slash = rindex("/etc/passwd","/"); # 值为 4

用substr处理子串

substr处理较长字符串中的一部分,用法如下:
$part = substr($string, $initial_position, $length);
该函数支持三个参数,分别为:待处理的字符串、从零算起的起始位置、子串的长度。如下面的例子:

my $mineral = substr ( "Fred J. Flintstone" , 8 , 5 ) ; # 得到 "Flint"
my $rock = substr "Fred J. Flintstone" , 13 , 1000 ; # 得到 "stone"

第二个例子中,我写的子串长度为1000,但前面给定的字符串总共也没有几个,这样一来perl就会将字符串第13个开始到最后的所有字符都截取出来。当不知道字符串有多少但要想将某个位置开始到最后的所有字符都截取时,可以省略第三个参数,这样也可以达到同样的目的。

字符串中子串的位置可以为负数,表示从字符串结尾开始倒数,看看下面的例子:

my $out = substr ( "some very long string" , ? 3 , 2 ) ; # $out 得到"in"

接下来看一个index和substr结合使用的例子:

my $long = "some very very long string" ;
my $right = substr ( $long , index ( $long , "l" ) ) ;

下面是将选取的字符串部分替换为其他的内容:

my $string = "Hello World!" ;
substr ( $string , 0 , 5 ) = "Goodbye" ;
# $string 值变为了Goodbye World!

如例子所示,被替换的字符长度不必与选取出的字符相等。甚至可以使用绑定操作符(=~)只对字符串的某部分进行替换:

substr ( $string , ? 20 ) =~ s/fred/barney/g ;

substr与index做的事情多半也可以使用正则表达式来做到。所以,请选择最适合解决问题的方法。但这里要说一点,通常substr与index的速度会略快一些,因为它们不用载入正则表达式引擎。它们总是区分大小写,不必担心元字符,而且不会设定任何的内存变量。

如果不给substr赋值,可以使用传统的四个参数的形式来使用,第四个参数为要替换的字符串,整个substr函数返回的内容仍然为找到的字符串。

my $previous_value = substr ( $string , 5 , "Goodbye" ) ;

用sprintf格式化数据

sprintf函数和printf函数类似,相同点是他们都是格式化字符串的,但sprintf不负责输出,只是格式化字符串然后将格式化后的字符串返回。如下:

my $date_tag = sprintf
"M/d/d -:d:d" ,
$yr , $mo , $da , $h , $m , $s ;

上面的例子中$date_tag会得到类似2010/05/24 5:20:03的结果。格式字符作为sprintf的第一个参数,会在某些格式数值前置零。格式串中的数值字段的前置零,表示必要的时候会在数值前面补零以符合要求的宽度。

下面的例子,会将一个较大的数字格式化为美元习惯的xx,xxx,xxx.xx的格式:

#!/usr/bin/perl -w

use strict ;

sub big_money {
my $number = sprintf "%.2f" , shift @_ ;
1 while $number =~ s/^(-?d+)(ddd)/$1,$2/ ;
$number =~ s/^(-?)/$1$/ ;
$number ;
}

my $money = &big_money ( @ ARGV ) ;

print "$moneyn" ;

# perl sprintf.pl 123456789.1
$123,456,789.10

高级排序

perl中的排序并不是前面章节介绍的sort函数那么简单,下面讲解更灵活,更复杂的排序。

首先要定义排序子程序,在子程序中说明一下我这个排序是按照什么规则排序的。如,数字,字符等。排序子程序的编写也很简单,你没有必要编写很多数字或字符的排序的过程,实际上你只需要声明一下是那种类型的比较就可以了。并在最后给出返回值即可。相等返回0,大于返回1,小于返回-1。直接看例子:

简单的按照数字排序的 排序子程序:

sub by_number {
# a sort subroutine,expect $a and $b
if ( $a < $b ) { ? 1 } elsif ( $a > $b ) { 1 } else { 0 }
}

#定义完排序子程序之后通过下面的方式调用
my @result = sort by_number @some_numbers ;

实际上排序子程序可以写成如下的简略形式:
sub by_number { $a <=> $b }

这里用到了飞碟操作符(<=>)该操作符只能用来比较两个数值,并返回-1、0或1
与飞碟操作符对应的那肯定是字符比较操作符cmp了,与<=>一样他们都是三路比较操作符。如下定义和调用字符串排序子程序:

sub ASCIIbetically { $a cmp $b }
my @strings = sort ASCIIbetically @any_strings ;

你还可以使用cmp操作符定义更高级的字符排序子程序,如不区分大小写的排序:
sub case_insensitive { "L$a" cmp "L$b" }
在这个例子中我们强制将$a,$b转换为小写然后再进行比较。要注意的是,我们并没有修改被比较的内容,我们只是那里使用了一下而已。实际上$a,$b并不是数据项的复制,而是原始列表元素的临时名称而已,因此千万不要修改它们的值。否则被比较的信息可能会改变。

如果排序子程序像例子一样简单,那就可以让程序代码更为简单,只要把子程序内嵌到函数名的位置就可以了。如下:
my @numbers = sort { $a <=> $b } @some_numbers;
实际上在Perl高手中几乎不会有人单独的写一个排序子程序。如果要逆序排序,可以直接用reverse函数来轻松解决:
my @descending = reverse sort { $a <=> $b } @some_numbers;
小技巧:<=>与cmp操作符并不知道谁是$a,谁是$b,只要你把$a写在左边那就是正序,否则就是逆序。

哈希按值排序

上文已经介绍了列表的排序,本节介绍哈希值的排序。如有下面的哈希:

my %score = ( "barney" => 195 , "fred" => 205 , "dino" => 30 ) ;

定义排序子程序

sub by_score { $score { $b } <=> $score { $a } }

这个排序子程序很容易实现。我们要的是积分的数值比较,而不是名字。换句话说,我们不应该比较$a与$b这两个球员的名字,而是比较$score{$a}与$score{$b}这两个积分。如果想到了这一点,那答案就出来了。

按多值排序

接着上面的例子,哈希中含有3个人名,并且相对应的也有3个分数,如果我再加一个人,积分和某一个相同那该如何处理呢?按照常规处理,积分相同的会返回0,本次比较无效,我们可以再加一个比较规则,也即是如果积分相同的话就按照人名将积分相同的人再排序,如下:

my %score = (
"barney" => 195 ,
"dino" => 30 , "bamm-bamm" => 195 ,
) ;

这里bamm-bamm和barney积分相同。所所以在排序完成后,哪一个应该排在前面是不可预知的,因此我又加入了按照名字的ASCII码排序。如下:

my @winners = sort by_score_and_name keys %score ;
sub by_score_and_name {
$score { $b } <=> $score { $a } # 按积分排序
or
$a cmp $b # 名字ASCII码排序
}

自己编写的一个实例:

#!/usr/bin/perl -w

use strict ;

my %score = ( "zyq" => 100 , "wcl" => 210 , "zyz" => 98 , "pf" => 182 ) ;

sub by_score { $score { $b } <=> $score { $a } }

sub by_score_and_name {
$score { $b } <=> $score { $a }
or
$a cmp $b
}

print "sort hash by score:n" ;
my @winner = sort by_score keys %score ;
foreach ( @winner ) {
print "$_ => $score{$_}n" ;
}

print "nsort hash by scroe and name:n" ;
%score = ( "zyq" => 100 , "pf" => 182 , "fyr" => 98 ) ;
@winner = sort by_score_and_name keys %score ;
foreach ( @winner ) {
print "$_ => $score{$_}n" ;
}

输出结果

sort hash by score:
wcl => 210
pf => 182
zyq => 100
zyz => 98

sort hash by scroe and name:
wcl => 210
pf => 182
zyq => 100
fyr => 98
zyz => 98

有人可能会问,上面的例子,如果积分和人名都一样那怎么排序?仔细想……我排序的是哈希,哈希的键是不可重复的,因此不会出现人名重复的情况。

这个例子只是涉及到两级比较,如果又需要的话还可以进行更多级别的比较,如下:

@patron_IDs = sort {
&fines ( $b ) <=> &fines ( $a ) or
$items { $b } <=> $items { $a } or
$family_name { $a } cmp $family_name { $a } or
$personal_name { $a } cmp $family_name { $b } or
$a <=> $b
} @patron_IDs ;

本章练习

1、编程读入一连串的数字并将它们按值排序,将结果以靠右对其的格式输出。请以下列数据来测试你的程序。
17 1000 04 1.50 3.14159 ?10 1.5 4 2001 90210 666
写法1

#!/usr/bin/perl -w

use strict ;

my @sort_num = sort { $a <=> $b } @ ARGV ;

foreach ( @sort_num ) {
printf "gn" , $_ ;
}

运行:perl ex_12_1.pl 17 1000 04 1.50 3.14159 ?10 1.5 4 2001 90210 666
结果:

1.5
1.5
3.14159
4
4
10
17
666
1000
2001
90210

写法2

#!/usr/bin/perl -w

use strict ;

my @number ;
push @number , split while <>;
foreach ( sort { $a <=> $b } @number ) {
printf "gn" , $_ ;
}

这种写法不是以参数的形式运行程序的,而是运行程序后输入待排序的字符,输入完成后按Ctrl+D结束输入。
程序的第二行可以写成
while(<>){
push @number,split
}
的格式,while循环么次会读入一行(从用户所要求的输入来源,也就是钻石操作符),接着split会以空白来分隔该行,与乘胜一个数字列表,这样一来while循环就会将其中所有的数字存进@number里。

2、编程以不区分大小写的字符顺序把下列的哈希数据按姓氏排序后输出。当姓一样时,就以名字排序(同样不区分大小写)。也就是说,输出结果中的第一个名字应该是Fred,最后一个应该是Betty。所有姓相同的人应该排在一起。千万不别更改原始数据。这些名字应该以它原来的大小写形式被显示出来。
my %last_name = qw{
fred flintstone Wilma Flintstone Barney Rubble
betty rubble Bamm-Bamm Rubble PEBBLES FLINTSTONE
};

答:

#!/usr/bin/perl -w

use strict ;

my %last_name = qw {
fred flintstone Wilma Flintstone Barney Rubble
betty rubble Bamm -Bamm Rubble PEBBLES FLINTSTONE
} ;

sub sort_by_name {
"L$last_name{$a}" cmp "L$last_name{$b}"
or
"L$a" cmp "L$b"
}
my @sort_name = sort sort_by_name keys %last_name ;

foreach ( @sort_name ) {
print "$_ $last_name{$_}n" ;
}

3、编程在输入字符串中找出特定子串出现的位置并将其输出。例如:输入字符串为“This is a test.”,而子串是“is”,则程序应该会报告位置2和5;如果子串是“a”,程序应该会报告8,如果子串是“t”,程序将报告什么?
答:写法1 程序中指定待检索字符和子串

#!/usr/bin/perl -w

use strict ;

my $string = "This is a test." ;
my $location =- 1 ;

while ( 1 ) {
$location = index ( $string , "is" , $location + 1 ) ;

last if $location == - 1 ;
printf "$locationt" ;
}

print "n" ;

写法2:用户命令行输入字符串和子串

#!/usr/bin/perl -w

use strict ;

print "pleash in put string:" ;
chomp ( my $string = <STDIN> ) ;

print "pleash input sub string:" ;
chomp ( my $substring = <STDIN> ) ;

my $location = - 1 ;

while ( 1 ) {
$location = index ( $string , $substring , $location + 1 ) ;

last if $location ==- 1 ;
print "$locationt" ;
}

print "n" ;

运行/结果:
# perl ex_14_3_1.pl
pleash in put string:this is a best test!
pleash input sub string:t 0 13 15 18

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读