Есть ли компактная операция Perl для среза альтернативных элементов из массива?
Если у меня есть массив myarray
в Python, я могу использовать нотацию среза
myarray[0::2]
чтобы выбрать только элементы с четными индексами. Например:
>>> ar = [ "zero", "one", "two", "three", "four", "five", "six" ]
>>> ar [ 0 : : 2 ]
['zero', 'two', 'four', 'six']
Есть ли аналогичный объект в Perl?
Спасибо.
Ответы
Ответ 1
Здесь есть срезы массива:
my @slice = @array[1,42,23,0];
Есть способ генерировать списки между $x и $y:
my @list = $x .. $y
Есть способ создания новых списков из списков:
my @new = map { $_ * 2 } @list;
И есть способ получить длину массива:
my $len = $#array;
Объединить вместе:
my @even_indexed_elements = @array[map { $_ * 2 } 0 .. int($#array / 2)];
Конечно, не так хорошо, как эквивалент python, но он выполняет ту же работу, и вы можете, конечно, поместить это в подпрограмму, если вы ее много используете и хотите спасти себя от написания.
Также возможно что-то, что позволило бы записать это более естественным образом в List::AllUtils
.
Ответ 2
Атрибут массива Perl - это @
перед именем массива, затем список индексов, который вы хотите:
@array[@indices];
Там нет встроенного синтаксиса для выбора кратных, но это не так сложно. Используйте grep() для получения списка индексов, которые вы хотите:
my @array = qw( zero one two three four five six );
my @evens = @array[ grep { ! ($_ % 2) } 0 .. $#array ];
Если вы используете PDL, есть много хороших вариантов нарезки.
Ответ 3
Я написал модуль List::Gen в CPAN, который предоставляет альтернативный способ сделать это:
use List::Gen qw/by/;
my @array = qw/zero one two three four five six/;
my @slice = map {$$_[0]} by 2 => @array;
by
разделы @array
в группы из двух элементов и возвращает массив ссылок массива. map
затем получает этот список, поэтому каждый $_
на карте будет ссылкой на массив. $$_[0]
(который также можно записать $_->[0]
), затем захватывает первый элемент каждой группы, созданный by
.
Или, используя функцию mapn
, которая by
использует внутренне:
use List::Gen qw/mapn/;
my @slice = mapn {$_[0]} 2 => @array;
Или, если ваш исходный список огромен, и вам могут понадобиться только определенные элементы, вы можете использовать List::Gen
ленивые списки:
use List::Gen qw/by gen/;
my $slicer = gen {$$_[0]} by 2 => @array;
$slicer
теперь представляет собой ленивый список (массив ref), который будет генерировать его срезы по требованию без обработки всего, что вы не просили. $slicer
также имеет кучу методов доступа, если вы не хотите использовать его как массив ref.
Ответ 4
Я сделаю это в двухэтапном процессе: сначала создайте нужные индексы, а затем используйте операцию среза, чтобы извлечь их:
@indices = map { $_ * 2 } (0 .. int($#array / 2));
my @extracted = @array[@indices];
Шаг за шагом, thats:
- сгенерировать список целых чисел от 0 до последнего элемента массива, деленный на два
- умножьте каждое целое на два: теперь мы имеем четные числа от нуля до индекса последнего элемента
- извлеките эти элементы из исходного массива
Ответ 5
Perl 6 значительно улучшит ситуацию, но (до сих пор?). Perl 5 имеет довольно ограниченную возможность срезания: вам нужно явно указать нужные вам индексы, и он не может быть открытым.
Итак, вам нужно будет сделать:
@ar = ( "zero", "one", "two", "three", "four", "five", "six" );
print @ar[ grep $_ % 2 == 0, 0..$#ar ]
Ответ 6
Один из способов сделать это красивее - обернуть его чем-то вроде autobox
.
Например, используя autobox::Core
:
use autobox::Core;
my @ar = qw/zero one two three four five six/;
# you could do this
@ar->slice_while( sub{ not $_ % 2 } );
# and this
@ar->slice_by(2);
# or even this
@ar->evens;
Вот как вы можете определить эти методы autobox:
sub autobox::Core::ARRAY::slice_while {
my ($self, $code) = @_;
my @array;
for (my $i = 0; $i <= $#{ $self }; $i++) {
local $_ = $i;
push @array, $self->[ $i ] if $code->();
}
return wantarray ? @array : \@array;
}
sub autobox::Core::ARRAY::slice_by {
my ($self, $by) = @_;
my @array = @$self[ map { $_ * $by } 0 .. int( $#{$self} / $by )];
return wantarray ? @array : \@array;
}
sub autobox::Core::ARRAY::evens {
my $self = shift;
my @array = $self->slice_by(2);
return wantarray ? @array : \@array;
}
/I3az/
Ответ 7
Если вам не нужен порядок, и если элементы с нечетным номером списка уникальны, вы можете кратко преобразовать массив в хэш и взять values
:
@even_elements = values %{{@array}};
@odd_elements = keys %{{@array}};
(Нет, это не серьезный ответ)
Ответ 8
Другим способом будет использование grep
:
my @array = qw( zero one two three four five six );
print map { "$_ " } @array[grep { !($_ & 1) } 0 .. $#array]; #even
Output:zero two four six
print map { "$_ " } @array[grep { ($_ & 1) } 0 .. $#array]; #odd
Output:one three five
Ответ 9
Если вы не возражаете использовать неясную особенность $| вы можете сделать это:
{
local $|; # don't mess with global $|
@ar = ( "zero", "one", "two", "three", "four", "five", "six" );
$| = 0;
@even = grep --$|, @ar;
$| = 1;
@odd = grep --$|, @ar;
print "even: @even\\n";
# even: zero two four six
print "odd: @odd\\n";
# odd: one three five
}
или, как 1 лайнер:
{ local $|=0; @even = grep --$|, @ar; }
В принципе, - $| flip flops между 0 и 1 значением (несмотря на то, что обычно уменьшает числовое значение), поэтому grep видит "истинное" значение каждый раз, заставляя его возвращать каждый другой элемент, начиная с начального значения $|. Обратите внимание, что вы должны начинать с 0 или 1, а не с каким-то произвольным индексом.
Ответ 10
Вот простейший код без создания каких-либо массивов индексов:
sub even { my $f=0; return grep {++$f%2} @_; }
sub odd { my $f=1; return grep {++$f%2} @_; }