Как этот Perl-код выбирает два разных элемента из массива? - PullRequest
8 голосов
/ 23 апреля 2010

Я унаследовал некоторый код от парня, который в прошлом любил сокращать каждую строку до абсолютного минимума (а иногда только, чтобы она выглядела круто). Его код трудно понять, но мне удалось понять (и переписать) большую его часть.

Теперь я наткнулся на кусок кода, который, как бы я ни старался, я не могу понять.

my @heads = grep {s/\.txt$//} OSA::Fast::IO::Ls->ls($SysKey,'fo','osr/tiparlo',qr{^\d+\.txt$}) || ();
my @selected_heads = ();
for my $i (0..1) {
   $selected_heads[$i] = int rand scalar @heads;
   for my $j (0..@heads-1) {
      last if (!grep $j eq $_, @selected_heads[0..$i-1]);
      $selected_heads[$i] = ($selected_heads[$i] + 1) % @heads; #WTF?
   }
   my $head_nr = sprintf "%04d", $i;
   OSA::Fast::IO::Cp->cp($SysKey,'',"osr/tiparlo/$heads[$selected_heads[$i]].txt","$recdir/heads/$head_nr.txt");
   OSA::Fast::IO::Cp->cp($SysKey,'',"osr/tiparlo/$heads[$selected_heads[$i]].cache","$recdir/heads/$head_nr.cache");
}

Из того, что я могу понять, предполагается, что это какой-то рандомизатор, но я никогда не видел более сложного способа достижения случайности. Или мои предположения неверны? По крайней мере, это то, что этот код должен делать. Выберите 2 случайных файла и скопируйте их.

=== ПРИМЕЧАНИЯ ===

OSA Framework является нашей собственной платформой. Они названы в честь своих коллег в UNIX и проводят некоторое базовое тестирование, чтобы приложение не беспокоилось об этом.

Ответы [ 5 ]

12 голосов
/ 23 апреля 2010

Это похоже на некоторый C-код с синтаксисом Perl. Иногда знание языка, на котором думает человек, помогает понять, что происходит. В этом случае мозг человека заражен внутренними механизмами управления памятью, арифметикой указателей и другими проблемами низкого уровня, поэтому он хочет тщательно контролировать все:

my @selected_heads = ();

# a tricky way to make a two element array
for my $i (0..1) {

   # choose a random file
   $selected_heads[$i] = int rand @heads;

   # for all the files (could use $#heads instead)
   for my $j (0..@heads-1) {
      # stop if the chosen file is not already in @selected_heads
      # it's that damned ! in front of the grep that's mind-warping
      last if (!grep $j eq $_, @selected_heads[0..$i-1]);

      # if we are this far, the two files we selected are the same
      # choose a different file if we're this far
      $selected_heads[$i] = ($selected_heads[$i] + 1) % @heads; #WTF?
   }

...
}

Это большая работа, потому что оригинальный программист либо не понимает хэши, либо не любит их.

my %selected_heads;
until( keys %selected_heads == 2 )
    {
    my $try = int rand @heads;
    redo if exists $selected_heads{$try};
    $selected_heads{$try}++;
    }

my @selected_heads = keys %selected_heads;

Если вы все еще ненавидите хеши и используете Perl 5.10 или более позднюю версию, вы можете использовать smart-match, чтобы проверить, есть ли значение в массиве:

my @selected_heads;
until( @selected_heads == 2 )
    {
    my $try = int rand @heads;
    redo if $try ~~ @selected_heads;
    push @selected_heads, $try;
    }

Однако у вас есть особое ограничение на эту проблему. Поскольку вы знаете, что есть только два элемента, вам просто нужно проверить, является ли элемент, который вы хотите добавить, предшествующим элементом. В первом случае это не будет undef, поэтому первое дополнение всегда работает. Во втором случае он просто не может быть последним элементом в массиве:

my @selected_heads;
until( @selected_heads == 2 )
    {
    my $try = int rand @heads;
    redo if $try eq $selected_heads[-1];
    push @selected_heads, $try;
    }

Ха. Я не могу вспомнить, когда в последний раз я использовал until, когда это действительно соответствовало проблеме. :)

Обратите внимание, что все эти решения имеют проблему, заключающуюся в том, что они могут вызывать бесконечный цикл, если число исходных файлов меньше 2. Я бы добавил условие защиты выше, чтобы в случае отсутствия и одиночного файла возникала ошибка возможно, дело о двух файлах не стоит их заказывать.

Другой способ сделать это - перемешать (скажем, с помощью List :: Util ) весь список оригинальных файлов и просто удалить первые два файла:

use List::Util qw(shuffle);

my @input = 'a' .. 'z';

my @two = ( shuffle( @input ) )[0,1];

print "selected: @two\n";
2 голосов
/ 23 апреля 2010

Выбирает случайный элемент из @глав.

Затем он добавляет еще один случайный , но отличный элемент от @heads (если это элемент, выбранный ранее, он просматривает @heads до тех пор, пока не найдет элемент, ранее не выбранный).

Таким образом, он выбирает N (в вашем случае N = 2) различных случайных индексов в массиве @heads, а затем копирует файлы, соответствующие этим индексам.

Лично я бы написал по-другому:

# ...
%selected_previously = ();
foreach my $i (0..$N) { # Generalize for N random files instead of 2
    my $random_head_index = int rand scalar @heads;
    while ($selected_previously[$random_head_index]++) {
        $random_head_index = $random_head_index + 1) % @heads; # Cache me!!!
    }
    # NOTE: "++" in the while() might be considered a bit of a hack
    # More readable version: $selected_previously[$random_head_index]=1; here.
1 голос
/ 23 апреля 2010

Как насчет

for my $i (0..1) {
    my $selected = splice( @heads, rand @heads, 1 );
    my $head_nr = sprintf "%04d", $i;
    OSA::Fast::IO::Cp->cp($SysKey,'',"osr/tiparlo/$selected.txt","$recdir/heads/$head_nr.txt");
    OSA::Fast::IO::Cp->cp($SysKey,'',"osr/tiparlo/$selected.cache","$recdir/heads/$head_nr.cache");
}

, если @heads или @selected_heads не будут использованы позже.

1 голос
/ 23 апреля 2010

Часть, которую вы пометили как "WTF", не так беспокоит, она просто гарантирует, что $selected_heads[$i] останется действительным индексом @head.Поистине тревожным моментом является то, что это довольно неэффективный способ убедиться, что он не выбирает один и тот же файл.

Опять же, если размер @heads невелик, переход с 0..$#heads, вероятно, более эффективен, чем просто генерация int rand( 2 ) и тестирование на совпадение.

Но в основном он случайным образом копирует два файла (почему?) В виде файла .txt и файла .cache.

0 голосов
/ 23 апреля 2010

Вот еще один способ выбрать 2 уникальных случайных индекса:

my @selected_heads = ();
my @indices = 0..$#heads;
for my $i (0..1) {
  my $j = int rand (@heads - $i);
  push @selected_heads, $indices[$j];
  $indices[$j] = $indices[@heads - $i - 1];
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...