perllol
СОДЕРЖАНИЕ
ИМЯ
perllol - Обработка массивов массивов в Perl
ОПИСАНИЕ
Объявление и доступ к массивам массивов
Самой простой двухуровневой структурой данных для создания в Perl является массив массивов, иногда неофициально называемый списком списков. Он достаточно легко понимается, и почти всё, что применимо здесь, будет также применимо позже к более сложным структурам данных.
Массив массивов — это обычный массив @AoA, к которому можно обратиться с помощью двух индексов, например, $AoA[3][2]. Вот объявление массива:
use 5.010; # so we can use say()
# assign to our array, an array of array references
@AoA = (
[ "fred", "barney", "pebbles", "bambam", "dino", ],
[ "george", "jane", "elroy", "judy", ],
[ "homer", "bart", "marge", "maggie", ],
);
say $AoA[2][1];
bart Теперь нужно быть очень внимательным, что внешняя скобка должна быть круглой, то есть, скобкой. Это потому, что вы присваиваете @array, поэтому нужны круглые скобки. Если бы вы хотели, чтобы не было @AoA, а была только ссылка на него, вы могли бы сделать что-то вроде этого:
# assign a reference to array of array references
$ref_to_AoA = [
[ "fred", "barney", "pebbles", "bambam", "dino", ],
[ "george", "jane", "elroy", "judy", ],
[ "homer", "bart", "marge", "maggie", ],
];
say $ref_to_AoA->[2][1];
bart Обратите внимание, что внешний тип скобок изменился, и поэтому синтаксис доступа также изменился. Это потому, что в отличие от C, в Perl вы не можете свободно взаимозаменять массивы и ссылки на них. $ref_to_AoA — это ссылка на массив, а @AoA — это собственно массив. Аналогично, $AoA[2] не является массивом, а ссылкой на массив. Так почему же вы можете написать это:
$AoA[2][2]
$ref_to_AoA->[2][2] вместо того, чтобы писать это:
$AoA[2]->[2]
$ref_to_AoA->[2]->[2] Ну, это потому, что правило гласит, что только для соседних скобок (квадратных или фигурных) вы можете опустить стрелку-дериференцию указателя. Но вы не можете сделать это для самой первой, если это скаляр, содержащий ссылку, что означает, что $ref_to_AoA всегда нуждается в ней.
Создание собственного
Это хорошо для объявления фиксированной структуры данных, но что если вы хотите добавлять новые элементы динамически или создавать её с нуля?
Сначала посмотрим, как это читать из файла. Это что-то вроде добавления строки за раз. Предположим, что существует плоский файл, в котором каждая строка — это строка, а каждое слово — это элемент. Если вы пытаетесь создать массив @AoA, содержащий все это, вот правильный способ сделать это:
while (<>) {
@tmp = split;
push @AoA, [ @tmp ];
} Вы также могли загрузить это из функции:
for $i ( 1 .. 10 ) {
$AoA[$i] = [ somefunc($i) ];
} Или у вас могла быть временная переменная, содержащая массив.
for $i ( 1 .. 10 ) {
@tmp = somefunc($i);
$AoA[$i] = [ @tmp ];
} Важно убедиться, что вы используете конструктор [ ] ссылки на массив. Это потому, что это не сработает:
$AoA[$i] = @tmp; # WRONG! Причина, по которой это не делает того, что вы хотите, заключается в том, что присваивание именованного массива скаляру — это получение массива в скалярном контексте, что означает просто подсчёт количества элементов в @tmp.
Если вы работаете под use strict (а если нет, то почему бы вам вообще не быть?), вам нужно добавить некоторые объявления, чтобы это работало:
use strict;
my(@AoA, @tmp);
while (<>) {
@tmp = split;
push @AoA, [ @tmp ];
} Конечно, временный массив не обязательно должен иметь имя:
while (<>) {
push @AoA, [ split ];
} Вы также не обязаны использовать push(). Вы можете просто выполнить прямое присваивание, если знаете, куда хотите его поместить:
my (@AoA, $i, $line);
for $i ( 0 .. 10 ) {
$line = <>;
$AoA[$i] = [ split " ", $line ];
} или даже просто
my (@AoA, $i);
for $i ( 0 .. 10 ) {
$AoA[$i] = [ split " ", <> ];
} В общем, вам следует остерегаться использования функций, которые могут возвращать списки в скалярном контексте без явного указания этого. Это было бы понятнее обычному читателю:
my (@AoA, $i);
for $i ( 0 .. 10 ) {
$AoA[$i] = [ split " ", scalar(<>) ];
} Если вы хотели иметь переменную $ref_to_AoA как ссылку на массив, вам нужно было бы сделать что-то вроде этого:
while (<>) {
push @$ref_to_AoA, [ split ];
} Теперь вы можете добавлять новые строки. А как насчёт добавления новых столбцов? Если вы работаете только с матрицами, часто проще использовать простое присваивание:
for $x (1 .. 10) {
for $y (1 .. 10) {
$AoA[$x][$y] = func($x, $y);
}
}
for $x ( 3, 7, 9 ) {
$AoA[$x][20] += func2($x);
} Не имеет значения, существуют ли эти элементы или нет: он с удовольствием создаст их для вас, задавая промежуточные элементы в undef по мере необходимости.
Если вам нужно просто добавить в строку, вам нужно сделать что-то более необычное:
# add new columns to an existing row
push $AoA[0]->@*, "wilma", "betty"; # explicit deref Доступ и вывод
Теперь пришло время вывести вашу структуру данных. Как это сделать? Ну, если вам нужен только один элемент, это тривиально:
print $AoA[0][0]; Если вы хотите вывести всё, однако, вы не можете сказать
print @AoA; # WRONG потому что вы получите только перечисленные ссылки, и Perl никогда не будет автоматически дериференцировать вещи для вас. Вместо этого вам нужно самостоятельно написать цикл или два. Это выводит всю структуру, используя конструкцию цикла for в стиле оболочки для прохода по внешнему набору индексов.
for $aref ( @AoA ) {
say "\t [ @$aref ],";
} Если вы хотели отслеживать индексы, вы могли бы сделать это:
for $i ( 0 .. $#AoA ) {
say "\t elt $i is [ @{$AoA[$i]} ],";
} или, возможно, даже это. Обратите внимание на внутренний цикл.
for $i ( 0 .. $#AoA ) {
for $j ( 0 .. $#{$AoA[$i]} ) {
say "elt $i $j is $AoA[$i][$j]";
}
} Как видите, это становится немного сложно. Вот почему иногда легче использовать временную переменную на пути:
for $i ( 0 .. $#AoA ) {
$aref = $AoA[$i];
for $j ( 0 .. $#{$aref} ) {
say "elt $i $j is $AoA[$i][$j]";
}
} Хмм... это всё ещё немного некрасиво. А как насчёт этого:
for $i ( 0 .. $#AoA ) {
$aref = $AoA[$i];
$n = @$aref - 1;
for $j ( 0 .. $n ) {
say "elt $i $j is $AoA[$i][$j]";
}
} Когда вам надоест писать пользовательский вывод для ваших структур данных, вы можете взглянуть на стандартные модули Dumpvalue или Data::Dumper. Первый используется Perl отладчиком, а второй генерирует анализируемый Perl код. Например:
use v5.14; # using the + prototype, new to v5.14
sub show(+) {
require Dumpvalue;
state $prettily = new Dumpvalue::
tick => q("),
compactDump => 1, # comment these two lines
# out
veryCompact => 1, # if you want a bigger
# dump
;
dumpValue $prettily @_;
}
# Assign a list of array references to an array.
my @AoA = (
[ "fred", "barney" ],
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
);
push $AoA[0]->@*, "wilma", "betty";
show @AoA; выведет:
0 0..3 "fred" "barney" "wilma" "betty"
1 0..2 "george" "jane" "elroy"
2 0..2 "homer" "marge" "bart" В то время как если вы закомментируете две строки, которые я посоветовал сделать, то он покажет это так:
0 ARRAY(0x8031d0)
0 "fred"
1 "barney"
2 "wilma"
3 "betty"
1 ARRAY(0x803d40)
0 "george"
1 "jane"
2 "elroy"
2 ARRAY(0x803e10)
0 "homer"
1 "marge"
2 "bart" Срезы
Если вы хотите получить срез (часть строки) в многомерном массиве, вам придётся выполнить сложные индексирования. Это потому, что, хотя у нас есть хорошее синоним для отдельных элементов через стрелку указателя для дериференции, нет такой же удобства для срезов.
Вот как выполнить одну операцию с помощью цикла. Мы предполагаем переменную @AoA, как и прежде.
@part = ();
$x = 4;
for ($y = 7; $y < 13; $y++) {
push @part, $AoA[$x][$y];
} Этот же цикл можно заменить операцией среза:
@part = $AoA[4]->@[ 7..12 ]; Теперь, что если бы вам понадобился двумерный срез, например, $x изменяется от 4 до 8, а $y от 7 до 12? Хмм... вот простой способ:
@newAoA = ();
for ($startx = $x = 4; $x <= 8; $x++) {
for ($starty = $y = 7; $y <= 12; $y++) {
$newAoA[$x - $startx][$y - $starty] = $AoA[$x][$y];
}
} Мы можем сократить некоторые циклы через срезы
for ($x = 4; $x <= 8; $x++) {
push @newAoA, [ $AoA[$x]->@[ 7..12 ] ];
} Если бы вы были любителем трансформаций Шварца, вы бы, вероятно, выбрали map для этого
@newAoA = map { [ $AoA[$_]->@[ 7..12 ] ] } 4 .. 8; Хотя, если бы ваш менеджер обвинил вас в поиске трудовой безопасности (или быстрого отсутствия таковой) через нечитаемый код, было бы трудно спорить. :-) Если бы я был на вашем месте, я бы поместил это в функцию:
@newAoA = splice_2D( \@AoA, 4 => 8, 7 => 12 );
sub splice_2D {
my $lrr = shift; # ref to array of array refs!
my ($x_lo, $x_hi,
$y_lo, $y_hi) = @_;
return map {
[ $lrr->[$_]->@[ $y_lo .. $y_hi ] ]
} $x_lo .. $x_hi;
} СМОТРИТЕ ТАКЖЕ
АВТОР
Tom Christiansen <tchrist@perl.com>
Последнее обновление: Вт 26 апр 18:30:55 MDT 2011
© 1993–2021 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.34.0/perllol