在Perl

时间:2016-06-20 21:31:59

标签: arrays perl search

我有一个问题要解决,即从索引数组中找到目标版本的索引。索引数组看起来像:{16.3.1, 16.2.5, 16.1.4, 15.3.5, 15.1.1}

对于此数组中的每个单独项目(例如16.3.1),它将连接这三个部分:

  1. 16是年度发布号。
  2. 3是季度版本号,它在{1,2,3,4}范围内。
  3. 1是两周一次的版本号,它可以是这六个选项之一(1,2,3,4,5,6)。
  4. 数组按降序排序。
  5. 现在这些是要求:

    1. 如果我提供目标版本,例如16.1.4,此算法将返回该数组的匹配索引,即2。

    2. 如果我给的是16.1.5的目标版本(不在该数组中),那么它将返回下一个可用的索引,也就是2.

    3. 目标值始终高于15.1.1,这意味着它将始终返回有效索引。

    4. 我正在考虑将这样的值转换为数字,然后进行搜索。 (例如,16.1.4 => 16 * 24 + 1 * 6 + 4 = 394,...,)

      但我只是想知道是否有一种简单的方法可以解决这个问题?

3 个答案:

答案 0 :(得分:2)

Perl支持名为version strings的数据类型,它将版本号打包为带有一系列代码点的字符串。例如,v1.2.3将表示为字符串"\x1\x2\x3"

您可以使用v后跟点分十进制序列来创建这样的字符串,或者任何带有两个或更多点的点分十进制序列将以相同的方式处理,即使没有v

因此,我们可以非常简单地通过将版本字符串与List::MoreUtils中的first_index函数结合使用来解决您的问题,就像这样

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

use List::MoreUtils 'first_index';

my @versions = ( v16.3.1, v16.2.5, v16.1.4, v15.3.5, v15.1.1 );

for my $target ( v16.1.4, v16.1.5 ) {
  say first_index { $_ le $target } @versions;
}

输出

2
2

首先将版本字符串放入程序可能会出现问题,这就是我询问您当前如何阅读它们的原因。但如果你解释一下你需要什么,那真的不是什么大问题


更新

我已经改变了使用v16.1.4v16.1.5等的答案。之前它运行良好,但16.1.4与浮点值16.1完全不同的文字并不明显。另一方面,v16.1.4v16.1都是版本字符串

您也没有真正说出您的输入来自哪里。很公平,你可以声明一个文字数组,正如我在答案中所说的那样,但大概你的$target也不是文字,否则在编写程序方面没有什么意义。第一个地方

我希望你能谈谈这些东西来自哪里,以便我可以帮助你,但你可能需要查看version pragma,它提供了将在普通字符串和版本字符串之间进行转换的类方法

例如,如果目标是作为字符串提供的,您可以使用version->parse将其转换为版本字符串,这意味着上面的最后一个循环看起来像这样

use version;

for my $target ( "16.1.5", "16.1.4" ) {
    my $vs = version->parse($target);
    say first_index { $_ le $vs } @versions;
}

所以version->parse("16.1.4") eq v16.1.4总是 true

我希望这已经澄清而不是混淆

答案 1 :(得分:2)

您的解决方案基本上具有以下形式:

use List::MoreUtils qw( first_index );

my @versions = qw( 16.3.1 16.2.5 16.1.4 15.3.5 15.1.1 );
my $target = '16.1.4';

my $target_key = make_key($target);
my $index = first_index { make_key($_) <= $target_key } @versions;

对于长列表,您可以使用二进制搜索。

之前发布的解决方案假设您从硬编码值开始,而这个解释了如何从任何来源的字符串开始。

现在您只需要生成一个可以与字符串或数字比较运算符轻松比较的键。以下内容从最快到最慢排序:

# Use numerical comparison functions (<=).
sub make_key {
   my @parts = split(/\./, $_[0]);
   return ( $parts[0] * 4 + $parts[1] ) * 6 + $parts[2];
}

# Use string comparison functions (le).
sub make_key {
   my $key = '';
   $key .= chr($_) for split(/\./, $_[0]);
   return $key;
}

use Sort::Key::Natural qw( mkkey_natural );

# Use string comparison functions (le).
sub make_key { mkkey_natural($_[0]) }

第一个解决方案是实施您建议的公式。

第二个解决方案类似于version->parse,但没有您不需要的所有开销和特殊情况。

答案 2 :(得分:2)

您的版本字符串看起来很正常,您的描述支持这样的概念,即您可能只需进行字符串比较而无需进一步转换。他们所代表的领域不太可能随意改变(例如,季度将永远是季度)。因此,由于它们在字符串方面具有可比性,因此简单的字符串比较可能就足够了。

来自List::BinarySearchbinsearch_pos函数将提供目标元素的索引,或者如果找不到目标元素,则可以插入目标以保留顺序的索引。它是一个稳定的二进制搜索,因此它将始终返回目标匹配的最低索引。这些特征似乎完全符合您的需求:

use List::BinarySearch qw(binsearch_pos);

my @array = qw(
    16.3.1
    16.2.5
    16.1.4
    15.3.5
    15.1.1
);

print "$_: $array[$_]\t" foreach 0 .. $#array;
print "\n\n";

print "$_: ", (binsearch_pos {$b cmp $a} $_, @array), "\n"
    foreach qw(16.3.1  16.3.6  16.2.7  16.2.5  16.2.4  15.1.1  15.1.3  15.1.0);

如果版本列表很短,那么List::MoreUtils::first_ix是一种简单的线性方法,效率很高。如果列表足够大,二进制搜索可能值得考虑,因为它以对数方式而不是线性方式进行缩放。这意味着随着版本字符串列表的增长,搜索列表所需的时间将使用二进制搜索以比使用线性搜索更慢的速度增长。

由于您的列表按降序排列,因此此解决方案使用$b cmp $a,它可以按降序排列。