Приглашаем посетить
Сумароков (sumarokov.lit-info.ru)

Массивы

Назад
Глава 4 Массивы
Вперед

Массивы

Введение

Если попросить вас перечислить содержимое своих карманов, назвать имена трех последних президентов или объяснить, как пройти к нужному месту, в любом случае получится синеок: вы называете объекты один за другим в определенном порядке. Списки являются частью нашего мировоззрения. Мощные примитивы Perl для работы со списками и массивами помогают преобразовать мировоззрение в программный код. Термины список (list) и массив (array) трактуются в этой главе в соответствии с канонами Perl. Например, ("Reagan", "Bush", "Clinton") - это список трех последних американских президентов. Чтобы сохранить его в переменной, воспользуйтесь л/ассмоол<: presidents = ("reagan", "bush", "clinton"). Каждый из этих терминов относится к упорядоченной совокупности скалярных величин; отличие состоит в том, что массив представляет собой именованную переменную, размер которой можно непосредственно изменить, а список является скорее отвлеченным понятием. Можно рассматривать массив как переменную, а список - как содержащиеся в пей значения. Отличие может показаться надуманным, но операции, изменяющие размер этой совокупности (например, push или pop), работают с массивом, а не списком. Нечто похожее происходит с $а и 4: в программе можно написать $а++, no ne 4++. Аналогично, рор(@а) - допустимо, а рор(1, 2, 3) - пет. Главное - помнить, что списки и массивы в perl представляют собой упорядоченные совокупности скалярных величин. Операторы и функции, работающие со списками и массивами, обеспечивают более быстрый или удобный доступ к элементам по сравнению с ручным извлечением. Поскольку размер массива изменяется не так уж часто, термины "массив" и "список" обычно можно считать синонимами. Вложенные списки не создаются простым вложением скобок. В perl следующие строки эквивалентны:
@nested = ("this", "that", "the", "order");
@nested = ("this", "that", ("the", "order"));
Почему Perl не поддерживает вложенные списки напрямую? Отчасти по историческим причинам, по также и потому, что это позволяет многим операциям (типа print или sort) работать со списками произвольной /тины и произвольного содержания. Что делать, если требуется более сложная структура данных - например, массив массивов или массив хэшей? Вспомните, что скалярные переменные могут хранить не только числа или строки, но и ссылки. Сложные (многоуровневые) структуры данных в Perl всегда образуются с помощью ссылок. Следовательно, "двумерные массивы" или "массивы массивов" в действительности реализуются как массив ссылок на массивы - по аналогии с двумерными массивами С, которые могут представлять собой массивы указателей на массивы. Для большинства рецептов этой главы содержимое массивов несущественно Например, проблема слияния двух массивов решается одинаково для массивол строк, чисел или ссылок. Решения некоторых проблем, связанных с содержимым массивов, приведены в главе 11 "Ссылки и записи". Рецепты этой главы ограничиваются обычными массивами. Давайте введем еще несколько терминов. Скалярные величины, входящие в массив или список, называются элементами. Для обращения к элементу используется его позиция, или индекс. Индексация в Perl начинается с 0, поэтому в следующем списке: @tune = ("the", "star-spangled", "banner" ); элемент "The" находится в первой позиции, но для обращения к нему используется индекс 0: $tune[0]. Это объясняется как извращенностью компьютерной логики, где нумерация обычно начинается с 0, так и извращенностью разработчиков языка, которые выбрали 0 как смещение внутри массива, а не порядковый номер элемента.

4.1. Определение списка в программе

Проблема

Требуется включить в программу список - например, при инициализации массива.

Решение

Перечислите элементы, разделяя их запятыми:
@а = ("quick", "brown", "fox");
При большом количестве однословных элементов воспользуйтесь оператором qw():
@a = qw(why are you teasing me?);


При большом количестве многословных элементов создайте встроенный документ и последовательно извлекайте из него строки:
@lines = (""end_of_here_doc" =~ m/"\s*(.+)/gm);
The boy stood on the burning deck, It was as hot as glass.
END_OF_HERE_DOC

Комментарий

