Spec-Zone.ru › Perl 5.38

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

Если вы любили бы Schwartzian Transforms, вы, вероятно, выбрали бы 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–2023 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.38.0/perllol

Spec-Zone.ru

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