在 Perl 中,如何测试序列是否为 n, n + 形式? 1、n+ 2, ..., n + k?

发布于 2024-12-16 15:29:45 字数 594 浏览 0 评论 0原文

我正在尝试实现一个子例程,它接受一个数组作为其参数(或使用多个参数 - 仍然没有完全理解差异),并根据该数组是否是一个返回 true 或 false递增序列(每个数字必须比最后一个数字多 1):

isIncreasingArray(1,2,3,4); # true
isIncreasingArray(1,2,3,1); # false
isIncreasingArray(0,9,1);   # false
isIncreasingArray(-2,-1,0); # true
isIncreasingArray(1,1,1,1); # false

这就是我的想法:

sub isIncreasingArray {

    my $last;

    foreach $n (@_) {
        return 0 if defined($last) && $last != $n - 1;
        $last = int($n);
    }

    return 1;

}

我对 Perl 很陌生,想知道是否有更简单或更简洁的方法来实现这一点?另外,我写的内容符合最佳实践吗?

I'm trying to implement a subroutine that takes an array as its argument (or uses multiple arguments — still haven't quite grokked the difference), and returns true or false depending on whether that array is an increasing sequence (each number must be 1 more than the last):

isIncreasingArray(1,2,3,4); # true
isIncreasingArray(1,2,3,1); # false
isIncreasingArray(0,9,1);   # false
isIncreasingArray(-2,-1,0); # true
isIncreasingArray(1,1,1,1); # false

This is what I've come up with:

sub isIncreasingArray {

    my $last;

    foreach $n (@_) {
        return 0 if defined($last) && $last != $n - 1;
        $last = int($n);
    }

    return 1;

}

I'm quite new to Perl and am wondering if there's a simpler or more concise way of achieving this? Also, is what I've written in line with best practices?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

迷迭香的记忆 2024-12-23 15:29:47

这是我能想到的最短形式,检查映射中的每个元素,看看它是否等于增加的 self,返回一组 0 和 1,计算 1 并与该集合的原始大小进行匹配。

print isIncreasingArray(1,2,3),"\n";
print isIncreasingArray(1,2,1),"\n";
print isIncreasingArray(1,2),"\n";
print isIncreasingArray(1),"\n";

sub isIncreasingArray {
  $i = $_[0];
  (scalar grep { 1 == $_ } map { $i++ == $_ } @_) == scalar(@_) || 0;
}

This is the shortest form I could come up to, check each element in a map to see if it is equal to the increased self, return a set of 0 and 1, count the 1 and match against the original size of the set.

print isIncreasingArray(1,2,3),"\n";
print isIncreasingArray(1,2,1),"\n";
print isIncreasingArray(1,2),"\n";
print isIncreasingArray(1),"\n";

sub isIncreasingArray {
  $i = $_[0];
  (scalar grep { 1 == $_ } map { $i++ == $_ } @_) == scalar(@_) || 0;
}
相权↑美人 2024-12-23 15:29:47

无论您使用哪种实现,事先进行一些快速检查都不会有什么坏处:

sub isSimplyIncreasingSequence {
   return 1 if @_ < 2;
   return 0 if $_[-1] - $_[0] != $#_;
   ...
}

Whatever implementation you use, it wouldn't hurt to make some quick checks beforehand:

sub isSimplyIncreasingSequence {
   return 1 if @_ < 2;
   return 0 if $_[-1] - $_[0] != $#_;
   ...
}
花开柳相依 2024-12-23 15:29:46

为什么没有人想出智能匹配的解决方案?

虽然这个解决方案不如其他一些解决方案高效,但它还具有使用字符串的额外好处。

编辑

Sub 现在对于空列表和单元素列表返回 true,因为这就是专家所说的应该做的:

use strict;
use warnings;
use 5.010;

sub is_simply_increasing { @_ < 2 || @_ ~~ [$_[0] .. $_[-1]] }

