Я наследовал некоторый код от парня, любимый прошлый раз которого должен был сократить каждую строку к своему абсолютному минимуму (и иногда только, чтобы заставить его выглядеть прохладным). Его код трудно понять, но мне удалось понять (и переписать) большая часть из него.
Теперь я наткнулся на часть кода, который, неважно, как трудно я пробую, я не могу понять.
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");
}
Из того, что я могу понять, это, как предполагается, некоторый randomizer, но я никогда не видел более сложный способ достигнуть случайности. Или мои предположения неправильно? По крайней мере, это - то, что этот код, как предполагается, делает. Выберите 2 случайных файла и скопируйте их.
=== ПРИМЕЧАНИЯ ===
Платформой OSA является собственная Платформа. Их называют в честь их дубликатов UNIX и делают некоторое основное тестирование так, чтобы приложение не должно было беспокоиться этим.
Это похоже на некоторый код 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 или новее, вы можете использовать интеллектуальное сопоставление, чтобы проверить, находится ли значение в массиве:
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;
}
Ха. Я не могу вспомнить, когда я последний раз использовал , до
, когда это действительно соответствовало задаче. :)
Обратите внимание, что у всех этих решений есть проблема, заключающаяся в том, что они могут вызвать бесконечный цикл, если количество исходных файлов меньше 2. Я бы добавил условие защиты выше, так что случаи отсутствия и одного файла через ошибка и, возможно, дело с двумя файлами не потрудится их упорядочить.
Другой способ сделать это - перетасовать (скажем, с помощью List :: Util ) весь список исходных файлов и просто удалить первые два файла:
use List::Util qw(shuffle);
my @input = 'a' .. 'z';
my @two = ( shuffle( @input ) )[0,1];
print "selected: @two\n";
Вот еще один способ выбрать 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];
}
Выбирает случайный элемент из @heads.
Затем он добавляет еще один случайный , но отличный элемент из @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.
Как насчет
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
.
Часть, которую вы пометили "WTF", не так уж и беспокоит, она просто проверяет, что $ selected_heads [$ i]
остается действительным индекс @head
. Что действительно беспокоит, так это то, что это довольно неэффективный способ убедиться, что он не выбирает один и тот же файл.
Опять же, если размер @heads
мал, переход от 0 .. $ # Heads
, вероятно, более эффективен, чем просто создание int rand (2)
и проверка, совпадают ли они.
Но в основном он копирует два файла в случайном порядке (почему?) Как файл .txt и файл .cache.