Spec-Zone.ru › Perl 5.30

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

или немного разнеся его:

@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–2020 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.28.3/perllol

Spec-Zone.ru

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