say (  is_simply_increasing(1,2,3,4)        ? 'true' : 'false' );  # true
say (  is_simply_increasing(1,2,3,1)        ? 'true' : 'false' );  # false
say (  is_simply_increasing(0,9,1)          ? 'true' : 'false' );  # false
say (  is_simply_increasing(-2,-1,0)        ? 'true' : 'false' );  # true
say (  is_simply_increasing(1,1,1,1)        ? 'true' : 'false' );  # false
say (  is_simply_increasing(1,4,1,-1)       ? 'true' : 'false' );  # false
say (  is_simply_increasing('a','c')        ? 'true' : 'false' );  # false
say (  is_simply_increasing('love'..'perl') ? 'true' : 'false' );  # true
say (  is_simply_increasing(2)              ? 'true' : 'false' );  # true
say (  is_simply_increasing()               ? 'true' : 'false' );  # true

我喜欢我的潜艇是单线的!

How come no one's come up with a smart-match solution?

While this one isn't as efficient as some of the other solutions, it has the added benefit of working with strings as well.

EDIT

Sub now returns true for empty and single-element lists because that's what the experts say it should do:

use strict;
use warnings;
use 5.010;

sub is_simply_increasing { @_ < 2 || @_ ~~ [$_[0] .. $_[-1]] }

say (  is_simply_increasing(1,2,3,4)        ? 'true' : 'false' );  # true
say (  is_simply_increasing(1,2,3,1)        ? 'true' : 'false' );  # false
say (  is_simply_increasing(0,9,1)          ? 'true' : 'false' );  # false
say (  is_simply_increasing(-2,-1,0)        ? 'true' : 'false' );  # true
say (  is_simply_increasing(1,1,1,1)        ? 'true' : 'false' );  # false
say (  is_simply_increasing(1,4,1,-1)       ? 'true' : 'false' );  # false
say (  is_simply_increasing('a','c')        ? 'true' : 'false' );  # false
say (  is_simply_increasing('love'..'perl') ? 'true' : 'false' );  # true
say (  is_simply_increasing(2)              ? 'true' : 'false' );  # true
say (  is_simply_increasing()               ? 'true' : 'false' );  # true

I love it when my sub's a single-line!

太阳哥哥 2024-12-23 15:29:46

我最终得到的东西比你的长一点。我认为这意味着您的解决方案没有任何问题:)

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

use Test::More;

sub is_increasing_array {
  return unless @_;
  return 1 if @_ == 1;

  foreach (1 .. $#_) {
    return if $_[$_] != $_[$_ - 1] + 1;
  }

  return 1;
}

ok(is_increasing_array(1,2,3,4));  # true
ok(!is_increasing_array(1,2,3,1)); # false
ok(!is_increasing_array(0,9,1));   # false
ok(is_increasing_array(-2,-1,0));  # true
ok(!is_increasing_array(1,1,1,1)); # false

done_testing;

I ended up with something a little longer than yours. Which means, I suppose, that there's nothing wrong with your solution :)

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

use Test::More;

sub is_increasing_array {
  return unless @_;
  return 1 if @_ == 1;

  foreach (1 .. $#_) {
    return if $_[$_] != $_[$_ - 1] + 1;
  }

  return 1;
}

ok(is_increasing_array(1,2,3,4));  # true
ok(!is_increasing_array(1,2,3,1)); # false
ok(!is_increasing_array(0,9,1));   # false
ok(is_increasing_array(-2,-1,0));  # true
ok(!is_increasing_array(1,1,1,1)); # false

done_testing;
ぃ弥猫深巷。 2024-12-23 15:29:46

使用 6 之前的“连接点”:

sub is_increasing_list { 
    use List::MoreUtils qw<none>;
    my $a = shift;
    return none { 
        ( my $v, $a ) = (( $_ - $a != 1 ), $_ ); 
        $v;
    } @_;
}

none 表达式也可以写成(更神秘)为

return none { [ ( $a, undef ) = ( $_, ( $_ - $a - 1 )) ]->[-1]; } @_;

