【问题标题】:Sort keys of hash by numerals and dots that like IPv4按类似于 IPv4 的数字和点对 hash 的键进行排序
【发布时间】:2018-04-16 20:16:38
【问题描述】:

假设我的 IPvN 是一个与 IPv4 格式几乎相同的序列,唯一的区别是可以有任意数量的点或数字。我想对这些序列的列表进行如下排序。

  • IPv4 在 IPv5 之前,依此类推。
  • 对于相同的小数位数,从左到右在相同垂直位置的数字升序排序。
    8.8.8.8
    20.0.0.0
    1.2.3.4.5


问题

1。如何让它更智能?

我使用了一种粗略的方式来合成逻辑条件表达式。我知道这样做是非常低效和浪费内存的,特别是当只有少数异常长的 IPvN 时,人类一眼就能解决,但我拖着完全冗余和无用的代码尾巴,例如 ... or $a->[1000] <=> $b->[1000]

2。如何在单个"Schwartzian transform"中实现满足多个条件的排序?

正如你在下面看到的,我做了一个双层的叠加 Schwartzian 变换。

3。为什么会出现这个错误?

(我输出的第一行。)

Use of uninitialized value in numeric comparison (<=>) at (eval 4) line 1.

但是,如果是
'8.8.8.6.0' =&gt; 6 而不是 %IP_counter 中的
'8.8.8.8.0' =&gt; 6,则不会出现警告。

我只知道这个错误与
'8.8.8.8' =&gt; 3有关。



我的垃圾代码

use strict;
use warnings;
use feature 'say';

use List::Util qw(max);


my %IP_counter =
    (
    '1.1.1.1.1'   => 1,
    '127.0.0.1'   => 2,
    '8.8.8.8'     => 3,
    '20.0.0.1'    => 4,
    '999999999999999999999999999999999999999999999999.9'
                  => 5,
    '8.8.8.8.0'   => 6,
    '888888888888888888888888888888888888888888888888.8'
                  => 7,
    );


# Find the max length
# Abbr. "c_n_d_m" -> "count numbers divided (max)".
my @c_n_d;
my $c_n_d_m = 0;
foreach (keys %IP_counter) {
    @c_n_d = split /\./, $_;
    $c_n_d_m = @c_n_d if $c_n_d_m < @c_n_d
}


# ?Q1. Question 1 arises here
# Concatenation of hardcode
my @m = (1..$c_n_d_m);

my $cond;
foreach my $i (0..@m-1) {$cond .= '$a->['.$m[$i].'] <=> $b->['.$m[$i].'] or '}
$cond = substr $cond, 0, rindex($cond, ' or ');


# ?Q2. Question 2 arises here
# Sort keys by numeral
# Do a double "Schwartzian transform"
my @sort_by_num =
    # Sort by number of dots:
    map  { $_ }
    sort { (split /\./, $a) <=> (split /\./, $b) }
    map  { [ $_, /(\d+)/g ] -> [@_] }
    # Sort by (regarded as) shift floats:
    map  { $_->[0] }
    sort { eval $cond }
    map  { [ $_, /(\d+)/g ] }
         keys %IP_counter;


# Print the result
my $width = max map {length $_} keys %IP_counter;

foreach my $IP (@sort_by_num) {
    printf "%-*s  =>  $IP_counter{$IP}\n", $width, $IP
}

输出

Use of uninitialized value in numeric comparison (<=>) at (eval 4) line 1.
888888888888888888888888888888888888888888888888.8  =>  7
999999999999999999999999999999999999999999999999.9  =>  5
8.8.8.8                                             =>  3
20.0.0.1                                            =>  4
127.0.0.1                                           =>  2
1.1.1.1.1                                           =>  1
8.8.8.8.0                                           =>  6

【问题讨论】:

  • 你真的需要表示 1E48 这样大的数字吗?如果是这样,那么您最初的问题非常具有误导性,您的价值观实际上与 IP 地址无关。
  • @Borodin 好吧,我不得不承认数字不会那么夸张:) 所以我终于完全重新接受了 melpomene 的回答。
  • 那么这些值的实际上限是多少?解决方案高度依赖此限制。
  • @Borodin 你的需求已经完全满意了~

标签: perl sorting


【解决方案1】:

我解决这个问题的方法是使用 Guttman Rosler 变换 (GRT)。 GRT 是 Schwartzian 变换的变体,其中 sort 块为空(即使用标准字典字符串比较)。这更有效,因为sort 可以完全在 C 代码中运行,而无需回调到 Perl 代码 O(n*log n) 次(对于每次比较)。在您的情况下,这更为重要,因为您的代码重复调用 eval,这很慢。

GRT 的棘手部分是如何构造您的密钥,以便

  1. 它们比较正确,并且
  2. 它们允许您事后提取原始数据。

pack / unpack 在这里通常很有用。

您可以将其应用于您的问题:

use strict;
use warnings;

my @values = qw(
    139.8.0.2
    127.0.0.11
    1.1.1.1.1
    217.0.22.3
    8.8.8.8
    127.0.0.1
    139.12.0.2
);

my @sorted =
    map join('.', unpack 'J>/C'),
    sort
    map pack('J>/C', split /\./),
    @values;

print "$_\n" for @sorted;

输出:

