首页 > 解决方案 > 关于使用 Schwartzian 变换的 Perl 排序的建议

问题描述

我一直在看一篇关于在 Perl 中使用正则表达式对数组进行排序的旧帖子。原帖在这里

我正在努力完全理解被选为“正确”答案的脚本。原来的帖子是关于对下面的数组进行排序:

  my @array = (
  "2014 Computer Monitor 200",
  "2010 Keyboard 30",
  "2012 Keyboard 80",
  "2011 Study Desk 100"
);

问题是如何在 perl 中使用正则表达式按年份、项目名称和价格对整个数组进行排序?例如,如果用户想按价格排序,他们输入“价格”,排序如下:

2010 Keyboard 30
2012 Keyboard 80
2011 Study Desk 100
2014 Computer Monitor 200

提出了一种使用 Schwartzian 变换的解决方案。我刚刚开始了解这一点,这个脚本与我见过的其他示例有点不同。被选为正确答案的脚本如下。我正在寻找有关它如何工作的建议。

   my $order = "price";
   my @array = (
  "2014 Computer Monitor 200",
  "2010 Keyboard 30",
  "2012 Keyboard 80",
  "2011 Study Desk 100"
);

my %sort_by = (
  year  => sub { $a->{year}  <=> $b->{year} },
  price => sub { $a->{price} <=> $b->{price} },
  name  => sub { $a->{name}  cmp $b->{name} },
);
@array = sort {

  local ($a, $b) = map {
    my %h; 
    @h{qw(year name price)} = /(\d+) \s+ (.+) \s+ (\S+)/x;
    \%h;
  } ($a, $b);
  $sort_by{$order}->();

} @array;

# S. transform
# @array =
#  map { $_->{line} }
#  sort { $sort_by{$order}->() }
#  map { 
#    my %h = (line => $_); 
#    @h{qw(year name price)} = /(\d+) \s+ (.+) \s+ (\S+)/x;
#    $h{name} ? \%h : ();
#  } @array;

use Data::Dumper; print Dumper \@array;

我知道脚本使用正则表达式/(\d+) \s+ (.+) \s+ (\S+)/x来匹配年份名称和价格。

我认为脚本的其余部分如下所示:

• 第 14 行的初始排序一次从@array 中获取两个项目,一个在 $a 中,一个在 $b 中

• map 函数然后获取项目$a 和$b 并将每个项目映射到一个散列——每个项目成为一个具有键'year'、'price' 和'name'的散列。这是基于正则表达式 /(\d+) \s+ (.+) \s+ (\S+)/x

• Map 返回两个哈希值,作为对局部变量 $a 和 $b 的引用

• 我认为有必要使用本地$a 和$b,否则排序将使用在第17 行排序开始时采用的默认$a 和$b?

• 'price' 排序函数作为 coderef 存储在 %sort_by 散列中

$sort_by{$order}->()• 这在第 26 行由$a 和 $b 的本地版本的代码调用

重复此操作,直到所有项目都返回到第 14 行的 @array

请任何人告诉我我是否在正确的路线上,或者纠正任何误解。您还可以就本地 $a 和 $b 变量的使用提出建议。

谢谢J

标签: arrayssortingperl

解决方案


Schwartzian 变换是一种避免多次计算排序键的方法,就像在解决方案中一样 - 具有local ($a,$b)

S. 变换的步骤基本上是:

  • 使用 Map 通过计算的排序键来丰富列表元素。在这里,%h被用作新元素,包含原行为line
  • 使用 Sort 对这个富豪列表进行排序。sort带有一点肮脏的魔法$a $b
  • 使用 Map 提取原始列表元素。这里通过提取line密钥。

关于$a $b

非常遗憾,$a并且$b是 Perl 中的全局变量。sort它们通常在块内自动分配。像 sort { $a <=> $b } (3,2,1)

这解释了为什么 S. 解决方案有效,即使比较的元素没有作为排序子的参数给出。它还解释了需要local(另一个 Perl 恐怖假装全局变量是本地的),所以天真的解决方案的排序函数在$a, $b.

我强烈建议您忘记这一点并避免隐式使用 $a , $b 比排序块本身更深。

一个更容易理解的版本是:

my $order = "price";
my @array = (
  "2014 Computer Monitor 200",
  "2010 Keyboard 30",
  "2012 Keyboard 80",
  "2011 Study Desk 100"
);

my %sort_by = (
  year  => sub { shift->{year}  <=> shift->{year} },
  price => sub { shift->{price} <=> shift->{price} },
  name  => sub { shift->{name}  cmp shift->{name} },
);

my @sorted = 
  map { $_->{line} }
  sort { $sort_by{$order}->($a, $b) }
  map { 
    my %h = (line => $_); # $_ is the array element (the input line)
    @h{qw(year name price)} = ( $_ =~ /(\d+) \s+ (.+) \s+ (\S+)/x );
    # Did the regex capture a name, i.e. did it work?
    if( $h{name} ){
        \%h
    } else{
        (); # Empty array will cause the invalid line to disappear, but you can choose to do something else with it.
    }
  } @array;
  
print(join("\n", @sorted))

推荐阅读