美文网首页Perl 语言入门 Learning Perl
Learning Perl 学习笔记 Ch14 字符串与排序

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

作者: sakam0to | 来源:发表于2019-06-20 10:49 被阅读0次
    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;
    

    相关文章

      网友评论

        本文标题:Learning Perl 学习笔记 Ch14 字符串与排序

        本文链接:https://www.haomeiwen.com/subject/ecmhqctx.html