(如果约束是 $x[$n+1] - $x[$n] = = 1,然后减去 1 也得到了“Perl 真值条件”。)

实际上,认为“无”连接运算符有点倒退了这个概念,所以我将使用 all

sub is_increasing_list { 
    use List::MoreUtils qw<all>;
    my $a = shift;
    return all { [ ( $a, undef ) = ( $_, ( $_ - $a == 1 )) ]->[-1]; } @_;
}

Using a pre-6 "junction":

sub is_increasing_list { 
    use List::MoreUtils qw<none>;
    my $a = shift;
    return none { 
        ( my $v, $a ) = (( $_ - $a != 1 ), $_ ); 
        $v;
    } @_;
}

The none expression could also be written (more cryptically) as

return none { [ ( $a, undef ) = ( $_, ( $_ - $a - 1 )) ]->[-1]; } @_;

(If the constraint is that $x[$n+1] - $x[$n] == 1, then subtracting 1 makes a "Perl truth condition" as well.)

Actually come to think of it a 'none' junction operator is kind of backward to the concept, so I'll use the all:

sub is_increasing_list { 
    use List::MoreUtils qw<all>;
    my $a = shift;
    return all { [ ( $a, undef ) = ( $_, ( $_ - $a == 1 )) ]->[-1]; } @_;
}
臻嫒无言 2024-12-23 15:29:46

有人必须在这里引入函数式编程解决方案,因为这种数学公式只需要递归。 ;)

sub isIncreasingArray {
  return 1 if @_ <= 1;   
  return (pop(@_) - $_[-1] == 1) && isIncreasingArray(@_);
}

至于子例程参数是数组还是多个参数,请这样想:Perl 总是将参数列表作为数组 @_ 发送到子例程。您可以从该数组中移动或弹出参数作为单独的标量,或者以其他方式将整个列表作为数组进行操作。从你的子例程内部来看,它仍然是一个数组,句号。

如果您涉及引用,是的,您可以将对数组的引用传递到子例程中。从技术上讲,该引用仍然作为包含一个标量值的数组(列表)传递给您的子例程:引用。首先,我会忽略所有这些,让您在没有参考资料的情况下专注于基本操作。

调用子程序。这样,Perl 就会秘密地将您的标量列表转换为标量数组:

isIncreasingArray(1,2,3,4); 

这样,Perl 就会传递您的数组:

@a = (1,2,3,4);
$answer = isIncreasingArray(@a); 

无论哪种方式,子例程都会获得一个数组。这是一个副本*,因此这里讨论参考文献的效率。不要担心 K<10,000,即使我这里的解决方案效率低得可笑,学术性的,优雅的,递归的,在我的笔记本电脑上仍然需要不到 1 秒:

print isIncreasingArray(1..10000), "\n"; # true

*副本:有点,但不是真的?请参阅下面的评论和其他资源,例如 PerlMonks。 “有人可能会说 Perl 总是进行引用传递,但可以保护我们免受我们自己的伤害。”有时。在实践中,我在子例程内将自己的副本复制到本地化的“我的”变量中。就这么做吧。

Someone has to throw in the functional-programming solution here, since this sort of mathematical formula just begs for recursion. ;)

sub isIncreasingArray {
  return 1 if @_ <= 1;   
  return (pop(@_) - $_[-1] == 1) && isIncreasingArray(@_);
}

As for a subroutine argument being an array versus multiple arguments, think of it this way: Perl is always sending a list of arguments to your subroutine as the array @_. You can either shift or pop off arguments from that array as individual scalars, or otherwise operate on the whole list as an array. From inside your subroutine, it's still an array, period.

If you get into references, yes you can pass a reference-to-an-array into a subroutine. That reference is still technically being passed to your subroutine as an array (list) containing one scalar value: the reference. First I'd ignore all this and wrap your head around basic operation without references.

Calling the subroutine. This way, Perl is secretly converting your bare list of scalars into an array of scalars:

isIncreasingArray(1,2,3,4); 

