在Perl中如何获取整数的序数后缀(例如st,nd,rd,th)

15

我有一个数字,需要添加后缀:'st'、'nd'、'rd'、'th'。例如:如果数字是42,后缀为'nd',521是'st',113是'th'等等。 我需要在perl中完成这个操作。有什么指导吗。

6个回答

27

使用Lingua::EN::Numbers::Ordinate。从概要看:

use Lingua::EN::Numbers::Ordinate;
print ordinate(4), "\n";
 # prints 4th
print ordinate(-342), "\n";
 # prints -342nd

# Example of actual use:
...
for(my $i = 0; $i < @records; $i++) {
  unless(is_valid($record[$i]) {
    warn "The ", ordinate($i), " record is invalid!\n"; 
    next;
  }
  ...
}

17

试试这个:

my $ordinal;
if ($foo =~ /(?<!1)1$/) {
    $ordinal = 'st';
} elsif ($foo =~ /(?<!1)2$/) {
    $ordinal = 'nd';
} elsif ($foo =~ /(?<!1)3$/) {
    $ordinal = 'rd';
} else {
    $ordinal = 'th';
}

2
为了使用难以捉摸的零宽度负回顾断言而点赞。尽管(可悲的是)正如Bill Ruppert指出的那样,已经有一个CPAN模块可以实现这个功能了。 - Andy Ross
4
虽然有一个CPAN的解决方案,但我也喜欢这个。它经过深思熟虑,易于阅读,没有依赖关系,并且对于任何整数来说与CPAN的解决方案一样准确。 - DavidO
同样的功能,但更符合 Perl 风格:sub ordinal { my $n = shift; return "${n}st" if $n =~ /(?(注意:这个函数也会返回数字本身) - matemaciek

7

尝试这个简短的子程序

use strict;
use warnings;

sub ordinal {
  return $_.(qw/th st nd rd/)[/(?<!1)([123])$/ ? $1 : 0] for int shift;
}

for (42, 521, 113) {
  print ordinal($_), "\n";
}

输出

42nd
521st
113th

这里有一些我不太理解的地方。当只有一个元素作为参数时,为什么要使用 for 循环?也可以使用 return int(shift) . (qw/...。对于多个参数,由于 return 语句的存在,for 循环也无法正常工作。虽然现在它能够正常运行,但是我是否错过了循环的某些内容? - Birei
@Birei:这只是将$_[0]放入$_的一种方式。你的方式行不通,因为正则表达式需要值在$_中。这非常类似于新的given语言单词,但你不能像使用for那样将其用作语句修饰符。 - Borodin
啊,好的。谢谢你。我不明白 $_ 的意思。这值得一个 +1 - Birei
@BillRuppert:谢谢Bill。我都忘了我写过这个!我认为for就是所有破损的given应该做的,但这是另一回事。 - Borodin

3
这里有一个解决方案,我最初是为了参加代码高尔夫挑战而编写的,稍作修改以符合非高尔夫代码的通常最佳实践:
$number =~ s/(1?\d)$/$1 . ((qw'th st nd rd')[$1] || 'th')/e;

它的工作原理是,正则表达式(1?\d)$匹配数字的最后一位,如果前面的数字是1,则还会匹配它前面的数字。接下来,替换操作使用匹配到的数字作为列表(qw'th st nd rd') 的索引,将0映射为th,将1映射为st,将2映射为nd,将3映射为rd,其他任何值都映射为undef。最后,||运算符将undef替换为th
如果你不喜欢s///e,基本上可以像这样编写相同的解决方案:
for ($number) {
    /(1?\d)$/ or next;
    $_ .= (qw'th st nd rd')[$1] || 'th';
}

或者作为一个函数:
sub ordinal ($) {
    $_[0] =~ /(1?\d)$/ or return;
    return $_[0] . ((qw'th st nd rd')[$1] || 'th');
}

1
另一个解决方案(虽然我更喜欢不使用模块的现有答案):
use Date::Calc 'English_Ordinal';
print English_Ordinal $ARGV[0];

1

这里有一个完全不复杂的方法来实现。

sub english_ordinal( $n ) {
    my @suffixes = qw( th st nd rd th th th th th th );

    my $x = $n % 100;

    my $suffix;
    if ( $x >= 10 && $x <= 19 ) {
        $suffix = 'th';
    }
    else {
        $suffix = $suffixes[$x % 10];
    }

    return "$n$suffix";
}

你是否希望让它占用更少的空间,更聪明,也许速度更快?当然,但其他答案已经涵盖了这些问题。

顺便加上一些单元测试。

my %tests = (
    0   => '0th',
    1   => '1st',
    2   => '2nd',
    3   => '3rd',
    4   => '4th',
    5   => '5th',
    6   => '6th',
    7   => '7th',
    8   => '8th',
    9   => '9th',
    10  => '10th',
    11  => '11th',
    12  => '12th',
    13  => '13th',
    14  => '14th',
    15  => '15th',
    16  => '16th',
    17  => '17th',
    18  => '18th',
    19  => '19th',
    20  => '20th',
    21  => '21st',
    22  => '22nd',
    23  => '23rd',
    24  => '24th',
    25  => '25th',
    26  => '26th',
    27  => '27th',
    28  => '28th',
    29  => '29th',
    30  => '30th',
    100 => '100th',
    101 => '101st',
    102 => '102nd',
    111 => '111th',
);

while ( my ($n,$s) = each %tests ) {
    is( english_ordinal($n), $s, "$n -> $s" );
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接