Spec-Zone.ru › Perl 5.36

perllol

СОДЕРЖАНИЕ

  • ИМЯ
  • ОПИСАНИЕ
    • Объявление и доступ к массивам массивов
    • Создание собственного массива
    • Доступ и вывод
    • Срезы
  • СМОТРИТЕ ТАКЖЕ
  • АВТОР

ИМЯ

perllol - Обработка массивов массивов в Perl

ОПИСАНИЕ

Объявление и доступ к массивам массивов

Самой простой двухуровневой структурой данных в Perl является массив массивов, иногда неформально называемый списком списков. Ее довольно легко понять, и почти всё, что применимо здесь, также будет применимо и к более сложным структурам данных.

Массив массивов — это обычный массив @AoA, к которому можно обратиться с помощью двух индексов, как в $AoA[3][2]. Вот объявление этого массива:

  use v5.10;  # 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;
}

СМОТРИТЕ ТАКЖЕ

perldata, perlref, perldsc

АВТОР

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.36.0/perllol

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API