Spec-Zone.ru › Perl 5.34

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;
}

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

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

Spec-Zone.ru

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