This way, Perl is passing your array:

@a = (1,2,3,4);
$answer = isIncreasingArray(@a); 

Either way, the subroutine gets an array. And it's a copy*, hence the efficiency talk of references here. Don't worry about that for K<10,000, even with my ridiculously inefficient, academic, elegant, recursive solution here, which still takes under 1 second on my laptop:

print isIncreasingArray(1..10000), "\n"; # true

*A copy: sort of, but not really? See comments below, and other resources, e.g. PerlMonks. "One might argue that Perl always does Pass-By-Reference, but protects us from ourselves." Sometimes. In practice I make my own copies inside subroutines into localized "my" variables. Just do that.

沧桑㈠ 2024-12-23 15:29:45

有几点:

  1. 为了提高效率,特别是为了最大限度地减少内存占用,您可能希望将对数组的引用传递给子例程。

  2. 在列表上下文中,return 0 将返回由单个元素组成的列表,因此为 true。当您想要返回 false 并在所有上下文中执行此操作时,只需 return 就足够了。

通过比较第一个和最后一个、第二个和倒数第二个等之间的差异,可以将比较次数减少一半,以查看差异等于索引差异,但我不认为这样 现在就很清楚了。

这是与您的版本略有不同的版本。请注意,您应该使用 strict 并确保使用 my 确定循环变量的范围:

#!/usr/bin/env perl

use strict; use warnings;

use Carp qw(croak);
use Test::More;

ok(     isSimplyIncreasingSequence( [ 1298 ]  ) ); # true
ok(     isSimplyIncreasingSequence( [1,2,3,4] ) ); # true
ok( not isSimplyIncreasingSequence( [1,2,3,1] ) ); # false
ok( not isSimplyIncreasingSequence( [0,9,1]   ) ); # false
ok(     isSimplyIncreasingSequence( [-2,-1,0] ) ); # true
ok( not isSimplyIncreasingSequence( [1,1,1,1] ) ); # false

done_testing();

sub isSimplyIncreasingSequence {
    my ($seq) = @_;

    unless (defined($seq)
            and ('ARRAY' eq ref $seq)) {
        croak 'Expecting a reference to an array as first argument';
    }

    return 1 if @$seq < 2;

    my $first = $seq->[0];

    for my $n (1 .. $#$seq) {
        return unless $seq->[$n] == $first + $n;
    }

    return 1;
}

当然,还有一些基准测试:

#!/usr/bin/env perl

use strict; use warnings;

use Benchmark qw( cmpthese );
use Carp qw( croak );

my %cases = (
    ordered_large => [1 .. 1_000_000],
    ordered_small => [1 .. 10],
    unordered_large_beg => [5, 1 .. 999_000],
    unordered_large_mid => [1 .. 500_000, 5, 500_002 .. 1_000_000],
    unordered_large_end => [1 .. 999_999, 5],
);

for my $case (keys %cases) {
    print "=== Case: $case\n";
    my $seq = $cases{$case};
    cmpthese -3, {
        'ref'  => sub { isSimplyIncreasingSequence($seq) },
        'flat' => sub {isIncreasingArray(@{ $seq } ) },
    };
}

sub isSimplyIncreasingSequence {
    my ($seq) = @_;

    unless (defined($seq)
            and ('ARRAY' eq ref $seq)) {
        croak 'Expecting a reference to an array as first argument';
    }

    return 1 if @$seq < 2;

    my $first = $seq->[0];

    for my $n (1 .. $#$seq) {
        return unless $seq->[$n] == $first + $n;
    }

    return 1;
}

sub isIncreasingArray {

    my $last;

    foreach my $n (@_) {
        return 0 if defined($last) && $last != $n - 1;
        $last = int($n);
    }

    return 1;

}
=== Case: unordered_large_mid
       Rate flat  ref
flat 4.64/s   -- -18%
ref  5.67/s  22%   --
=== Case: ordered_small
         Rate  ref flat
ref  154202/s   -- -11%
flat 173063/s  12%   --
=== Case: ordered_large
       Rate flat  ref
