В Perl, как я могу проверить, имеет ли последовательность форму n, n + 1, n + 2, ..., n + k? - PullRequest
17 голосов
/ 17 ноября 2011

Я пытаюсь реализовать подпрограмму, которая принимает массив в качестве аргумента ( или использует несколько аргументов - все еще не совсем уловил разницу ), и возвращает 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 и мне интересно, есть ли более простой или более лаконичный способ достижения этого?Кроме того, соответствует ли то, что я написал, передовым практикам?

Ответы [ 7 ]

16 голосов
/ 17 ноября 2011

Пара баллов:

  1. Для эффективности, особенно для минимизации использования памяти, вы, вероятно, захотите передать ссылку на массив в подпрограмму.

  2. В контексте списка return 0 вернет список, состоящий из одного элемента, и, следовательно, будет истинным. голого return достаточно, если вы хотите вернуть false и выполняет работу во всех контекстах.

Вероятно, можно сократить количество сравнений пополам, сравнивая разницу между первым и последним, вторым и вторым последним и т. Д., Чтобы увидеть разницу равных различий в показателях, но я не думаю, это ясно прямо сейчас.

Вот немного другая версия, основанная на вашей. Обратите внимание, что вы должны использовать 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%   --
9 голосов
/ 17 ноября 2011

Почему никто не придумал решение для интеллектуального матча?

Хотя это не так эффективно, как некоторые другие решения, у него есть дополнительное преимущество работы со строками.

EDIT

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

Мне нравится, когда моя лодка однострочная!

8 голосов
/ 17 ноября 2011

Я закончил с чем-то немного длиннее твоего. Это означает, я полагаю, что в вашем решении нет ничего плохого:)

#!/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;
6 голосов
/ 17 ноября 2011

Использование пре-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».)

На самом деле, если подумать, оператор соединения «none» отчасти отстал от концепции, поэтому я буду использовать all:

sub is_increasing_list { 
    use List::MoreUtils qw<all>;
    my $a = shift;
    return all { [ ( $a, undef ) = ( $_, ( $_ - $a == 1 )) ]->[-1]; } @_;
}
5 голосов
/ 17 ноября 2011

Здесь кто-то должен добавить решение для функционального программирования, поскольку математическая формула такого рода просто требует рекурсии.;)

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 <10000, даже с моим смехотворно неэффективным, академическим, элегантным, рекурсивным решением, которое все еще занимает менее 1 секунды на моем ноутбуке: </p>

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

* Копия: своего родано не совсем?См. Комментарии ниже и другие ресурсы, например PerlMonks .«Можно утверждать, что Perl всегда делает Pass-By-Reference, но защищает нас от нас самих».Иногда.На практике я делаю свои собственные копии внутри подпрограмм в локализованные «мои» переменные.Просто сделай это.

4 голосов
/ 17 ноября 2011

Это самая короткая форма, которую я мог придумать, проверить каждый элемент на карте, чтобы увидеть, равен ли он самому себе, вернуть набор из 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;
}
3 голосов
/ 17 ноября 2011

Какую бы реализацию вы не использовали, не мешало бы сделать несколько быстрых проверок заранее:

sub isSimplyIncreasingSequence {
   return 1 if @_ < 2;
   return 0 if $_[-1] - $_[0] != $#_;
   ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...