用于排序的子键比较功能

时间:2012-07-17 18:29:01

标签: perl sorting hierarchical cmp

我需要一个可以与sort一起使用的Perl比较函数。

每个键都是一个文本字符串,它具有任意数量的子键,由分隔符(点,冒号,空格和斜杠)分隔。有些子键是数字的,需要按数字排序。密钥格式和子密钥数量各不相同。因此,比较必须处理一个密钥比另一个密钥长,并且必须处理子密钥在一个密钥中是数字而在另一个密钥中不是的情况(在这种情况下,文本比较适合于该子密钥)。

这有效,但我敢打赌有更好的解决方案:

use warnings;
use strict;
use Scalar::Util qw[looks_like_number];

sub hier_cmp {

    my $aa = $a;
    my $bb = $b;

    # convert all delims (. : / space) to the same delim

    $aa =~ tr/.:\/ /::::/;
    $bb =~ tr/.:\/ /::::/;
    my @lista = split(":", $aa);
    my @listb = split(":", $bb);

    my $result;

    for my $ix (0 .. min($#lista, $#listb)) {
        if (exists($lista[$ix]) && exists($listb[$ix])) {
            if ( looks_like_number($lista[$ix]) && looks_like_number($listb[$ix])) {
                # compare numerically
                $result = ($lista[$ix] <=> $listb[$ix]);
            } else {
                # compare as strings
                $result = ($lista[$ix] cmp $listb[$ix]);
            }
            if ($result == 0) {
                next;
            }
            return $result;

        } elsif (exists($lista[$ix])) {
            return 1;
        } else {
            return -1;
        }
    }
}

就我而言,可读性比速度更重要。这仅适用于内部工具,列表很少会有数百个元素。但是,任何学习东西的机会都是好的。

如您所见,我不是perl向导。我的代码即使是微不足道的改进也会受到赞赏。

谢谢!

2 个答案:

答案 0 :(得分:2)

这看起来像是自然排序。 CPAN上有几个模块可以执行此操作,例如Sort::NaturallySort::Key::Natural

例如:

use Sort::Key::Natural qw(natsort);
my @sorted = natsort @data;

答案 1 :(得分:1)

如果您给我们一些数据进行测试会有所帮助,但是这段代码通过了一些基本的测试,看起来是正确的。

它通过使用List::MoreUtils函数pairwise来创建字段对数组来简化问题。

然后只需要检查是否只定义了一个,当其中一个列表在另一个列表之前结束并且应该先排序;如果它们都是数字的,那么它们应该与数字比较进行比较;或者简单地将它们比作字符串。

如果达到对数组的末尾,那么一切都匹配,返回零表示相等。

<强>更新

我更改了此代码以删除对List::MoreUtils::pairwise的依赖。

use strict;
use warnings;

use Scalar::Util 'looks_like_number';

sub hier_cmp {

  our ($a, $b);

  my @a = split m|[.: /]+|, $a;
  my @b = split m|[.: /]+|, $b;

  for my $i (0 .. $#a > $#b ? $#a : $#b) {
    my @ab = ( $a[$i], $b[$i] );
    if (grep defined, @ab < 2) {
      return defined $ab[0] ? 1 : -1;
    }
    else {
      my $numeric = grep(looks_like_number($_), @ab) == 2;
      my $result = $numeric ? $ab[0] <=> $ab[1] : $ab[0] cmp $ab[1];
      return $result if $result;
    }
  }

  return 0;
}