flat 2.41/s   -- -13%
ref  2.78/s  15%   --
=== Case: unordered_large_beg
       Rate flat  ref
flat 54.2/s   -- -83%
ref   315/s 481%   --
=== Case: unordered_large_end
       Rate flat  ref
flat 2.41/s   -- -12%
ref  2.74/s  14%   --

A couple of points:

  1. For efficiency, especially to minimize memory footprint, you probably want to pass a reference to an array to the subroutine.

  2. In list context, return 0 will return a list consisting of a single element and thus will be true. a bare return suffices when you want to return false and does the job in all contexts.

It is probably possible to cut the number of comparisons in half by comparing the difference between the first and the last, the second and the second last etc. to see differences equal difference in indexes, but I am not thinking that clearly right now.

Here is a slightly different version based on yours. Note that you should use strict and make sure to scope your loop variable using my:

#!/usr/bin/env perl

use strict; use warnings;

use Carp qw(croak);
use Test::More;

ok(     isSimplyIncreasingSequence( [ 1298 ]  ) ); # true
ok(     isSimplyIncreasingSequence( [1,2,3,4] ) ); # true
ok( not isSimplyIncreasingSequence( [1,2,3,1] ) ); # false
ok( not isSimplyIncreasingSequence( [0,9,1]   ) ); # false
ok(     isSimplyIncreasingSequence( [-2,-1,0] ) ); # true
ok( not isSimplyIncreasingSequence( [1,1,1,1] ) ); # false

done_testing();

sub isSimplyIncreasingSequence {
    my ($seq) = @_;

    unless (defined($seq)
            and ('ARRAY' eq ref $seq)) {
        croak 'Expecting a reference to an array as first argument';
    }

    return 1 if @$seq < 2;

    my $first = $seq->[0];

    for my $n (1 .. $#$seq) {
        return unless $seq->[$n] == $first + $n;
    }

    return 1;
}

And, of course, some benchmarks:

#!/usr/bin/env perl

use strict; use warnings;

use Benchmark qw( cmpthese );
use Carp qw( croak );

my %cases = (
    ordered_large => [1 .. 1_000_000],
    ordered_small => [1 .. 10],
    unordered_large_beg => [5, 1 .. 999_000],
    unordered_large_mid => [1 .. 500_000, 5, 500_002 .. 1_000_000],
    unordered_large_end => [1 .. 999_999, 5],
);

for my $case (keys %cases) {
    print "=== Case: $case\n";
    my $seq = $cases{$case};
    cmpthese -3, {
        'ref'  => sub { isSimplyIncreasingSequence($seq) },
        'flat' => sub {isIncreasingArray(@{ $seq } ) },
    };
}

sub isSimplyIncreasingSequence {
    my ($seq) = @_;

    unless (defined($seq)
            and ('ARRAY' eq ref $seq)) {
        croak 'Expecting a reference to an array as first argument';
    }

    return 1 if @$seq < 2;

    my $first = $seq->[0];

    for my $n (1 .. $#$seq) {
        return unless $seq->[$n] == $first + $n;
    }

    return 1;
}

sub isIncreasingArray {

    my $last;

    foreach my $n (@_) {
        return 0 if defined($last) && $last != $n - 1;
        $last = int($n);
    }

    return 1;

}
=== Case: unordered_large_mid
       Rate flat  ref
flat 4.64/s   -- -18%
ref  5.67/s  22%   --
=== Case: ordered_small
         Rate  ref flat
ref  154202/s   -- -11%
flat 173063/s  12%   --
=== Case: ordered_large
       Rate flat  ref
flat 2.41/s   -- -13%
ref  2.78/s  15%   --
=== Case: unordered_large_beg
       Rate flat  ref
flat 54.2/s   -- -83%
ref   315/s 481%   --
=== Case: unordered_large_end
       Rate flat  ref
flat 2.41/s   -- -12%
ref  2.74/s  14%   --
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文