Наиболее распространен первый способ - в основном из-за того, что в виде литералов в программе инициализируются лишь небольшие массивы. Инициализация большого массива загромождает программу и усложняет ее чтение, поэтому такие массивы либо инициализируются в отдельном библиотечном файле (см. главу 12 "Пакеты, библиотеки и модули"), либо просто читаются из файла данных:
@bigarray = ();
open(DATA, "< mydatafile") or die "Couldn't read from datafile: $!\n";
while () {
chomp;
push(@bigarray, $_);
}
Во втором способе используется оператор qw. Наряду с q(), qq() и qx() он предназначен для определения строковых величин в программе. Оператор q() интерпретируется но правилам для апострофов, поэтому следующие две строки эквивалентны:
$banner = 'the mines of moria':
$banner = q(the mines of moria): Оператор qq() интерпретируется по правилам для кавычек:
$name = "gandalf";
$banne'r = "speak, $name, and enter!";
$banner = qq(speak, $name, and welcome!); А оператор qx() интерпретируется почти так же, как и обратные апострофы, - то есть выполняет команду с интерполяцией переменных и служебными символами \ через командный интерпретатор. В обратных апострофах интерполяцию отменить нельзя, а в qx - можно. Чтобы отказаться от расширения переменных perl, используйте в qx ограничитель :
$his_host = 'www.perl.com';
$host_info = 'nslookup $his_host'; # Переменная perl расширяется
$perl_info = qx(ps $$); # Значение
$$o от Perl $shell_info = qx'ps $$'; # Значение $$ от интерпретатора Если операторы q(), qq() и qx() определяют одиночные строки, то qw() определяет список однословных строк. Строка-аргумент делится по пробелам без интерполяции переменных. Следующие строки эквивалентны:
@banner = ('costs', 'only', '$4.95');
banner = qw(costs only $4.95);
banner = split(' ', 'costs only $4.95');

