Привет! Меня зовут Габор Сабо. Это третья часть презентаций о Perl 6. На этот раз я расскажу про массивы и немного про диапазоны. Остальные презентации можно найти по ссылкам на странице szabgab.com/perl6.html.
Как вы помните из предыдущей презентации, мы создали массив из списка значений и прошлись по всем элементам в цикле for с переменной $n.
На этот раз код работает так же:
use v6;
my @people = <Foo 123 Bar 456 Moo 789 Foo 512>;
for @people -> $n {
say $n;
}
Так что я могу выполнить файл, и массив распечатается:
eval slurp '01.p6'
Foo
123
Bar
456
Moo
789
Foo
512
Хорошо. Но как вы видите, это на самом-то деле похоже на пары: чье-то имя (Foo), и затем какое-то число (123), вероятно, телефонный номер этого человека.
Поэтому хочется пройтись в цикле по ним и брать сразу по два элемента. Пользователи Perl 5, возможно, предложат поместить все в хеш и затем пройтись по ключам и значениям. С этим возникнут проблемы.
Первая — мы еще не изучили хеши (в других языках они называются словарями). Другая — как только мы поместили значения в хеш, мы тут же потеряли их порядок, так что не сможем узнать, что Foo находится перед Bar. Во многих случаях это не имеет значения, но не всегда. Худшая же проблема в том, что Foo у нас появляется дважды. И если поместить все данные в хеш, то одно из них — скорее всего, первое — потеряется. Так что это неподходящее решение.
Нужен какой-то способ пройтись по всем парам, но без хешей.
Стандартный способ, которым обычно пользуются (он показан во втором примере), — перебор с помощью индекса $i и отбор пар расчетом нужных индексов элементов.
use v6;
my @people = <Foo 123 Bar 456 Moo 789 Foo 512>;
for 0 .. @people.elems/2 -> $i {
say "@people[$i*2] - @people[$i*2+1]";
}
Мы можем сделать это: взять массив, вызвать .elems — метод массива, возвращающий число элементов, разделить это надвое и получить диапазон, по которому мы должны пройтись в цикле. Посмотрим, как это работает.
eval slurp '02.p6'
Foo - 123
Bar - 456
Moo - 789
Foo - 512
Any() - Any()
Видно, здесь есть то, что мы ожидали, но кроме этого еще два элемента, которых не было в наших данных. Мы неверно вычислили верхнюю границу диапазона. Один из вариантов решения — дописать -1:
for 0 .. @people.elems/2 - 1 -> $i {
Это решит проблему:
Foo - 123
Bar - 456
Moo - 789
Foo - 512
Другой, лучший, способ — создать диапазон, исключив из него последнее значение:
for 0 ..^ @people.elems/2 -> $i {
Проблема решается так же хорошо:
Foo - 123
Bar - 456
Moo - 789
Foo - 512
Это неплохое решение, но в Perl 6 возможно поступить намного лучше.
В Perl 6 мы можем попросить цикл for выбирать по два элемента, указав две переменные после стрелочки:
use v6;
my @people = <Foo 123 Bar 456 Moo 789 Foo 512>;
for @people -> $name, $phone {
say "$name - $phone";
}
eval slurp '03.p6'
Опять тот же результат, уже начинающий надоедать:
Foo - 123
Bar - 456
Moo - 789
Foo - 512
Вот так мы можем выбирать по два элемента. Точно так же можно выбирать три или больше — любое число элементов, которое требуется.
А что произойдет, если у нас два массива и мы хотим поэлементно объединить их?
Можно вновь применить индексы, а можно и оператор Perl 6 Z (от zip):
use v6;
my @names = <Foo Bar Moo Foo>;
my @phones = <123 456 789 512>;
for @names Z @phones -> $name, $phone {
say "$name - $phone";
}
Оператор Z берет два массива, и в цикле мы получаем пары значений — одно из первого массива, другое из второго.
По-прежнему получаем тот же результат:
eval slurp '04.p6'
Foo - 123
Bar - 456
Moo - 789
Foo - 512
Можно воспользоваться и другим оператором — X, который объединяет два массива другим способом.
use v6;
my @names = <Foo Bar Moo Foo>;
my @phones = <123 456 789 512>;
for @names X @phones -> $name, $phone {
say "$name - $phone";
}
eval slurp '04.p6'
Foo - 123
Foo - 456
Foo - 789
Foo - 512
Bar - 123
Bar - 456
Bar - 789
Bar - 512
Moo - 123
Moo - 456
Moo - 789
Moo - 512
Foo - 123
Foo - 456
Foo - 789
Foo - 512
Он объединяет каждый элемент первого массива с каждым элементом второго. Мы видим Foo со всеми числами, Bar со всеми числами, Moo со всеми и опять Foo со всеми числами (потому что Foo было дважды).
Следующий пример показывает, как можно итерировать по диапазону.
use v6;
for 1 .. 10 -> $n {
$n;
}
Простой диапазон, от одного до десяти. Запускаем.
eval slurp '05.p6'
Напечатаются числа от одного до десяти:
1
2
3
4
5
6
7
8
9
10
Как мы уже видели, можно использовать ^, чтобы сказать, что мы не хотим включать последний элемент диапазона:
for 1 ..^ 10 -> $n {
$n;
}
1
2
3
4
5
6
7
8
9
Аналогично можно поместить ^ и в начале, чтобы исключить нижнюю границу диапазона:
for 1 ^..^ 10 -> $n {
$n;
}
2
3
4
5
6
7
8
9
Что делать, если нужен цикл сначала для единицы, а потом от трех до десяти? На самом деле, это не очень сложно, я могу написать единицу, и затем диапазон от трех до десяти:
for 1,3 .. 10 -> $n {
$n;
}
Выполнив это, получим 1 и числа от 3 до 10:
1
3
4
5
6
7
8
9
10
Это не всегда то, что нужно. Чаще потребуются, например, 1, 3, 5, то есть, каждый второй элемент. В Perl 6 это просто, потому что нужно поставить еще одну точку, превращающую оператор диапазона (..) в оператор последовательности (...):
for 1,3 ... 10 -> $n {
$n;
}
В этом случае Perl 6 попытается посмотреть на первые два элемента и понять, как нужно продолжать последовательность до правой границы:
1
3
5
7
9
Очевидно, 10 не попало в список; цикл закончился, когда была пройдена эта граница.
Хорошо, но здесь есть некоторые ограничения. Что будет, если я не знаю границы диапазона, а захочу идти бесконечно?
for 1 .. Inf -> $n {
say $n;
}
Perl 6 сможет и это, но очевидно не остановится. Чтобы сделать пример полезным, потребуется некоторое условие выхода из цикла. Я напечатаю:
last if $n > 10;
for 1 .. Inf -> $n {
say $n;
last if $n > 10;
}
1
2
3
4
5
6
7
8
9
10
11
Теперь я вновь могу запустить этот код, и цикл пройдет от 1 до 11, пока не удовлетворится условие. Очевидно, условие может быть и другим, не связанным с $n, лишь бы оно останавливало цикл.
То же самое, что мы сделали с Inf, можно сделать и со звездочкой. * — это оператор «whatever» в Perl 6.
for 1 .. * -> $n {
say $n;
last if $n > 10;
}
Это означает, что нам не известно, где нужно остановиться, и работает аналогично:
1
2
3
4
5
6
7
8
9
10
11
Опять же, можно воспользоваться той же идеей и создать диапазон от трех до «whatever».
for 1, 3 .. * -> $n {
say $n;
last if $n > 10;
}
Или можно создать последовательность 1, 3, 5 до какого-либо предела.
for 1, 3 ... * -> $n {
say $n;
last if $n > 10;
}
1
3
5
7
9
11
На сегодня это все. Спасибо.