8.8.8.8
127.0.0.1
127.0.0.11
139.8.0.2
139.12.0.2
217.0.22.3
1.1.1.1.1

想法是获取每个字符串,将其拆分为.,然后使用格式字符串J&gt;/C 打包数字。这告诉pack 打包一个字符序列(实际上是八位字节),其代码点以无符号整数(C)的形式给出,但在其前面加上一个 UV 格式(J)的计数(/),大端(&gt;)。 UV 是 Perl 内部用于无符号整数的 C 类型;它通常是 4 字节(对于 32 位机器)或 8 字节(对于 64 位机器)宽,split 不能产生比这种类型更多的字段。

因此例如'139.8.0.2'split 产生 139, 8, 0, 2pack 变成 "\x00\x00\x00\x00\x00\x00\x00\x04\x8b\x08\x00\x02",即长度值为 4 的编码为大端 8 字节整数 ("\x00\x00\x00\x00\x00\x00\x00\x04"),后跟对应于虚线的字节十进制数。

按字典顺序比较时,此字符串会根据您的标准进行排序:

  • 首先比较长度字段(因为它首先出现在字符串中)。大端整数按数字升序排序,因此数字分量较少(即点数较少)的字符串首先排序。
  • 对于具有相同长度字段(即相同数量的数字组件)的字符串,将比较打包的内容(每个数字组件都被编码为一个字节),这再次产生所需的顺序。

最后,我们通过应用逆运算joinunpack 来撤销split / pack 产生的编码。


也就是说,在 Schwartzian 变换中使用多个标准的一般方法是从第一个 map 生成多个字段:

my @sorted = 
    map $_->[0],
    sort {
        $a->[1] <=> $b->[1] ||
        $a->[2] <=> $b->[2] ||
        $a->[3] <=> $b->[3]
    }
    map [ $_, compute_key_1($_), compute_key_2($_), compute_key_3($_) ],
    @values;

【讨论】:

  • 感谢这么详细!!!但是输入0.0.0.256 将得到错误Character in 'C' format wrapped in pack at ... 和错误输出0.0.0.0如何处理“任意”长度的数字?
  • @ooo C 用于八位字节。如果您需要支持比这更宽的字符,请改用W(在打包/解包中)。
  • @ooo 这是您平台上的最大整数值。您的原始代码也不适用于这些值。
  • @ooo 我不知道你想告诉我什么。
  • 我的意思是,有没有办法绕过我机器上的位数限制,同时仍然使用 GRT 进行排序?
【解决方案2】:

这显示了 IP 地址的排序并忽略了代码的其余部分

use strict;
use warnings 'all';
use feature 'say';

my @ips = qw/
    139.8.0.2
    127.0.0.11
    1.1.1.1.1
    217.0.22.3
    8.8.8.8
    127.0.0.1
    139.12.0.2
/;

my @sorted = map { $_->[0] }
sort { $a->[1] <=> $b->[1] or $a->[2] cmp $b->[2] }
map { [ $_, tr/.//, join ('', map chr, /\d+/g) ] } @ips;

say for @sorted;

输出

8.8.8.8
127.0.0.1
127.0.0.11
139.8.0.2
139.12.0.2
217.0.22.3
1.1.1.1.1

【讨论】:

  • 您的代码是否智能地按需判断比较?也就是说,例如,$a-&gt;[1000] &lt;=&gt; $b-&gt;[1000] 在不需要时是否仍会“生成”?
  • @ooo 没有-&gt;[1000],永远只有 0、1 和 2。而且没有“生成”。
  • @hobbs 即使1. ...(998 "1")... .1 vs 1. ...(998 "1")... .2?
  • @ooo 是的。看代码。 -&gt;[0] 是原始字符串,-&gt;[1] 是点数(即元素数减一),-&gt;[2] 将是 "\x01\x01\x01...\x01""\x01\x01\x01... \x02".
  • 太棒了。当遇到多位数字时,您的代码大致排序,保持形状正确输出。
【解决方案3】:

使用来自 CPAN 的 Sort::Naturally。为什么要重新发明轮子?

#!/usr/bin/env perl

# always use these two
use strict;
use warnings;
# use autodie to automatically die on open errors
use autodie;

use Sort::Naturally;
my %IP_counter =
    (
    '1.1.1.1.1'   => 1,
    '127.0.0.1'   => 2,
    '8.8.8.8'     => 3,
    '20.0.0.1'    => 4,
    '999999999999999999999999999999999999999999999999.9'
                  => 5,
    '8.8.8.8.0'   => 6,
    '888888888888888888888888888888888888888888888888.8'
                  => 7,
    );

my @sorted_keys = nsort( keys %IP_counter );
print "$_\n" for @sorted_keys;

【讨论】:

  • 嘿家伙,请不要忽略我帖子开头的两个要求。在我发布我的问题之前我已经尝试过这个模块,它根本不适合我的案例!
  • @ooo 是吗?在哪里?顺便说一句,Perl 是 CPAN,CPAN 是 Perl。使用一个没有另一个就像使用一辆没有一个轮子的自行车。
  • 显示你的输出。然后你会看到它没有按我想要的顺序排序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-07-08
  • 1970-01-01
  • 1970-01-01
  • 2022-01-08
  • 1970-01-01
  • 1970-01-01
  • 2023-01-12
相关资源
最近更新 更多