Во всех операторах определения строк, как и при поиске регулярных выражений, разрешается выбор символа-ограничителя, включая парные скобки. Допустимы все четыре тина скобок (угловые, квадратные, фигурные и круглые). Следовательно, вы можете без опасении использовать любые скобки при условии, что для открывающей скобки найдется закрывающая:
@brax = qw! ()<>{}[]!;
rings = qw(nenya narya vilya);
$tags = qw
  • ;
    $sample = qw(the vertical bar (|) looks and behaves like a pipe.);


    Если ограничитель встречается в строке, а вы не хотите заменить его другим, используйте префикс \:
    @banner = qw|the vertical bar (||) looks and behaves like a pipe.l;
    Оператор qw() подходит лишь для списков, в которых каждый элемент является отдельным словом, ограниченным пробелами. Будьте осторожны, а то у Колумба вместо трех кораблей появится четыре:
    $ships = qw(nica pinta santa Магна); # НЕВЕРНО!!!
    $ships = ('mica', 'pinta', 'santa Магна'); # Правильно


    Смотри также: Раздел "List Value Constructors" perldata(1) раздел "Quote and Quote-Like Operators" perlop(1); оператор s/// описан в реrlоg(1).

    4.2. Вывод списков с запятыми

    Проблема

    Требуется вывести список с неизвестным количеством элементов. Элементы разделяются занятыми, а перед последним элементом выводится слово and.

    Решение

    Следующая функция возвращает строку, отформатированную требуемым образом:

    sub commify_series {
    (@_ == 0) ? " :
    (@_ == 1) ? $_[q] :
    (@_ == 2) ? join(" and ", @>_) :
    join(", ", @_[0 .. ($й_-1], "and $_[-1]");
    }

    Комментарий

    При выводе содержимое массива порой выглядит довольно странно:

    @аrrау = ("red", "yellow", "green");
    print "I have ", @array, " marbles.\n";
    print "I have @array marbles\n";

    I have redyellowgreen marbles. I have red yellow green marbles.
    На самом деле вам нужна строка "I have red, yellow, and green marbles". Приведенная выше функция генерирует строку именно в таком формате. Между двумя последними элементами списка вставляется "and". Если в списке больше двух элементов, все они разделяются запятыми. Пример 4.1 демонстрирует применение этой функции с одним дополнением: если хотя бы один элемент списка содержит запятую, в качестве разделителя используется точка с занятой. Пример 4.1. commify_series

    #!/usr/bin/perl -w
    # commify_series - демонстрирует вставку запятых при выводе списка
    @lists = (
    [ 'just one thing' ],
    [ qw(Mutt Jeff) ],
    [ qw(Peter Paul Mary) ],
    [ 'To our parents', 'Mother Theresa'. 'God' ],
    [ 'pastrami', 'ham and cheese', 'peanut butter and jelly', 'tuna' ],
    [ 'recycle tired, old phrases', 'ponder big, happy thoughts' ],
    [ 'recycle tired, old phrases',
    'ponder big, happy thoughts',
    'sleep and dream peacefully' ],
    );
    foreach $aref ((Slists) {
    print "The list is: " , commify_series(@$aref) . ".\n";
    sub commify_series {
    my $sepchar = grep(/,/ => @_) ? ";" : ",";
    (@>_ == 0) ? "
    (@_ == 1) ^ $_[0]
    (@_ == 2) r). join(" and ", @_)
    join("$sepchar ", @_[0 .. ($#_-1)], "and $_[-1]"):
    }i>

    Результаты выглядят так:

    The list is just one thing.
    The list is Mutt and Jeff.
    The list is Peter, Paul, and Mary.
    The list is To our parents, Mother Theresa, and God.
    The list is pastrami, ham and cheese, peanut butter and jelly, and tuna,
    The list is recycle tired, old phrases and ponder big, happy thoughts.
    The list is: recycle tired, old phrases; ponder
    big, happy thoughts; and sleep and dream peacefully.


    Как видите, мы отвергаем порочную практику исключения последней занятой из списка, что нередко приводит к появлению двусмысленностей.

    Смотри также: Описание функции grep в perlfunc(l); описание тернарного оператора выбора в perlop(1) Синтаксис вложенных списков рассматривается в рецепте 11.1.

    4.3. Изменение размера массива

    Проблема

    Требуется увеличить или уменьшить размер массива. Допустим, у вас имеется массив работников, отсортированный но размерам оклада, и вы хотите ограничить его пятью самыми высокооплачиваемыми работниками. Другой пример - если окончательный размер массива точно известен, намного эффективнее выделить всю намять сразу вместо того, чтобы увеличивать массив постепенно, добавляя элементы в конец.

    Решение


    Присвойте значение $#ARRAY:
    ti Увеличить или уменьшить (SARRAY $#ARRAY = $new_last_element_index_number
    Присваивание элементу, находящемуся за концом массива, автоматически увеличивает массив:
    $ARRAY[$NEW_LAST_ELEMENT_INDEX_NUMBER] = $value:

    Комментарий


    $"ARRAY - последний допустимый индекс массива OARRAY. Если ему присваивается значение меньше текущего, массив уменьшается. Отсеченные элементы безвозвратно теряются. Если присвоенное значение больше текущего, массив увеличивается. Новые элементы получают неопределенное значение. Однако $#ARRAY не следует путать с @ARRAY. $#ARRAY представляет собой последний допустимый индекс массива, a @ARRAY (в скалярном контексте, то есть в числовой интерпретации) - количество элементов. $#ARRAY на единицу меньше ARRAY, поскольку нумерация индексов начинается с 0. В следующем фрагменте использованы оба варианта:

    sub what_about_that_array {
    print "The array now has ", scalar((a'people), " elements. \n";
    print "The index of the last element is $#people.\n";
    print "Element #3 is '$people[3]',\n";
    }
    @people = qw(crosby stills nasn young);
    what_about_that_array();

    Результат:


    The array now has 4 elements.
    The index of the last element is 3.
    Element #3 is 'Young'.

    А другой фрагмент:
    $#people--:
    what_about_that_array();
    выводит следующий результат:
    The array now has 3 elements.
    The index of the last element is 2.
    Element #3 is o '.'


    Элемент с индексом 3 пропал при уменьшении массива. Если бы программа запускалась с ключом -w, Per! также выдал бы предупреждение об использовании неинициализированной величины, поскольку значение $реорlе[3] не определено. В следующем примере:
    $#реорlе = 10000;
    what_about_that_array();

    результат выглядит так:
    The array now has 10001 elements.
    The index of the last element is 10000.
    Element #3 is ' '.
    Элемент "Young" безвозвратно утерян. Вместо присваивания $#people можно было сказать:
    $people[10000]=undef;
    Массивы Perl не являются разреженными. Другими словами, если у вас имеется 10000-й элемент, то должны присутствовать и остальные 9999 элементов. Они могут быть неопределенными, но все равно будут занимать память. Из-за этого $array[time] или любая другая конструкция, где в качестве индекса используется очень большое целое число, является неудачным решением. Лучше воспользуйтесь хэшем. При вызове print нам пришлось написать scalar @array, поскольку Perl интерпретирует большинство аргументов в списковом контексте, а требовалось значение array в скалярном контексте.

    Смотри также: Описание $#ARRAY в perldata(1).


    Назад
    Вперед