修改数组perl中的多个元素

modifying multiple elements in array perl

假设我有一个包含名称字段和 3 个日期字段的文件,并且想要重新格式化日期。我可以这样走:

while (<DATA>) {
  my @lines = split(/\|/); ##splitting DATA by '|'

  my @dates = split( /\/|[-]/, $lines[0] ); #splitting only the first element of array and performing modifications below.
  if ( $dates[2] =~ /^[0-1][0-9]$/gi ) { $dates[2] = $dates[2] + 2000 }
  elsif ( $dates[2] =~ /^[2-9][0-9]$/gi ) {
    $dates[2] = $dates[2] + 1900;
  }
  if ( $dates[1] =~ /^\d$/gi ) { $dates[1] = "0" . $dates[1] }
  if ( $dates[0] =~ /^\d$/gi ) { $dates[0] = "0" . $dates[0] }
  my $date = join "-", @dates[ 2, 0, 1 ]; #joining the dates to be in yyyy-mm-dd format.
  print $date, "\n"; #double check
  print $date, ",", ( join ",", @lines[ 1 .. $#lines ] ), "\n"; appending date to print the join of @lines.
}

有没有一种方法可以同时对所有需要的字段进行修改,而不是必须拆分和连接每个 $line[0] 到 $lines[2]? ($lines[0] 到 $lines[2]).

__DATA__
12/23/2014|2/20/1995|3/25/1905|josh

您必须保留字段的拆分和连接,但您可以将替换减少为:

$dates[2] =~ s/^([01]\d)$/20/;
$dates[2] =~ s/^([2-9]\d)$/19/;
$dates[1] =~ s/^(\d)$/0/;
$dates[0] =~ s/^(\d)$/0/;

它很丑陋,但它会按照您的要求一次性完成所有字段。

while (<DATA>) {
    s/
      (?:^|\|)\K # start after a leading start-of-line or pipe
      (\d{1,2})
      [\/-]
      (\d{1,2})
      [\/-]
      (\d\d(?:\d\d)?)
      (?=\||\z) # look-ahead to see trailing pipe or end-of-string
     /
        sprintf('%04d-%02d-%02d',
             <  20 ?  + 2000
          :  < 100 ?  + 1900
          : ,
            ,
            
        )
     /gex;
    print;

}

__DATA__
1/23/14|2/20/95|3/25/1905|josh

您的脚本会根据您提供的输入给出令人讨厌的输出。以下输出对我来说似乎更合乎逻辑:

#!/usr/bin/env perl

use strict;
use warnings;

while (my $line = <DATA>) {
    next unless $line =~ /\S/;
    my ($name, @dates) = reverse split qr{\|}, $line;
    @dates = reverse map sprintf('%04d-%02d-%02d', (split qr{/})[2,0,1]), @dates;
    print join(',', @dates, $name), "\n";
}
__DATA__
12/23/2014|2/20/1995|3/25/1905|josh

输出:

2014-12-23,1995-02-20,1905-03-25,josh

如果这不是您想要的输出,请描述您想要获得的确切输出。

几点:

  • while (<DATA>)DATA 读取一行。将它分配给一个有意义的变量,使您的代码更易于阅读。

  • 跳过空行处理

  • 不要成为 LTS 的牺牲品:/\|/qr{\|} 甚至 qr{ \| }x 更难区分。

  • reverse 使代码更易于阅读,但如果您有大量字段,它们可能会成为真正的瓶颈。在这种情况下,poppush

您可以使用正则表达式来帮助匹配您的数据行的各种点点滴滴,并消除多次拆分。使用正则表达式也可以验证你的行格式。你有一个三位数的月份吗?你有三个约会吗?验证您的输入总是一个好主意:

#! /usr/bin/env perl
#
use strict;
use warnings;
use feature qw(say);

my $date_re = qr(
        ^(?<month1>\d{1,2})/
        (?<day1>\d{1,2})/
        (?<year1>\d{2,4})
        \|                    # Separator between date1 and date2
        (?<month2>\d{1,2})/
        (?<day2>\d{1,2})/
        (?<year2>\d{2,4})
        \|                    # Separator between date2 and date3
        (?<month3>\d{1,2})/
        (?<day3>\d{1,2})/
        (?<year3>\d{2,4})
        \|                    # Separator between date3 and name
        (?<name>.*)
    )x;
while ( my $line = <DATA> ) {
    my @array;
    if ( not @array = $line =~ m^$date_re^ ) {
        say "Something's wrong";
    }
    else {
        say "First Date: Year = $+{year1}  Month = $+{month1}  Day = $+{day1}";
        say "Second Date: Year = $+{year2}  Month = $+{month2}  Day = $+{day2}";
        say "Third Date: Year = $+{year3}  Month = $+{month3}  Day = $+{day3}";
        say "Name = $+{name}";
    }
}

__DATA__
12/23/2014|2/20/1995|3/25/1905|josh

运行 这个程序打印出:

First Date: Year = 2014  Month = 12  Day = 23
Second Date: Year = 1995  Month = 2  Day = 20
Third Date: Year = 1905  Month = 3  Day = 25
Name = josh

这是使用正则表达式的一些高级功能

  • qr/.../可以用来定义正则表达式。因为你在正则表达式中有斜线,我决定用括号来分隔我的正则表达式,所以它是 qr(...).

  • 最后的)x意味着我可以用白色的space让我的正则表达式更容易理解。例如,我将每个日期分成三行(月、日、年)。

  • (?<name>...) 命名您的捕获组,这样可以更轻松地引用特定的捕获组。我可以使用 %+ 散列来调用我的捕获组。例如 (?<month1>\d{1,2}) 表示我希望月份为 1 到两位数。我将它存储在捕获组 month1 中,我可以通过使用 $+{month1}.

    来引用它

    使用命名捕获组的好处之一是它记录您试图捕获的内容。

  • {M,N}是重复。我希望前面的正则表达式发生 MN 次。 \d{1,2} 表示我期待一位或两位数字。