第六章. Positionals

Positionals

声明

本章翻译仅用于 Raku 学习和研究, 请支持电子版或纸质版

第六章. Positionals

Your programming career is likely to be, at its heart, about moving and transforming ordered lists of some kind. Those might be to-do lists, shopping lists, lists of web pages, or just about anything else.

The broad term for such as list is Positional. Not everything in this chapter is strictly one of those; it’s okay to pretend that they are, though. The language easily interchanges among many of the types you’ll see in this chapter, and it’s sometimes important to keep them straight. Mind their differences and their different uses to get exactly the behavior you want.

This is the first chapter where you’ll experience the laziness of the language. Instead of computing things immediately as you specify them in your code, your program will remember it needs to do something. It then only does it if you later use it. This feature allows you to have infinite lists and sequences without actually creating them.

从本质上讲,你的编程职业可能是移动和转换某种有序列表。这些可能是待办事项列表,购物清单,网页列表或其他任何内容。

列表的广义术语是 Positional。并非本章中的所有内容都是其中之一;但是可以假装他们是。Raku 很容易在本章中看到的许多类型之间进行交换,有时候保持它们是正确的。注意他们的差异和他们的不同用途,以获得你想要的行为。

这是你将体验这门语言懒惰的第一章。在你的代码中指定它们时,你的程序将记住它需要做某事,而不是立即计算事物。它只会在你以后使用它时才会这样做。此功能允许你拥有无限的列表和序列,而无需实际创建它们。

Constructing a List

A List is an immutable series of zero or more items. The simplest List is the empty list. You can construct one with no arguments. The List as a whole is one thingy and you can store it in a scalar:

List 是零个或多个项目的不可变系列。最简单的 List 是空列表。你可以构造一个没有参数的列表。你可以将列表作为一个整体存储在标量中:

my $empty-list = List.new;
put 'Elements: ', $empty-list.elems;  # Elements: 0

The .elems method returns the number of elements, which is 0 for the empty List. This might seem like a trivial result, but imagine those cases where you want to return no results: an empty List can be just as meaningful as a nonempty one.

Instead of the call to .new, you can use empty parentheses to do the same thing. Normally parentheses simply group items, but this is special syntax:

.elems 方法返回元素的个数,对于空列表,它返回 0。可能这看起来像一个微不足道的结果,但想象一下你想要返回没有结果的那些情况:一个空的列表可以和非空列表一样有意义。

你可以使用空括号来执行相同的操作,而不是调用 .new。通常括号只是对项目进行分组,但这是特殊的语法:

my $empty-list = (); # Also the empty List

There’s also a special object for that. Empty clearly shows your intent:

还有一个特殊的对象。Empty 显示你的意图:

my $empty-list = Empty;

You can specify elements in .new by separating the elements with commas. Both the colon and parentheses forms work:

你可以通过用逗号分隔元素来指定 .new 中的元素。冒号和括号形式都有用:

my $butterfly-genus = List.new:
    'Hamadryas', 'Sostrata', 'Junonia';

my $butterfly-genus = List.new(
    'Hamadryas', 'Sostrata', 'Junonia'
    );
警告

You cannot make an empty List with $(): that’s just Nil.

The $(...) with a list inside also constructs a List. The $ indicates that it is an item. This one happens to be a List object. You can check the number of elements in it with .elems:

你不能用 $() 创建一个空列表:那只是 Nil

带有列表的 $(...) 也构造了一个列表。 $ 表示它是一个项。这恰好是一个 List 对象。你可以使用 .elems 检查其中的元素数量:

my $butterfly-genus = $('Hamadryas', 'Sostrata', 'Junonia');
put $butterfly-genus.elems;     # 3

Or you can leave off the $ in front of the parentheses. You still need the grouping parentheses because item assignment is higher precedence than the comma:

或者你可以去掉括号前留下的 $。你仍然需要分组括号,因为项赋值的优先级高于逗号:

my $butterfly-genus = ('Hamadryas', 'Sostrata', 'Junonia');
put $butterfly-genus.elems;     # 3

A container can be an element in a List. When you change the value in the container it looks like the Listchanges, but it doesn’t actually change because the container is the List item and that container itself was still the List item:

列表中的元素可以是容器。当你更改容器中的值时,看起来像是列表更改了,但它实际上没有更改,因为容器是列表项,并且该容器本身仍然是列表项:

my $name = 'Hamadryas perlicus';
my $butterflies = ( $name, 'Sostrata', 'Junonia' );
put $butterflies; # (Hamadryas perlicus Sostrata Junonia)

$name = 'Hamadryas';
put $butterflies; # (Hamadryas Sostrata Junonia)

You don’t need the named variable, though. You can use an anonymous scalar container as a placeholder that you’ll fill in later. Since it has no value (or even a type), it’s an Any type object:

但是,你不需要命名变量。你可以使用匿名标量容器作为占位符,稍后你将填写。由于它没有值(甚至是类型),因此它是一个Any类型的对象:

my $butterflies = ( $, 'Sostrata', 'Junonia' );
put $butterflies; # ((Any) Sostrata Junonia)

All of this quoting and comma separating is a bit tedious, but there’s a shortcut. You can quote a list with qw. It creates items by breaking the text apart by whitespace. This makes a three-element List:

所有这些引用和逗号分离有点单调乏味,但有一条捷径。你可以用 qw 引起列表。它通过用空格分隔文本来创建项目。这使得三元素列表:

my $butterfly-genus = qw<Hamadryas Sostrata Junonia>;
put 'Elements: ', $butterfly-genus.elems;  # Elements: 3

qw is another form of the generalized quoting you saw in Chapter 4. It uses the :w adverb and returns a List. You won’t see this form much, but it’s what you’re doing here:

qw是你在第4章中看到的另一种形式的广义引用。它使用 :w 副词并返回一个列表。你不会看到这个形式太多,但这是你在这里做的:

my $butterfly-genus = Q :w/Hamadryas Sostrata Junonia/

That’s still too much work. You can enclose the Strs in angle brackets and leave out the item quoting and the separating commas. This acts the same as qw:

那仍然是太多的工作。你可以将字符串括在尖括号中,并省略每项的引号和逗号分隔符。这与 qw 相同:

my $butterfly-genus = <Hamadryas Sostrata Junonia>;

The <> only works if you don’t have whitespace inside your Strs. This gives you four elements because the space between 'Hamadryas and perlicus' separates them:

只有在字符串中没有空格时,<> 才有效。这给你四个元素,因为 'Hamadryasperlicus' 之间的空格将它们分开:

my $butterflies = < 'Hamadryas perlicus' Sostrata Junonia >;
put 'Elements: ', $butterflies.elems;  # Elements: 4

Raku has thought of that too and provides a List quoting mechanism with quote protection. The <<>> keeps the thingy in quotes as one item even though it has whitespace in it:

Raku 也考虑过这一点并提供带引号保护的列表引用机制。 <<>> 将引号中的东西保持为一个项,即使它里面有空格:

my $butterflies = << 'Hamadryas perlicus' Sostrata Junonia >>;
put 'Elements: ', $butterflies.elems;  # Elements: 3

With the <<>> you can interpolate a variable. After that the value of the variable is an item but isn’t linked to the original variable:

使用 <<>> 可以插入变量。之后,变量的值是一个项,但没有链接到原始变量:

my $name = 'Hamadryas perlicus';
my $butterflies = << $name Sostrata Junonia >>;
say $butterflies;

Instead of <<>>, you can use the fancier quoting with the single-character «» version (double angle quotes). These are sometimes called French quotes:

你可以使用单字符 «» 版本(双角引号)的更好看的引号代替 <<>>。这些有时被称为法语引号:

my $butterflies = « $name Sostrata Junonia »;

Both of these quote-protecting forms are the same as the :ww adverb for Q:

这两个引号保护形式都与 Q:ww 副词相同:

my $butterflies = Q :ww/ 'Hamadryas perlicus' Sostrata Junonia /;
put 'Elements: ', $butterflies.elems;  # Elements: 3

Sometimes you want a List where all the elements are the same. The xx list replication operator does that for you:

有时你需要一个列表,其中所有元素都相同。 xx 列表复制操作符为你执行此操作:

my $counts = 0 xx 5; # ( 0, 0, 0, 0, 0 )

A List interpolates into a Str like any other scalar variable:

列表像任何其他标量变量一样插入到字符串中:

my $butterflies = << 'Hamadryas perlicus' Sostrata Junonia >>;
put "Butterflies are: $butterflies";

The List stringifies by putting spaces between its elements. You can’t tell where one element stops and the next starts:

列表通过在其元素之间放置空格来进行字符串化。你无法分辨元素停止的位置和下一个元素的开始:

Butterflies are: Hamadryas perlicus Sostrata Junonia

The .join method allows you to choose what goes between the elements:

.join 方法允许你选择元素之间的内容:

my $butterflies = << 'Hamadryas perlicus' Sostrata Junonia >>;
put "Butterflies are: ", $butterflies.join: ', ';

Now the output has commas between the elements:

现在输出元素之间有逗号:

Butterflies are: Hamadryas perlicus, Sostrata, Junonia

You can combine both of these, which makes it easier to also surround the List items with characters to set them off from the rest of the Str:

你可以将这两者结合起来,这样可以更容易地使用字符围绕列表项,以便将它们与字符串的其余部分相关联:

my $butterflies = << 'Hamadryas perlicus' Sostrata Junonia >>;
put "Butterflies are: /{$butterflies.join: ', '}/";

If you needed to parse this Str in some other program you’d know to grab the elements between the slashes:

如果你需要在其他程序中解析这个字符串你知道要抓取斜杠之间的元素:

Butterflies are: /Hamadryas perlicus, Sostrata, Junonia/

EXERCISE 6.1Write a program that takes two arguments. The first is a Str and the second is the number of times to repeat it. Use xx and .join to output the text that number of times on separate lines.

练习6.1 编写一个带有两个参数的程序。第一个是字符串,第二个是重复它的次数。使用 xx.join 在单独的行上输出该次数的文本。

Iterating All the Elements

Iteration is the repetition of a set of operations for each element of a collection. The for control structure iterates through each element of a List and runs its Block once for each element as the topic. You can use the .List method to treat the one thing in your scalar variable (your List) as its individual elements:

迭代是对集合的每个元素重复一组操作。 for 控制结构遍历列表的每个元素,并为每个元素作为主题运行一次Block。你可以使用 .List 方法将标量变量(列表)中的一个元素视为其各个元素:

for $butterfly-genus.List {
    put "Found genus $_";
    }

You get one line per element:

每个元素得到一行:

Found genus Hamadryas
Found genus Sostrata
Found genus Junonia
NOTE

Although I tend to call these things Positionals there is actually a separate role for Iterables that does the magic to make for work. The Positionals I present in this book also do the Iterable role, so I don’t distinguish them even though I’m strictly wrong.

Calling .List is a bit annoying though, so there’s a shortcut for it. Prefix the variable with @ to do the same thing:

虽然我倾向于将这些东西称为“Positional”,但Iterable实际上有一个单独的角色可以为工作带来魔力。我在本书中提出的定位也做了Iterable角色,所以即使我严重错误,我也不区分它们。

调用 .List 虽然有点烦人,所以有一个快捷方式。使用 @ 前缀变量来执行相同的操作:

for @$butterfly-genus {
    put "Found genus $_";
}

Skip the $ sigil altogether and use the @ sigil to store a List in a variable:

完全跳过 $ sigil并使用 @ sigil 将 List 存储在变量中:

my @butterfly-genus = ('Hamadryas', 'Sostrata', 'Junonia');

for @butterfly-genus {
    put "Found genus $_";
}

This is actually different from the item assignment you’ve seen before. It’s a list assignment where the = operator has a lower precedence:

这实际上与你之前看到的项赋值不同。这是一个列表赋值,其中 = 运算符的优先级较低:

my @butterfly-genus = 'Hamadryas', 'Sostrata', 'Junonia';

Why would you choose $ or @? Assigning to $butterfly-genus gives you a List and all the restrictions of that type. You can’t add or remove elements. You can change the values inside a container but not the container itself. What do you get when you assign this way?

你为什么选择 $@? 赋值给 $butterfly-genus 会给你一个列表以及该类型的所有限制。你无法添加或删除元素。你可以更改容器内的值,但不能更改容器本身。当你指定这种方式时你会得到什么?

my @butterfly-genus = 'Hamadryas', 'Sostrata', 'Junonia';
put @butterfly-genus.^name;  # Array

You get an Array, which you’ll see more of later in this chapter. An Array relaxes all those restrictions. It allows you to add and remove elements and change values. Choose the type that does what you want. If you want the data to stay the same, choose the one that can’t change.

This looks a little better with interpolation, which means you’re less likely to forget explicit whitespace around words:

你得到一个数组,你将在本章后面看到更多。数组 放宽了所有这些限制。它允许你添加和删除元素并更改值。选择满足你需求的类型。如果你希望数据保持不变,请选择无法更改的数据。

插值看起来好一点,这意味着你不太可能忘记单词周围的显式空格:

for @butterfly-genus {
    put "$_ has {.chars} characters";
}

You’ll often want to give your variable a meaningful name. You can use a pointy Block to name your parameter instead of using the topic variable, $_:

你经常希望为变量赋予有意义的名称。你可以使用尖头来命名参数,而不是使用主题变量 $_

for @butterfly-genus -> $genus {
    put "$genus has {$genus.chars} characters";
}

That looks a lot like the definition of a subroutine with -> { ... }, because that’s what it is. That parameter is lexical to that Block just as it would be in a subroutine.

If your Block has more than one parameter, then the for takes as many elements as it needs to fill in all of them. This goes through the List by twos:

这看起来很像是带有 -> { ... } 的子程序的定义,因为它就是它的本质。该参数对于该是词法的,就像它在子例程中一样。

如果你的Block有多个参数,那么 for 需要尽可能多的元素来填充所有这些参数。这每俩个元素遍历列表

my @list = <1 2 3 4 5 6 7 8>;

for @list -> $a, $b {
    put "Got $a and $b";
}

Each iteration of the Block takes two elements:

Block 的每次迭代都接收两个元素:

Got 1 and 2
Got 3 and 4
Got 5 and 6
Got 7 and 8

Ensure that you have enough elements to fill all of the parameters or you’ll get an error. Try that bit of code with one less element to see what happens!

You can use placeholder variables in your Block, but in that case you don’t want to use a pointy Block, which would already create a signature for you. Using placeholder variables also works:

确保你有足够的元素来填充所有参数,否则你将收到错误。用少一个元素尝试那些代码来看看会发生什么!

你可以在块中使用占位符变量,但在这种情况下,你不希望使用尖头块,这会为你创建签名。使用占位符变量也有效:

my @list = <1 2 3 4 5 6 7 8>;

for @list {
    put "Got $^a and $^b";
}

READING LINES OF INPUT

The lines routine reads lines of input from the files you specify on the command line, or from standard input if you don’t specify any. You’ll read more about this in Chapter 8 but it’s immediately useful with for:

lines 例程从你在命令行中指定的文件中读取输入行,如果未指定任何文件,则从标准输入读取。你将在第8章中详细了解这一点,但它对以下内容非常有用:

for lines() {
    put "Got line $_";
}

Your programs reads and reoutputs all of the lines from all of the files. The line ending was autochomped; it was automatically stripped from the value because that’s probably what you wanted. The put adds a line ending for you:

你的程序会读取并重新输出所有文件中的所有行。换行符是自动切除的;它会自动从值中删除,因为这可能是你想要的。 put 为你添加换行符:

% raku your-program.p6 file1.txt file2.txt
Got line ...
Got line ...
...

You need those parentheses even without an argument. The lines routine can take an argument that tells it how many lines to grab:

即使没有参数,你也需要这些圆括号。lines 例程可以接收一个参数来告诉它要抓取多少行:

for lines(17) {
    put "Got line $_";
}

You can break the lines into “words.” This takes a Str (or something that can turn into a Str) and gives you back the nonwhitespace chunks as separate elements:

你可以将这些行分解成“单词”。这接收一个字符串 (或者可以变成字符串的东西),并将非空白块作为单独的元素返回:

say "Hamadryas perlicus sixus".words; # (Hamadryas perlicus sixus)
put "Hamadryas perlicus sixus".words.elems; # 3

Combine this with lines to iterate one word at a time:

将其与 lines 组合以一次迭代一个单词:

for lines.words { ... }

The .comb method takes it one step further by breaking it into characters:

.comb 方法通过将其分解为字符更进一步:

for lines.comb { ... }

You’ll see more about .comb in Chapter 16, where you’ll learn how to tell it to divide up the Str.

With those three things you can implement your own wc program:

你将在第16章中看到更多关于 .comb 的内容,在那里你将学习如何划分字符串

有了这三个东西你可以实现自己的 wc 程序:

for lines() {
    state $lines = 0;
    state $words = 0;
    state $chars = 0;
    $lines++;
    $words += .words;
    $chars += .comb;
    LAST {
        put "lines: $lines\nwords: $words\nchars: $chars";
        }
    }

The character count with this version doesn’t count all of the characters because the line ending was automatically removed.

EXERCISE 6.2Read the lines from the files you specify on the command line. Output each line prefixed by the line number. At the end of each line show the number of “words” in the line.

EXERCISE 6.3Output all of the lines of the butterfly census file (from https://www.learningraku.com/downloads/) that contain the genus Pyrrhogyra. How many lines did you find? If you don’t want to use that file try something else you have lying around.

此版本的字符计数不会计算所有字符,因为换行符会自动删除。

练习6.2 从命令行中指定的文件中读取行。输出以行号为前缀的每一行。在每行的末尾显示行中“单词”的数量。

练习6.3 输出包含 Pyrrhogyra 属的蝴蝶人口普查文件(来自https://www.learningraku.com/downloads/)的所有行。你找到了多少行?如果你不想使用该文件,请尝试其他的东西。

Ranges

A Range specifies the inclusive bounds of possible values without creating all of the items that would be in that List. A Range can be infinite because it doesn’t create all the elements; a List would take up all your memory.

Create a Range with .. and your bounds on either side:

Range 指定可能值的包含范围,而不创建该列表中的所有项。Range 可以是无限的,因为它不会创建所有元素;列表 会占用你所有的内存。

使用 .. 创建一个 Range,并在两边创建边界:

my $digit-range =   0 .. 10;
my $alpha-range = 'a' .. 'f';

If the lefthand value is larger than the righthand value you still get a Range, but it will have no elements and you won’t get a warning:

如果左手值大于右手值,你仍然得到一个 Range,但它没有元素,你不会收到警告:

my $digit-range = 10 .. 0;
put $digit.elems; # 0

You can exclude one or both endpoints with ^ on the appropriate side of the .. operator. Some people call thesethe cat ears:

你可以在 .. 运算符的适当一侧使用 ^ 排除一个或两个端点。有人称这些是猫耳朵:

my $digit-range = 0 ^..  10;  # exclude  0        ( 1..10 )
my $digit-range = 0  ..^ 10;  # exclude 10        ( 0..9  )
my $digit-range = 0 ^..^ 10;  # exclude  0 and 10 ( 1..9  )

As a shortcut for a numeric range starting from 0, use the ^ and the upper (exclusive) bound. This is very common Raku code:

作为从 0 开始的数字范围的快捷方式,使用 ^ 和上限(不包括)。这是非常常见的 Raku 代码:

my $digit-range = ^10; # Same as 0 ..^ 10

This gives you the values 0 to 9, which is 10 values altogether even though 10 is not part of the range.

EXERCISE 6.4How many items are in the range from 'aa' to 'zz'? How many from 'a' to 'zz'?

A Range knows its bounds. To see all of the values it would produce you can use .List to turn it into a list. Be aware that if your Range is very large you might suddenly hog most of the memory on your system, so this isn’t something you’d normally want to do. It’s nice for debugging though:

这将为你提供值 09,即使 10 不是 Range 的一部分,也是 10 个值。

练习6.4 'aa''zz' Range内有多少项?从 'a''zz' 有多少?

Range 知道它的界限。要查看它将生成的所有值,你可以使用 .List 将其转换为列表。请注意,如果你的Range 非常大,你可能会突然占用系统上的大部分内存,因此这通常不是你想要做的事情。虽然调试很好:

% raku
> my $range = 'a' .. 'f';
"a".."f"
> $range.elems
6
> $range.List
(a b c d e f)

EXERCISE 6.5Show all of the spreadsheet cell addresses from B5 to F9.

A smart match against a Range checks if a value is between the Range’s bounds:

练习6.5 显示从 B5F9 的所有电子表格单元格地址。

针对 Range 的智能匹配检查值是否在 Range 的边界之间:

% raku
> 7 ~~ 0..10
True
> 11 ~~ ^10
False

A Range isn’t a List, though. Any value between the bounds is part of the Range, even if it’s not a value that you would get if you listified the Range:

但是,Range 不是列表。边界之间的任何值都是Range的一部分,即使它不是你为Range列表化时得到的值:

% raku
> 1.37 ~~ 0..10
True
> 9.999 ~~ 0..10
True
> -137 ~~ -Inf..Inf # infinite range!
True

Excluding the endpoint doesn’t mean that the last element is the next-lowest integer. Here, it’s the exact value 10 that’s excluded; everything positive and less than 10 is still in the Range:

排除端点并不意味着最后一个元素是下一个最小的整数。在这里,它被排除在外的确切值 10;一切积极且小于 10 的东西仍然在范围内:

% raku
> 9.999 ~~ ^10
True

This is quite different from the listified version!

这与listified版本完全不同!

The @ Coercer

A Range isn’t a List. In some situations it acts like separate elements instead of merely bounds but in others it maintains its rangeness. Usually that works because something implicitly coerces it for you.

Start with a Range. Output it using put and say. These show you different representations because their text representations of the object are different: put uses .Str and say uses .gist:

Range不是列表。在某些情况下,它的作用类似于单独的元素,而不仅仅是边界,但在其他情况下,它保持其范围。通常这是有效的,因为某些内容会暗中强转它。

Range 开始。用 putsay 输出它。这些显示了不同的表示形式,因为它们的对象的文本表示是不同的:put 使用 .Strsay 使用 .gist

my $range = 0..3;
put $range.^name; # Range
say $range;       # 0..3
put $range;       # 0 1 2 3

This distinction in the representation of the object is important. When you see say in this book it’s because I want to show you .gist because that’s closer to a summary of the object.

You can make that a List by coercing it with the .List method:

这种对象表示的区别很重要。当你看到本书中的说法时,这是因为我想向你展示 .gist,因为它更接近于对象的摘要。

你可以通过使用 .List 方法强制它来创建 列表

my $list = $range.List;
put $list.^name; # List
say $list;       # (0 1 2 3)

Which one of these you have matters. A List works differently in a smart match because the element must be part of the List:

你有哪些重要事项。 List 在智能匹配中的工作方式不同,因为该元素必须是列表的一部分:

put "In range? ", 2.5 ~~ $range;  # True
put "In list? ", 2.5  ~~ $list;   # False

Instead of typing .List everywhere that you want something treated as such, you can use the prefix list context operator, @, just like you’ve seen with the context operators + and ~:

你可以使用前缀列表上下文运算符 @,而不是在你想要的上下文运算符 +~ 中使用。

my $range = 0..3;

put "In range? ",  2.5 ~~ $range;       # True (Range object)
put "In .List? ",  2.5 ~~ $range.List;  # False (List object)
put "In @?     ",  2.5 ~~ @$range;      # False (List object)

Later you’ll use the @ sigil for Array variables. For now it’s a convenient way to treat something like a List.

稍后你将对数组变量使用 @ sigil。现在,它是一种方便的方式来处理像列表这样的东西。

Sequences

A sequence, Seq, knows how to make a future List. It’s similar to a List but it’s lazy. It knows where its values will come from and defers producing them until you actually need them.

序列 Seq 知道如何创建未来的 List。它类似于 List,但它很惰性的。它知道它的值来自哪里,并推迟生产它们,直到你真正需要它们为止。

NOTE

A Seq isn’t really a Positional but it has a way to fake it. Rather than explain that I’m going to fake it too.

You call .reverse on a List to flip the list around. When you call List methods it just works:

Seq 不是真正的 Positional ,但它有办法伪造它。多说无意,我也会伪造它。

你在列表上调用 .reverse 来翻转列表。当你调用列表方法时,它只是起作用:

my $countdown = <1 2 3 4 5>.reverse;
put $countdown.^name; # Seq
put $countdown.elems; # 5

The result isn’t actually a Seq, but in most common cases that isn’t important. The things that try to use it as a List will get what they expect, and there’s no immediate need to create another List when the Seq knows the values from the original one.

However, calling .eager converts the Seq to a List:

结果实际上不是 Seq,但在大多数常见情况下并不重要。尝试将其用作列表的东西将获得他们期望的东西,并且当Seq知道原始值的值时,不需要立即创建另一个List

但是,调用 .eager 会将 Seq 转换为 List

my $countdown = <1 2 3 4 5>.reverse.eager;
put $countdown.^name; # Seq
put $countdown.elems; # 5

If you assign the Seq to a variable with the @ sigil the Seq also turns into a List. This is an eager assignmentto an Array (coming up soon):

如果将 Seq 赋值给带有 @ sigil的变量,Seq 也会变成 List。这是对数组的急切赋值(即将推出):

my @countdown = <1 2 3 4 5>.reverse;
put @countdown.^name; # Array
put @countdown.elems; # 5

The .pick method chooses a random element from a List:

.pick 方法从 List 中选择一个随机元素:

my $range = 0 .. 5;
my $sequence = $range.reverse;
say $sequence.^name; # Seq;

put $sequence.pick;  # 5 (or maybe something else)

By default you can only iterate through a Seq once. You use an item, then move on to the next one. This means that a Seq needs to know how to make the next element, and once it uses it it can discard it—it doesn’t remember past values. If you try to use the Seq after it’s gone through all of its elements you get an error:

默认情况下,你只能迭代一次 Seq。你使用项,然后转到下一项。这意味着 Seq 需要知道如何制作下一个元素,一旦它使用它就可以丢弃它 - 它不记得过去的值。如果你尝试使用 Seq 后,它会遇到所有元素,则会出现错误:

put $sequence.pick;  # 3 (or maybe something else)
put $sequence;       # Error

The error tells you what to do:

错误告诉你该怎么做:

This Seq has already been iterated, and its values consumed
(you might solve this by adding .cache on usages of the Seq, or
by assigning the Seq into an array)

Adding .cache remembers the elements of the Seq so you can reuse them. After the .pick there’s no error:

添加 .cache 会记住 Seq 的元素,因此你可以重用它们。在 .pick 之后没有错误:

my $range = 0 .. 5;
my $sequence = $range.reverse.cache;
say $sequence.^name; # Seq;

put $sequence.pick;  # 5 (or maybe something else)
put $sequence;       # 5 4 3 2 1 0

This isn’t something you want to do carelessly, though. Part of the benefit of the Seq is the memory saving it provides by not duplicating data unless it needs to.

不过,这不是你想要做的事情。 Seq 的部分好处是它提供的内存节省,除非需要,否则不会复制数据。

Infinite Lazy Lists

The Seq has to make all of the elements to .pick one of them. Once it does that it forgets them and doesn’t have a way to make more elements. Raku does this to support infinite lazy lists. You make these with the triple-dot sequence operator, .... By binding to the Seq you give it a name without immediately reducing it to its values:

Seq 必须使所有元素能 .pick 其中之一。一旦它这样做就会忘记它们,并且没有办法制作更多的元素。 Raku这样做是为了支持无限的惰性列表。你使用三点序列运算符 ... 来制作它们。通过绑定到 Seq,你给它一个名字,而不是立即将它减少到它的值:

my $number-sequence := 1 ... 5;

That’s the integers from 1 to 5. The Seq looks at the start and figures out how to get to the end. That sequence is easy; it adds a whole number.

You can make an exclusive endpoint (but not an exclusive startpoint). This Seq is the integers from 1 to 4:

这是从 1 到 5 的整数。 Seq 查看开始并找出如何到达终点。这个序列很简单;它增加了一个整数。

你可以创建一个独占端点(但不是一个独占的起点)。此 Seq 是 1 到 4 的整数:

my $exclusive-sequence := 1 ...^ 5;

A Range can’t count down, but a Seq can. This one subtracts whole numbers:

Range 不能倒数,但是 Seq 可以。这一个减去整数:

my $countdown-sequence := 5 ... 1;

The same thing works for letters:

同样的事情适用于字母:

my $alphabet-sequence := 'a' ... 'z';

You can tell the Seq how to determine the next element. You can specify more than one item for the start to give it the pattern:

你可以告诉 Seq 如何确定下一个元素。你可以为开头指定多个项目以为其指定模式:

my $s := 0, 1, 2 ... 256; # 257 numbers, 0 .. 256

This is the series of whole numbers from 0 to 256. That’s the easiest pattern there. But add a 4 after the 2 and it’s a different series. Now it’s the powers of 2:

这是一系列从 0 到 256 的整数。这是最简单的模式。但是在 2 之后添加 4,这是一个不同的系列。现在它是 2 的幂:

my $s := 0, 1, 2, 4 ... 256; # powers of 2
say $s; # (0 1 2 4 8 16 32 64 128 256)

The ... can figure out arithmetic or geometric series. But it gets better. If you have a more complicated series you can give it a rule to make the next item. That rule can be a Block that grabs the previous argument and transforms it. Here it adds 0.1 to the previous element until it gets to 1.8. You couldn’t do this with a Range:

... 可以算出算术或几何系列。但它变得更好。如果你有一个更复杂的系列,你可以给它一个规则来制作下一个项。该规则可以是一个阻止前一个参数并对其进行转换的块。这里它将前一个元素加 0.1,直到达到 1.8。你无法使用 Range 执行此操作:

my $s := 1, { $^a + 0.1 } ... 1.8;
say $s; # (1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8)

If you have more than one positional parameter in your Block it looks farther back in the series. Here are the Fibonacci numbers up to 21:

如果你的 Block 中有多个位置参数,它会在系列中看起来更远。以下是最多 21 的斐波那契数字:

my $s := 1, 1, { $^a + $^b } ... 21;
say $s; # (1 1 2 3 5 8 13 21)

The Seq only ends when it creates an item that is exactly equal to the endpoint. If you change that to 20 you get an infinite series and your program hangs while it creates every element so it can count them:

Seq 仅在创建与端点完全相同的项目时结束。如果将其更改为 20,则会得到一个无限系列,并且程序会在创建每个元素时挂起,以便计算它们:

my $s := 1, 1, { $^a + $^b } ... 20;
say $s.elems;  # never gets an answer but keeps trying

Instead of a literal endpoint you can give it a Block. The Seq stops when the Block evaluates to True (but keeps the element that makes it True):

而不是字面端点,你可以给它一个Block。当Block计算为 True 时,Seq 停止(但保持使其为 True 的元素):

my $s := 1, 1, { $^a + $^b } ... { $^a > 20 };
say $s.elems; # (1 1 2 3 5 8 13 21)

Those Blocks are unwieldy, but you know that you can shorten them with Whatevers. Do the endpoint first:

那些 Block 很笨重,但你知道你可以用 Whatever 缩短它们。首先执行端点:

my $s := 1, 1, { $^a + $^b } ... * > 20;

You can reduce the first Block with two Whatevers. That WhateverCode sees two *s and knows it needs two elements:

你可以使用两个 Whatever 减少第一个 BlockWhateverCode 看到两个 * 并知道它需要两个元素:

my $s := 1, 1, * + * ... * > 20;

That stops the Fibonacci numbers at 21. What if you wanted all of the Fibonacci numbers? The Whatever by itself can be the endpoint and in that context it is never True; this series never ends:

这会阻止斐波那契数字为 21.若你想要所有斐波那契数字怎么办? Whatever 本身可以是端点,在这种情况下它永远不会是真的;这个系列永远不会结束

my $s := 1, 1, * + * ... *;

This is one of the reasons .gist exists. It gives a summary of the object. It knows that this is an infinite Seq so it doesn’t try to represent it:

这是其中一个原因 .gist 存在。它给出了对象的摘要。它知道这是一个无限的 Seq 所以它不会试图表示它:

put $s.gist; # (...)
say $s;      # (...), .gist implicitly

That’s it. That’s the heart of Seq. It can produce an infinite number of values but it doesn’t do it immediately. It knows the pattern to get to the next one.

Recall that a Seq doesn’t remember all the values. Once it goes through them it doesn’t store them or regenerate them. In this example it reverses the list and exhausts the series. That’s all in the first put. There’s nothing left for the second put:

而已。这是 Seq 的核心。它可以产生无限数量的值,但它不会立即执行。它知道进入下一个模式的模式。

回想一下 Seq 不记得所有的值。一旦它通过它们就不会存储它们或重新生成它们。在此示例中,它会反转列表并耗尽系列。这是第一次投入。第二次放置没有任何东西:

my $s := 1 ... 5;

put $s.reverse; # (5 4 3 2 1)
put $s;         # Error

You get this error:

你收到这个错误:

This Seq has already been iterated, and its values consumed
(you might solve this by adding .cache on usages of the Seq, or
by assigning the Seq into an array)

The error tells you what to do. You can call .cache on a Seq to force it to remember the values, even if this will eat up all of your memory:

该错误告诉你该怎么做。你可以在 Seq 上调用 .cache 来强制它记住这些值,即使这会占用你所有的内存:

my $s := 1 ... 5;
put $s.cache.reverse; # 5 4 3 2 1
put $s;               # 1 2 3 4 5

Should you need to treat a Seq as a List, coerce it with @. This generates all of its values:

如果你需要将 Seq 视为列表,请使用 @ 强制它。这会生成所有值:

my $s = ( 1 ... 5 );
put $s.^name; # Seq

my $list-from-s = @$s;
put $list-from-s.^name; #List

Most of the time a Seq will act like a List, but sometimes you need to give some hints.

大多数情况下,Seq 会像 List 一样,但有时你需要提供一些提示。

Gathering Values

The previous Seqs could easily compute their next values based on the ones that came before. That’s not always the case. A gather with a Block returns a Seq. When you want the next value the gather runs the code. A take produces a value. Here’s the same thing as 1 ... 5 using gather:

之前的 Seq 可以根据之前的 Seq 轻松计算下一个值。情况并非总是如此。带有块的聚集返回 Seq。当你想要下一个值时,聚集会运行代码。拍摄会产生一个价值。这与使用 gather1 ... 5 相同:

my $seq := gather {
    state $previous = 0;

    while $previous++ < 5 { take $previous }
    }

say $seq;

Each time the code encounters a take it produces a value, then waits until the next time something asks for a value. The Seq stops when the code gets to the end of the gather Block. In this example, the while Blockruns once for each access to the Seq.

You don’t need the braces for the Block if the statement fits on one line. This is an infinite Seq:

每次代码遇到一个 take 它产生一个值,然后等到下一次有东西要求一个值。当代码到达 gather

的末尾时, Seq 停止。在这个例子中,while Block 每次访问 Seq 一次。

如果语句适合一行,则不需要Block的大括号。这是一个无限的 Seq

my $seq := gather take $++ while 1;

Those are easily done with the tools you already had. What about a random Seq of random values? This gather keeps choosing one value from @array, forever:

使用你已有的工具可以轻松完成这些工作。随机值的随机 Seq 怎么样?这个 gather 永远从 @array 中选择一个值:

my @array = <red green blue purple orange>;
my $seq := gather take @array.pick(1) while 1;

Here’s a gather that provides only the lines of input with eq in them. It doesn’t have to wait for all of the input to start producing values. And since the Seq controls access to the lines, you don’t need to use or store them right away:

这是一个 gather,它只提供带有 eq 的输入行。它不必等待所有输入开始生成值。由于 Seq 控制对行的访问,因此你无需立即使用或存储它们:

my $seq := gather for lines() { next unless /eq/; take $_ };

for $seq -> $item {
    put "Got: $item";
    }

You can store these in a Positional without being eager:

你可以将它们存储在一个 Positional 而不是急切的:

my @seq = lazy gather for lines() { next unless /eq/; take $_ };

for @seq -> $item {
    put "Got: $item";
    }

It doesn’t matter how you create the Seq. Once you have it you can use it and pass it around like any other sequence.

EXERCISE 6.6Use gather and take to produce an infinite cycle of alternating values from an Array of color names. When you get to the end of the array, go back to the beginning and start again.

你如何创建 Seq 并不重要。一旦你拥有它,你可以使用它并像任何其他序列一样传递它。

练习6.6 使用 gathertake 从颜色名称数组中产生无限循环的交替值。当你到达数组的末尾时,回到开头并重新开始。

Single-Element Access

You can extract a particular element by its position in the object, whether that’s a List, Range, Seq, or other type of Positional thingy. Each position has an index that’s a positive integer (including 0). To get the element, append [POSITION] to your thingy:

你可以通过它在对象中的位置来提取特定元素,无论是 List, Range, Seq 还是其他类型的 Positional thingy。每个位置都有一个正整数(包括0)的索引。要获取元素,请将[POSITION]附加到你的东西:

my $butterfly-genus = <Hamadryas Sostrata Junonia>;
my $first-butterfly = $butterfly-genus[0];
put "The first element is $first-butterfly";

[POSITION]is a postcircumfix operator. Operators are actually methods (Chapter 12), so you can use the method dot between the object and the [POSITION] (although you mostly won’t):

[POSITION]是一个 postcircumfix 运算符。操作符实际上是方法(第12章),因此你可以使用对象和[ POSITION ] 之间的方法点(尽管你通常不会):

my $first-butterfly = $butterfly-genus.[0];

You can interpolate either form in double-quoted Strs:

你可以在双引号字符串中插入任一形式:

put "The first butterfly is $butterfly-genus[0]";
put "The first butterfly is $butterfly-genus.[0]";

Since the index counts from zero the last position is one less than the number of elements. The .end methodknows that position:

由于索引从零开始计数,因此最后一个位置比元素数少一个。 .end 方法知道该位置:

my $end = $butterfly-genus.end;               # 2
my $last-butterfly = $butterfly-genus[$end];  # Junonia

If the thingy happens to be a lazy list you’ll get an error trying to find its end element; you can check if it is with .is-lazy and perhaps do something different in that case:

如果thingy恰好是一个懒惰的列表,你会在尝试找到它的结束元素时遇到错误;你可以检查它是否与 .is-lazy 一起,并且可能在这种情况下做一些不同的事情:

my $butterfly-genus = <Hamadryas Sostrata Junonia>;
$butterfly-genus = (1 ... * );
put do if $butterfly-genus.is-lazy { 'Lazy list!' }
       else {
            my $end = $butterfly-genus.end;
            $butterfly-genus[$end]
            }

If you specify a position less than 0 you get an error. If you try to do it with a literal value the error message tells you that you’ve carried a habit over from a different language:

如果指定小于 0 的位置,则会出现错误。如果你尝试使用文字值来执行此操作,则错误消息会告诉你,你已经习惯使用其他语言:

$butterfly-genus[-1];  # fine in Perl 5, but error in Raku!

The error message tells you to use *-1 instead, which you’ll read more about in just a moment:

错误消息告诉你使用 *-1 代替,你将在稍后阅读更多信息:

Unsupported use of a negative -1 subscript to index from the end;
in Raku please use a function such as *-1

But if you’ve put that index in a variable, perhaps as the result of poor math, you get a different error:

但是如果你把这个索引放在变量中,也许是因为数学不好,你会得到一个不同的错误:

my $end = -1;
$butterfly-genus[$i];

This time it tells you that you are out of bounds:

这次它告诉你你已经出界了:

Index out of range. Is: -1, should be in 0..^Inf

This doesn’t work the same way on the other side. If you try to access an element beyond the last one, you get back Nil with no error message:

这在另一方面不起作用。如果你尝试访问超出最后一个元素的元素,则会返回 Nil 而不显示任何错误消息:

my $end = $butterfly-genus.end;
$butterfly-genus[$end + 1];  # Nil!

Curiously, though, you can’t use Nil to tell if you specified a wrong position because Nil can be an element of a List:

但奇怪的是,你不能使用 Nil 来判断你是否指定了错误的位置,因为 Nil 可以是 List 的一个元素:

my $has-nil = ( 'Hamadryas', Nil, 'Junonia', Nil );
my $butterfly = $has-nil.[3]; # works, but still Nil!

You can also put almost any code you like inside the square brackets. It should evaluate to an Int, but if it doesn’t the operator will try to convert it to one. You can skip the $end variable you’ve used so far and use.end directly:

你也可以将几乎任何你喜欢的代码放在方括号内。它应该评估为 Int,但如果不是,则运算符将尝试将其转换为1。你可以跳过你到目前为止使用的 $end 变量并直接使用 .end

my $last-butterfly = $butterfly-genus[$butterfly-genus.end];

If you wanted the next-to-last element, you could subtract one:

如果你想要倒数第二个元素,你可以减去一个:

my $next-to-last = $butterfly-genus[$butterfly-genus.end - 1];

This way of counting from the end is quite tedious though, so there’s a shorter way to do it. A Whatever starinside the [] is the number of elements in the list (not the last index!). That * is one greater than the last position. Subtract 1 from * to get the index for the last element:

这种从末尾计算的方式相当繁琐,所以有一个更短的方法来做到这一点。 Whatever,[]是列表中元素的数量(不是最后一个索引!)。那个比最后一个位置大一个。从中减去1以获取最后一个元素的索引:

my $last-butterfly = $butterfly-genus[*-1];

To get the next-to-last element, subtract one more:

要获得倒数第二个元素,再减去一个:

my $next-to-last = $butterfly-genus[*-2];

If you subtract more than the number of elements, you’ll get Nil (rather than an out-of-index error like you would without the *).

If you have a Seq it will create whatever items it needs to get to the one that you ask for. The triangle numbers add the index of the element to the previous number to get the next number in the series. If you want the fifth one ask for that index:

如果你减去超过元素的数量,你将得到 Nil (而不是像你没有 * 那样的索引之外的错误)。

如果你有一个 Seq,它将创建你需要的任何物品到你要求的那个。三角形数字将元素的索引添加到前一个数字,以获得系列中的下一个数字。如果你想要第五个请求该索引:

my $triangle := 0, { ++$ + $^a } ... *;
say $triangle[4];

EXERCISE 6.7The squares of numbers is the sequence where you add 2n–1 to the previous value. n is the position in the sequence. Use the sequence operator ... to compute the square of 25.

练习6.7 数字的平方是将 2n-1加到前一个值的序列。 n 是序列中的位置。使用序列运算符 ... 来计算 25 的平方。

Changing a Single Element

If your List element is a container you can change its value. Previously you used an anonymous scalar container as a placeholder in one of your lists:

如果List元素是容器,则可以更改其值。以前,你使用匿名标量容器作为其中一个列表中的占位符:

my $butterflies = ( $, 'Sostrata', 'Junonia' );
say $butterflies; # ((Any) perlicus Sostrata Junonia)

You can’t change the container, but you can change the value that’s in the container:

你无法更改容器,但可以更改容器中的值:

$butterflies.[0] = 'Hamadryas';
say $butterflies; # (Hamadryas Sostrata Junonia)

If you try to change an item that is not a container you get an error:

如果你尝试更改不是容器的项目,则会收到错误消息:

$butterflies.[1] = 'Ixias';

The error tells you that the element there is something that you cannot change:

该错误告诉你该元素有一些你无法更改的内容:

Cannot modify an immutable Str (...)

Multiple-Element Access

You can access multiple elements at the same time. A slice specifies more than one index in the brackets:

你可以同时访问多个元素。切片在括号中指定多个索引:

my $butterfly-genus = <Hamadryas Sostrata Junonia>;
my ( $first, $last ) = $butterfly-genus[0, *-1];
put "First: $first Last: $last";

Notice that you can declare multiple variables at the same time by putting them in parentheses after the my. Since that’s not a subroutine call you still need a space after my. The output shows the first and last elements:

请注意,你可以通过将多个变量放在 my 之后的括号中来同时声明多个变量。因为这不是一个子程序调用,你仍需要一个空格。输出显示第一个和最后一个元素:

First: Hamadryas Last: Junonia

The indices can come from a Positional. If you’ve stored that in a scalar variable you have to coerce or flatten it:

指数可以来自一个 Positional。如果你将它存储在标量变量中,你必须强制或压扁它:

put $butterfly-genus[ 1 .. *-1 ];    # Sostrata Junonia

my $indices = ( 0, 2 );
put $butterfly-genus[ @$indices ];  # Hamadryas Junonia
put $butterfly-genus[ |$indices ];  # Hamadryas Junonia

my @positions = 1, 2;
put $butterfly-genus[ @positions ]; # Sostrata Junonia

Assigning to multiple elements works the same way inside the brackets. However, the elements must be mutable. If they aren’t containers you won’t be able to change them:

分配给多个元素在括号内的工作方式相同。但是,元素必须是可变的。如果它们不是容器,你将无法更改它们:

my $butterfly-genus = ( $, $, $ );
$butterfly-genus[ 1 ] = 'Hamadryas';
$butterfly-genus[ 0, *-1 ] = <Gargina Trina>;
put $butterfly-genus;

You can fix that by using an Array, which you’re about to read more about. The Array automatically containerizes its elements:

你可以通过使用一个数组来修复它,你将要阅读更多。 数组自动容纳其元素:

my @butterfly-genus = <Hamadryas Sostrata Junonia>;
@butterfly-genus[ 0, *-1 ] = <Gargina Trina>;
put @butterfly-genus;

Arrays

You can’t change a List. Once constructed it is what it is and keeps the same number of elements. You can’t add or remove any elements. Unless the item is a container, each List item’s value is fixed.

Arrays are different. They containerize every item so that you can change any of them, and the Array itself is a container. You could start with the Array class to make an object:

你无法更改列表。一旦构造它就是它的原因并保持相同数量的元素。你无法添加或删除任何元素。除非该项是容器,否则每个列表项的值都是固定的。

数组是不同的。它们将每个项目包含在内,以便你可以更改它们中的任何项目,而数组本身就是一个容器。你可以从Array类开始创建一个对象:

my $butterfly-genus = Array.new: 'Hamadryas', 'Sostrata', 'Junonia';

You’ll probably never see that, though. Instead, you can use square brackets to make an Array. Each item in the Array becomes a container even if it didn’t start as one:

但是你可能永远都看不到。相反,你可以使用方括号来制作数组数组中的每个项目都成为一个容器,即使它没有以一个方式启动:

my $butterfly-genus = ['Hamadryas', 'Sostrata', 'Junonia'];

Since every item is a container you can change any value by assigning to it through a single-element access:

由于每个项目都是容器,因此你可以通过单元素访问权限分配任何值:

$butterfly-genus.[1] = 'Paruparo';
say $butterflies; # [Hamadryas Paruparo Junonia]

This new behavior gets its own sigil, the @ (which looks a bit like an a for Array). When you assign a listy thing to an @ variable you get an Array:

这个新行为得到了自己的印记,@ (看起来有点像一个数组)。当你为 @ 变量分配一个listy的东西时,你得到一个数组:

my @butterfly-genus = <Hamadryas Sostrata Junonia>;
put @butterfly-genus.^name;  # Array

The = here is the list assignment operator you met earlier. Since you have an Array on the left side of the operator the = knows it’s the list variety. That one is lower precedence than the comma, so you can leave off the grouping parentheses you’ve been using so far:

这里的 = 是你之前遇到的列表赋值运算符。由于运算符左侧有一个数组,因此 = 知道它是列表种类。那个优先级低于逗号,所以你可以不用你到目前为止使用的分组括号:

my @butterfly-genus = 1, 2, 3;

EXERCISE 6.8You’ve already used an Array that you haven’t seen. @*ARGS is the collection of Strs that you’ve specified on the command line. Output each element on its own line.

练习6.8 你已经使用过一个你没见过的数组@*ARGS 是你在命令行中指定的字符串集合。输出每个元素在自己的行上。

Constructing an Array

There’s a hidden list assignment here that makes this possible. In its expanded form there are a couple of steps. Greatly simplified, the Array sets up a scalar container for the number of items it will hold and binds to that:

这里有一个隐藏的列表分配,这使得这成为可能。在其扩展形式中,有几个步骤。大大简化了,Array 为它将保持并绑定到的项目数量设置了一个标量容器:

my @butterfly-genus := ( $, $, $ ); # binding

Then it assigns the items in the incoming list to the containers in the Array:

然后它将传入列表中的项目分配给 Array 中的容器:

@butterfly-genus = <Hamadryas Sostrata Junonia>;

You don’t need to do any of this yourself because it happens automatically when you assign to an Array (the @variable). Array items are always containers, and the Array itself is a container.

The square brackets construct an Array (and it’s the square brackets that index Arrays). You can assign to a scalar or Array variable:

你不需要自己执行任何操作,因为它在你分配给数组(@variable )时会自动发生。数组项始终是容器,Array本身是容器。

方括号构造一个数组(它是索引数组的方括号)。你可以分配标量或数组变量:

my $array = [ <Hamadryas Sostrata Junonia> ];
put $array.^name;      # Array
put $array.elems;      # 3
put $array.join: '|';  # Hamadryas|Sostrata|Junonia

my @array = [ <Hamadryas Sostrata Junonia> ];
put @array.^name;      # Array
put @array.elems;      # 3
put @array.join: '|';  # Hamadryas|Sostrata|Junonia

If you are going to assign to @array you don’t need the brackets, though. This is the same thing:

但是,如果要分配给 @array,则不需要括号。这是一回事:

my @array = <Hamadryas Sostrata Junonia>;
put @array.^name;      # Array
put @array.elems;      # 3

The brackets are handier when you want to skip the variable. You would do this for temporary data structures or subroutine arguments. You’ll see more of those as you go on.

如果要跳过变量,括号更方便。你可以为临时数据结构或子例程参数执行此操作。随着你的继续,你会看到更多这些。

Interpolating Arrays

A double-quoted Str can interpolate single or multiple elements of a Positional or even all the elements. Use the brackets to select the elements that you want:

双引号字符串可以插入Positional的单个或多个元素甚至所有元素。使用括号选择所需的元素:

my $butterflies = <Hamadryas Sostrata Junonia>;
put "The first butterfly is $butterflies[0]";
put "The last butterfly is $butterflies[*-1]";
put "Both of those are $butterflies[0,*-1]";
put "All the butterflies are $butterflies[]";

When it interpolates multiple elements it inserts a space between the elements:

当它插入多个元素时,它会在元素之间插入一个空格:

The first butterfly is Hamadryas
The last butterfly is Junonia
Both of those are Hamadryas Junonia
All the butterflies are Hamadryas Sostrata Junonia

You can interpolate Ranges too:

你也可以插入 Range

my $range = 7 .. 13;
put "The first is $range[0]";    # The first is 7
put "The last is $range[*-1]";   # The last is 13
put "All are $range";            # All are 7 8 9 10 11 12 13

The other Positionals behave similarly based on how they generate their elements.

其他 Positional 基于它们如何生成元素的行为类似。

Array Operations

Since the Array is a container you can change it. Unlike with a List, you can add and remove items. The .shift method removes the first item from the Array and gives it back to you. That item is no longer in the Array:

由于 Array 是容器,你可以更改它。与 List 不同,你可以添加和删除项目。 .shift 方法从数组中删除第一个项目并将其返回给你。该项不再在数组中:

my @butterfly-genus = <Hamadryas Sostrata Junonia>;
my $first-item = @butterfly-genus.shift;
say @butterfly-genus;  # [Sostrata Junonia]
say $first-item;       # Hamadryas

If the Array is empty you get a Failure, but you won’t learn about those until Chapter 7. You don’t get an immediate error; the error shows up when you try to use it later:

如果数组为空,则会出现 Failure,但在第7章之前你将不会了解这些故障。你没有立即收到错误;稍后尝试使用时会出现错误:

my @array = Empty;
my $element = @array.shift;
put $element.^name;  # Failure (soft exception)

That error is False but won’t complain when it’s in a conditional:

该错误是 False 的,但是当它处于条件状态时不会抱怨:

while my $element = @array.shift { put $element }

The .pop method removes the last item:

.pop 方法删除最后一项:

my @butterfly-genus = <Hamadryas Sostrata Junonia>;
my $first-item = @butterfly-genus.pop;
say @butterfly-genus;  # [Hamadryas Sostrata]
say $first-item;       # Junonia

To add one or more items to the front of the list, use .unshift. One top-level item becomes one element in the Array:

要将一个或多个项添加到列表的前面,请使用 .unshift。一个顶级项目成为数组中的一个元素:

my @butterfly-genus = Empty;
@butterfly-genus.unshift: <Hamadryas Sostrata>;
say @butterfly-genus;  # [Hamadryas Sostrata]

.push adds a list of items to the end of the list:

.push将一个项列表添加到列表的末尾:

@butterfly-genus.push: <Junonia>;
say @butterfly-genus;  # [Hamadryas Sostrata Junonia]

With .splice you can add elements to or remove them from anywhere in the Array. It takes a starting index, a length, and the items to remove from the list. It gives you the elements it removed:

使用 .splice,你可以在Array中的任何位置添加元素或从中删除元素。它需要一个起始索引,一个长度以及要从列表中删除的项目。它为你提供了删除的元素:

my @butterfly-genus = 1 .. 10;
my @removed = @butterfly-genus.splice: 3, 4;
say @removed;          # [4 5 6 7]
say @butterfly-genus;  # [1 2 3 8 9 10]

You can give .splice items to replace those that you removed:

你可以提供 .splice 项目来替换你删除的项目:

my @butterfly-genus = 1 .. 10;
my @removed = @butterfly-genus.splice: 5, 2, <a b c>;
say @removed;          # [6 7]
say @butterfly-genus;  # [1 2 3 4 5 a b c 8 9 10]

If the length is 0 you don’t remove anything, but you can still insert items. You get an empty Array back:

如果长度为 0,则不会删除任何内容,但仍可以插入项目。你得到一个空数组

my @butterfly-genus = 'a' .. 'f';
my @removed = @butterfly-genus.splice: 5, 0, <X Y Z>;
say @removed;          # []
say @butterfly-genus;  # [a b c d e X Y Z f]

Each of these Array methods have routine versions:

这些数组方法中的每一个都有例程版本:

my $first = shift @butterfly-genus;
my $last  = pop @butterfly-genus;

unshift @butterfly-genus, <Hamadryas Sostrata>;
push @butterfly-genus, <Junonia>

splice @butterfly-genus, $start-pos, $length, @elements;

EXERCISE 6.9Start with an Array that holds the letters from a to f. Use the Array operators to move those elements to a new Array that will have the same elements in reverse order.

EXERCISE 6.10Start with the Array that holds the letters from a to f. Use only .splice to make these changes: remove the first element, remove the last element, add a capital A to the front of the list, and add a capital F to the end of the list.

练习6.9 启动一个包含从 a 到 f 的字母的数组。使用数组运算符将这些元素移动到一个新的数组,它将以相反的顺序具有相同的元素。

EXERCISE 6.10 从包含 a 到 f 的字母的数组开始。仅使用 .splice 进行这些更改:删除第一个元素,删除最后一个元素,将大写 A 添加到列表的前面,然后将大写字母 F 添加到列表的末尾。

Lists of Lists

A List can be an element of another List (or Seq). Depending on your previous language experience your reaction to this idea will be either “Of course!” or “This is so wrong!”

The .permutations method produces a Seq of sublists where each one represents a unique ordering of all the elements of the original:

List 可以是另一个 List(或 Seq)的元素。根据你之前的语言经验,你对此想法的反应将是“当然!”或“这是错误的!”

.permutations 方法生成一个 Seq 的子列表,其中每个子列表代表原始元素的唯一排序:

my $list = ( 1, 2, 3 );
say $list.permutations;
put "There are {$list.permutations.elems} elements";

The output shows a List of Lists where each element is another List:

输出显示列表列表,其中每个元素是另一个列表

((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))
There are 6 elements

You can make these directly. This List has two elements, both of which are Lists:

你可以直接制作这些。此列表有两个元素,两个元素都是列表

my $list = ( <a b>, <1 2> );
put $list.elems;             # 2
say $list;                   # ((a b) (1 2))

You can explicitly create the sublists with parentheses:

你可以使用括号显式创建子列表:

my $list = ( 1, 2, ('a', 'b') );
put $list.elems;  # 3
say $list;        # (1 2 (a b))

You can separate sublists with semicolons. Elements between ; end up in the same sublist, although sublists of a single element are just that element:

你可以使用分号分隔子列表。; 之间的元素最终在同一个子列表中,尽管单个元素的子列表只是该元素:

my $list = ( 1; 'Hamadryas'; 'a', 'b' );
put $list.elems;       # 3
say $list;             # (1 2 (a b))
put $list.[0].^name;   # Int
put $list.[1].^name;   # Str
put $list.[*-1].^name; # List

Flattening Lists

You may be more comfortable with flat Lists, if you want a bunch of elements with no structure. .flat extracts all the elements of the sublist and makes it a single-level simple list. The flat List created here has four elements instead of three:

如果你想要一堆没有结构的元素,你可能会更喜欢平面列表.flat 提取子列表的所有元素,并使其成为单级简单列表。这里创建的平面列表有四个元素而不是三个:

my $list = ( 1, 2, ('a', 'b') );
put $list.elems;  # 3

my $flat = $list.flat;
put $flat.elems;  # 4
say $flat;        # (1 2 a b)

This works all the way down into sublists of sublists (of sublists…). Here, the last element is a sublist that has a sublist. The flat List ends up with six elements:

这一直有效地进入子列表(子列表……)的子列表中。这里,最后一个元素是一个具有子列表的子列表。平面列表最终有六个元素:

my $list = ( 1, 2, ('a', 'b', ('X', 'Z') ) );
put $list.elems;  # 3

my $flat = $list.flat;
put $flat.elems;  # 6
say $flat;        # (1 2 a b X Z)

Sometimes you don’t want a sublist to flatten. In that case you can itemize it by putting a $ in front of the parentheses. An itemized element resists flattening:

有时你不希望子列表变平。在这种情况下,你可以通过在圆括号前加一个 $ 来项化它。项化的元素抵制展平:

my $list = ( 1, 2, ('a', 'b', $('X', 'Z') ) );
put $list.elems;  # 3

my $flat = $list.flat;
put $flat.elems;  # 5
say $flat;        # (1 2 a b (X Z))

A List held in a scalar variable is already itemized and does not flatten:

标量变量中保存的列表已项化,并未展平:

my $butterfly-genus = ('Hamadryas', 'Sostrata', 'Junonia');

my $list = ( 1, 2, ('a', 'b', $butterfly-genus ) );
my $flat = $list.flat;
say $flat;        # (1 2 a b (Hamadryas Sostrata Junonia))

Then what do you do to un-itemize something? You can use the prefix | to flatten it. This decontainerizes thethingy:

那么你怎么做才能不项化一些事情呢?你可以使用前缀 | 压扁它。这使得这些东西解容器化了:

my $butterfly-genus = ('Hamadryas', 'Sostrata', 'Junonia');

my $list = ( 1, 2, ('a', 'b', |$butterfly-genus ) );
my $flat = $list.flat;
put $flat.elems;  # 7
say $flat;        # (1 2 a b Hamadryas Sostrata Junonia)

The | takes certain types (Capture, Pair, List, Map, and Hash) and flattens them. You’ll see more of this in Chapter 11. It actually creates a Slip, which is a type of List that automatically flattens into an outer list. You could coerce your List with .Slip to get the same thing:

| 接收某些类型(CapturePair, List, Map, and Hash并展平它们。你将在第11章中看到更多内容。它实际上创建了一个 Slip,它是一种 List,可以自动展平到外部列表中。你可以使用 .Slip 强制你的 List 来获得相同的东西:

my $list = ( 1, 2, ('a', 'b', $butterfly-genus.Slip ) );

Now the elements in $butterfly-genus are at the same level as the other elements in its sublist:

现在 $butterfly-genus 中的元素与其子列表中的其他元素处于同一级别:

(1 2 (a b Hamadryas Sostrata Junonia))

The slip routine does the same thing:

slip 例程做同样的事情:

my $list = ( 1, 2, ('a', 'b', slip $butterfly-genus ) );

These Slips will be handy later in this chapter.

这些Slip将在本章后面方便使用。

Interesting Sublists

Here’s something quite useful. The .rotor method breaks up a flat List into a List of Lists where each sublist has the number of elements you specify. You can get five sublists of length 2:

这儿有一些非常有用的东西。 .rotor 方法将平面列表拆分为列表列表,其中每个子列表都包含你指定的元素数。你可以获得 5 个长度为 2 的子列表:

my $list = 1 .. 10;
my $sublists = $list.rotor: 2;
say $sublists;        # ((1 2) (3 4) (5 6) (7 8) (9 10))

This is especially nice to iterate over multiple items at the same time. It grabs the number of items that you specify and supplies them as a single List:

这对于同时迭代多个项尤其有用。它会抓取你指定的项数并将它们作为单个列表提供:

my $list = 1 .. 10;
for $list.rotor: 3 {
    .say
}

By default it only grabs exactly the number you specify. If there aren’t enough elements it doesn’t give you a partial List. This output is missing 10:

默认情况下,它只捕获你指定的数字。如果没有足够的元素,它不会给你一个部分列表。此输出没有 10

(1 2 3)
(4 5 6)
(7 8 9)

If you want a short sublist at the end, the :partial adverb will do that:

如果你想在结尾处有一个简短的子列表,那么 :partial 副词将会这样做:

my $list = 1 .. 10;
for $list.rotor: 3, :partial {
    .say
}

Now there’s a short list in the last iteration:

现在在最后一次迭代中有一个短的列表:

(1 2 3)
(4 5 6)
(7 8 9)
(10)

EXERCISE 6.11Use lines and .rotor to read chunks of three lines from input. Output the middle line in each chunk.

练习6.11 使用 lines.rotor 从输入中读取三行的块。输出每个块的中间行。

Combining Lists

Making and manipulating Positionals is only the first level of your programming skill. Raku has several facilities to manage, combine, and process multiple Positional things together.

制作和操作 Positional 只是编程技巧的第一级。 Raku 有几个工具来管理,组合和处理多个 Positional 事物。

The Zip Operator, Z

The Z operator takes elements from the same positions in the lists you provide and creates sublists from them:

Z 运算符从你提供的列表中的相同位置获取元素,并从中创建子列表:

say <1 2 3> Z <a b c>;  # ((1 a) (2 b) (3 c))

When it reaches the end of the shortest list, it stops. It doesn’t matter which list is shorter:

当它到达最短列表的末尾时,它会停止。哪个列表更短并不重要:

say <1 2 3> Z <a b>;  # ((1 a) (2 b))

say <1 2> Z <a b c>;  # ((1 a) (2 b))

The zip routine does the same thing:

zip 例程做同样的事情:

say zip( <1 2 3>, <a b> );  # ((1 a) (2 b))

This one gives the same output because $letters doesn’t have enough elements to make more sublists:

这个给出相同的输出,因为 $letters 没有足够的元素来制作更多的子列表:

my $numbers = ( 1 .. 10 );
my $letters = ( 'a' .. 'c' );

say @$numbers Z @$letters; # ((1 a) (2 b) (3 c))

You can do it with more than two lists:

你可以使用两个以上的列表:

my $numbers = ( 1 .. 3 );
my $letters = ( 'a' .. 'c' );
my $animals = < 🐈 🐇 🐀 >; # cat rabbit rat
say @$numbers Z @$letters Z @$animals;

Each sublist has three elements:

每个子列表都有三个元素:

((1 a 🐈)(2 b 🐇)(3 c 🐀))

zip does the same thing as Z:

zipZ 做同样的事情:

say zip @$numbers, @$letters, @$animals;

You can use it with for:

你可以将它和 for 一块使用:

for zip @$numbers, @$letters, @$animals {
    .say;
}


(1 a 🐈)
(2 b 🐇)
(3 c 🐀)

EXERCISE 6.12Use the Z operator to make an Array of Lists that pair each letter with its position in the alphabet.

练习6.12 使用 Z 运算符创建一个列表数组,将每个字母与其在字母表中的位置配对。

The Cross Operator, X

The X cross operator combines every element of one Positional with every element of another:

X 交叉运算符将一个 Positional 的每个元素与另一个 Positional 的每个元素组合在一起:

my @letters = <A B C>;
my @digits  = 1, 2, 3;

my @crossed = @letters X @digits;
say @crossed;

The output shows that every letter was paired with every number:

输出显示每个字母都与每个数字配对:

[(A 1) (A 2) (A 3) (B 1) (B 2) (B 3) (C 1) (C 2) (C 3)]

EXERCISE 6.13 A deck of 52 playing cards has four suits, ♣ ♡ ♠ ♢, each with 13 cards, 2 to 10, jack, queen, king, and ace. Use the cross operator to make a List of Lists that represents each card. Output the list of cards so all the cards of one suit show up on the same line.

练习6.13 一副 52 张的扑克牌有四套花色,♣♡♠♢,每套有 13 张纸牌,2 到 10,J,Q,K,小王和大王。使用交叉运算符创建代表每张纸牌的列表列表。输出纸牌的列表,以便一套花色的所有纸牌显示在同一条线上。

The Hyperoperators

Instead of combining Positionals, you can operate on pairs of them to create a List of the results. The hyperoperators can do that. Surround the + operator with <<>>. This numerically adds the first element of @right to the first element of @left. The result of that addition becomes the first element of the result. This happens for the second elements, then the third, and so on:

你可以对其中的一对进行操作,而不是组合 Positional,以创建结果列表。 hyperoperators 可以做到这一点。使用 <<>> 包围 + 运算符。这在数字上将 @right 的第一个元素加到 @left 的第一个元素中。相加的结果成为结果的第一个元素。这发生在第二个元素上,然后是第三个元素上,依此类推:

my @right = 1, 2, 3;
my @left  = 5, 9, 4;

say @left <<+>> @right;  # [6 11 7]

Pick a different operator and follow the same process. The concatenation operator joins the Str versions of each element:

选择一个不同的运算符并按照相同的过程。连接运算符连接每个元素的字符串版本:

my @right = 1, 2, 3;
my @left  = 5, 9, 4;

say @left <<~>> @right;  # [51 92 43]

If one of the sides has fewer elements the <<>> hyper recycles elements from the shorter one. It doesn’t matter which side the shorter list is on. Here, @left has fewer elements. When it’s time to operate on the third elements the hyper starts at the beginning of @left again to reuse 11:

如果其中一边具有较少的元素,则 <<>> hyper 会从较短的元素中循环该元素。短列表的哪一方无关紧要。在这里,@left 有更少的元素。当是时候对第三个元素进行操作时,hyper 会在 @left 的开头再次开始重用 11

my @right =  3,  5, 8;
my @left  = 11, 13;

say @left <<+>> @right;  # [14 18 19]
say @right <<+>> @left;  # [14 18 19]

Point the angle brackets toward the inside to insist that both sides have the same number of elements. You’ll get an error when the sizes don’t match:

将尖括号指向内侧以确保两侧具有相同数量的元素。当大小不匹配时,你会收到错误:

say @left >>+<< @right;  # Error!

Another option is to allow one side to be smaller than the other but to not recycle elements. If both sets of angle brackets point away from the shorter side then the hyper does not reuse elements from the shorter side:

另一种选择是允许一侧比另一侧小但不循环元素。如果两组尖括号都指向较短的一侧,那么 hyper 不会重用短边的元素:

my @long  =  3,  5, 8;
my @short = 11, 13;

say @short >>+>> @long;    # [14 18]    no recycling
say @long  >>+>> @short;   # [14 18 19]

say @short <<+<< @long;    # [14 18 19]
say @long  <<+<< @short;   # [14 18]    no recycling

Instead of the double angle brackets you can use the fancier »« versions:

你可以使用更漂亮的 »« 版本代替双角括号:

my @long  =  3,  5, 8;
my @short = 11, 13;

say @short  «+» @long;    # [14 18 19]
say @short  »+« @long;    # Error

say @short  »+» @long;    # [14 18]  no recycling
say @long   »+» @short;   # [14 18 19]

say @short  «+« @long;    # [14 18 19]
say @long   «+« @short;   # [14 18]  no recycling

The Reduction Operator

The reduction operator is a bit different from Z, X, or the hyperoperators. It turns a Positional into a single value by operating on two elements at a time to turn them into one element.

The prefix [] is the reduction operator. On the inside you put a binary operator. It applies that operator to the first two elements of its Positional to get a single value. It replaces those two values with the result; this makes the input one element shorter. It keeps doing this until there’s one element left. That’s the final value.

Here’s a quick way to sum some numbers:

简化运算符与 ZX 或超运算符略有不同。它通过一次操作两个元素将 Positional 转换为单个值,将它们转换为单个值。

前缀 [] 是简化运算符。在里面你放了一个二元运算符。它将该运算符应用于其 Positional 的前两个元素以获取单个值。它用结果替换这两个值;这使得输入一个元素更短。它一直这样做,直到剩下一个元素。这是最终值。

这是一个快速的方法来合计一些数字:

my $sum = [+] 1 .. 10;  # 55

This is the same as this expression if you write out the steps:

如果你写出以下步骤,则与此表达式相同:

(((((((((1 + 2) + 3) + 4) + 5) + 6) + 7) + 8) + 9) + 10)

And to do a factorial:

计算阶乘:

my $factorial = [*] 1 .. 10;  # 3628800

Are all the values True? Apply the && to the first two elements and replace them with the result until there’s one element left. At the end use the ? ( or .so) to coerce the result to a Boolean:

所有的值都是 True 吗?将 && 应用于前两个元素并将其替换为结果,直到剩下一个元素。最后使用 ? (或 .so )将结果强制转换为布尔值:

my $condition = ?( [&&] 1 .. 10 );  # True
my $condition = ?( [&&] ^10 );      # False

There’s a binary max operator too:

还有一个二元的 max 运算符:

my $max = 1 max 137; # 137;

You can put that inside the brackets. This makes one pass through the elements to discover the largest numeric value:

你可以把它放在方括号内。这使得一个遍历元素以发现最大的数值:

my $max = [max] @numbers

If you want to use your own subroutine, use an extra set of braces and the & sigil to make it look like an operator:

如果你想使用自己的子程序,请使用额外的方括号和 & sigil使其看起来像一个运算符:

sub longest {
    $^a.chars > $^b.chars ?? $^a !! $^b;
}

my $longest =
    [[&longest]] <Hamadryas Rhamma Asterocampa Tanaecia>;

put "Longest is $longest"; # Longest is Asterocampa

That trick works to convert a subroutine to a binary operator:

该技巧可以将子例程转换为二元运算符:

$first [&longest] $second

Filtering Lists

The .grep method filters a Positional to get the elements that satisfy your condition. Any element that satisfies the condition becomes part of the new Seq:

.grep 方法过滤 Positional 以获取满足条件的元素。满足条件的任何元素都将成为新 Seq 的一部分:

my $evens = (0..10).grep: * %% 2;  # (0 2 4 6 8 10)

A Block works too. The current element shows up in $_:

Block 也有效。当前元素出现在 $_中:

my $evens = (0..10).grep: { $_ %% 2 };  # (0 2 4 6 8 10)

If your condition is only a type .grep smart matches the current element against that type:

如果你的条件只是一种类型 .grep 将当前元素与该类型智能匹配:

my $allomorphs = <137 2i 3/4 a b>;
my $int-strs = $allomorphs.grep: IntStr;      # (137)
my $rat-strs = $allomorphs.grep: RatStr;      # (3/4)
my $img-strs = $allomorphs.grep: ComplexStr;  # (2i)
my $strs     = $allomorphs.grep:  Str;        # (a b)

Remember that a smart match against a type includes matching anything that type is based on. Trying to get all the Strs finds everything since the <> creates allomorphs and every element matches Str:

请记住,针对类型的智能匹配包括匹配基于类型的任何内容。试图得到所有的字符串会找出所有的东西,因为 <> 创建了同质异形并且每个元素都匹配字符串

my $everything = $allomorphs.grep: Str; # (1 2i 3/4 a b)

The .does method checks if the element has a role. Here, you want the elements that don’t do that role—if it can be a number, you don’t want it:

.does 方法检查元素是否具有角色。在这里,你需要该元素不执行该角色 - 如果它可以是数字,则你不需要它:

my $just-str = $allomorphs.grep: { ! .does(Numeric) };  # (a b)

You can specify some adverbs with .grep. The :v adverb (for “values”) gives the same list you get without it:

你可以使用 .grep 指定一些副词。 :v 副词(对于“值”)给出了没有它的相同列表:

my $int-strs = $allomorphs.grep: IntStr, :v;  # same thing

The :k adverb (for key) gives the positions of the matching elements. This returns 1 because that’s the index of the matching element:

:k 副词(用于键)给出匹配元素的位置。这返回 1,因为这是匹配元素的索引:

my $int-strs = $allomorphs.grep: ComplexStr, :k;  # (1)

You can get both the key and the value with :kv. You get a flat List in key-value order:

你可以使用 :kv 获取键和值。你可以按键值顺序获得一个平面列表

my $int-strs = $allomorphs.grep: RatStr, :kv;  # (2 3/4)

If multiple elements match you get a longer Seq. The even positions are still keys:

如果多个元素匹配,则获得更长的 Seq。偶数位置仍是键:

$allomorphs.grep: { ! .does(Numeric) }, :kv;  # (3 a 4 b)

There’s also a routine form of grep. The Positional comes after the matcher:

还有一种例程形式的 grepPositional 在匹配器之后:

my $matched = grep IntStr, @$allomorphs;

Transforming a List

.map creates a new Seq based on an existing one by creating zero or more elements from each input element. Here’s an example that returns a Seq of squares. .map can take a Block or WhateverCode (although that’s a lot of *s):

.map通过从每个输入元素创建零个或多个元素,基于现有的 Seq 创建新的 Seq。这是一个返回平方的 Seq 的示例。 .map 可以接收一个 BlockWhateverCode(虽然那有很多 * 号 ):

my $squares = (1..5).map: { $_ ** 2 }; # (0 1 4 9 16 25)
my $squares = (1..5).map: * ** 2;

There’s a routine version of map that does the same :

有一个例程版本的 map 做同样的事情:

my $even-squares = map { $_ ** 2 }, @(1..5);

Perhaps you want to lowercase everything:

也许你想要将字符串都变成小写:

my $lowered = $words.map: *.lc;

You might return no output elements, but you can’t merely return the empty List because it will show up as an element in the new Seq. In this example the |() indicates an empty List slipped into the bigger List:

你可能不返回任何输出元素,但你不能仅返回空列表,因为它将显示为新列表中的元素。在这个例子中,|() 表示一个空列表滑入更大的列表

my $even-squares = (0..9).map: { $_ %% 2 ?? $_**2 !! |()  }; # (0 4 16 36 64)

You can use these methods together. This selects the even numbers then squares them:

你可以一起使用这些方法。这将选择偶数然后将它们平方:

my $squares = $allomorphs
    .grep( { ! .does(Numeric) } )
    .map(  { $_ %% 2 ?? $_**2 !! |()  } );

Sorting Lists

Often you want a list in some order. Perhaps that’s increasing numerical or alphabetic order, by the length of the Strs, or anything else that makes sense for you. You can do this with .sort:

通常你想要一个按顺序排列的列表。也许以数字递增或字母顺序,按字符串的长度,或任何其他对你有意义的顺序。你可以使用 .sort 执行此操作:

my $sorted = ( 7, 5, 9, 3, 2 ).sort;   # (2 3 5 7 9)

my $sorted = <p e r l 6>.sort;         # (6 e l p r)

By default, .sort compares each pair of elements with cmp. If the two elements are numbers, it compares them as numbers. If it thinks they are Strs, it compares them as such. Here’s a Str comparison that may surprise you the first time you see it (and annoy you hereafter):

默认情况下,.sortcmp 比较每对元素。如果这两个元素是数字,则将它们作为数字进行比较。如果它认为它们是字符串,那么就比较它们。这是一个字符串比较,你可能会在第一次看到它时感到惊讶(并在此后惹恼你):

my $sorted = qw/1 11 10 101/.sort;     # (1 10 101 11)

What happened? Since you constructed the list with qw, you got a list of Str objects. These compare character by character, so the text 101 is “less than” the text 11. This isn’t dictionary sorting, though. Try it with upper- and lowercase letters:

发生了什么?由于你使用 qw 构建了列表,因此你获得了字符串对象的列表。这些字符逐字符比较,因此文本101 “小于”文本 11。但这不是字典排序。尝试用大写和小写字母:

my $sorted = qw/a A b B c C/.sort;

Did you get what you expected? Some of you probably guessed incorrectly. The lowercase code points come after the uppercase ones, for they are greater:

你得到了你的期望吗?你们中的一些人可能猜错了。小写代码点位于大写代码之后,因为它们更大:

(A B C a b c)

cmp sorts by the code number in the Universal Character Set (UCS). If you are used to ASCII, the code number is the same thing. Above ASCII, you may not get what you expect.

cmp 按通用字符集(UCS)中的代码编号排序。如果你习惯使用 ASCII,则代码编号是相同的东西。在 ASCII 之上,你可能无法得到你期望的结果。

NOTE

There is a .collate method that can handle Unicode collation for language-specific sorting, but it’s experimental.

You can tell .sort how to compare the elements. For dictionary order (so case does not matter), you have to do a bit more work. The .sort method can take a routine that decides how to sort. Start with the default .sortfully written out with its comparison:

有一种 .collate 方法可以处理特定于语言的排序的 Unicode 排序规则,但它是实验性的。

你可以告诉 .sort 如何比较元素。对于字典顺序(所以大小写无关紧要),你必须做更多的工作。 .sort 方法可以采用一个例程来决定如何排序。从默认的 .sort 完整写出来与其比较开始:

my $sorted = qw/a A b B c C/.sort: { $^a cmp $^b }

You can also write that with two Whatevers so you don’t have to type the braces. This is the same thing:

你也可以使用两个 Whatever 来编写它,这样你就不必键入花括号。这是一样的:

my $sorted = qw/a A b B c C/.sort: * cmp *

If you want to compare them case insensitively, you can call the .fc method to do a proper case folding:

如果你想不区分大小写的比较它们,你可以调用 .fc 方法进行正确的大小写折叠:

my $sorted = qw/a A b B c C/.sort: *.fc cmp *.fc

Now you get the order that ignores case:

现在你得到忽略大小写的顺序:

(A a B b C c)

However, if you want to make the same transformation on both elements, you don’t need to write it twice; .sort will figure it out. It saves the result and reuses it for all comparisons. This means that Raku has a builtinSchwartzian transform (a Perl 5 idiom for a cached-key sort)!

但是,如果要对两个元素进行相同的转换,则不需要将其写两次; .sort 会弄清楚的。它保存结果并重复使用它进行所有比较。这意味着 Raku 具有内置的 Schwartzian 变换(用于缓存按键排序的 Perl 5 惯用法)!

my $sorted = qw/a A b B c C/.sort: *.fc;

There’s a problem with cmp, though. The order of elements you get depends on the type and order of elements in your input:

但是 cmp 存在问题。你获得的元素的顺序取决于输入中元素的类型和顺序:

for ^5 {
    my @numbers = (1, 2, 11, '21', 111, 213, '7', 77).pick: *;
    say @numbers.sort;
}

The .pick method randomly chooses from the List the number of elements you specify. The * translates to the number of elements in the List. The effect is a shuffled List of the same elements. Some of these are Ints and some are Strs. Depending on which element shows up where, they sort differently:

.pick 方法从列表中随机选择你指定的元素数。 * 转换为列表中的元素数。效果是相同元素的混乱列表。其中一些是整数,一些是字符串。根据哪个元素显示在哪里,它们的排序方式不同:

(1 2 11 111 21 77 213 7)
(1 2 11 111 21 213 7 77)
(1 2 11 21 77 111 213 7)
(1 2 11 111 21 7 77 213)
(1 2 11 21 77 111 213 7)

Use leg (less-equal-greater) if you want to order these by their Str values every time:

如果你希望每次按字符串值排序,请使用 leg(小于等于大于):

say @numbers.sort: * leg *;

If you want numbers, use <=>:

如果你想按数字值排序,请使用 <=>

say @numbers.sort: * <=> *;

Alternatively, you can coerce the input to the type you want:

或者,你可以将输入强制转换为所需的类型:

say @numbers.sort: +*;  # numbers

say @numbers.sort: ~*;  # strings

Finally, there’s a routine version of .sort. It has a single-argument form that takes a List and a two-argument form that takes a sort routine and a List:

最后,还有 .sort 的例程版本。它有一个单参数形式,它接受一个列表和一个带有排序例程和列表的双参数形式:

my $sorted = sort $list;
my $sorted = sort *.fc, $list;

EXERCISE 6.14Represent a deck of cards as a List of Lists. Create five poker hands of five cards from that. Output the cards in ascending order of their ranks.

练习6.14将一副牌作为列表列表。从中创建五手五张牌。按名次的升序输出卡片。

Sorting on Multiple Comparisons

You can use a Block to create more complicated comparisons, comparing two things that are the same in one regard in another way. When two people’s lasts names are the same, you can sort by the first name. If you sort these with the default .sort you probably won’t get what you want:

你可以使用Block来创建更复杂的比较,以另一种方式比较两个在一个方面相同的事物。当两个人的姓名相同时,你可以按名字排序。如果你使用默认的 .sort 对它们进行排序,你可能无法获得所需内容:

my @butterflies = (
    <John Smith>,
    <Jane Smith>,
    <John Doe>,
    <Jon Smithers>,
    <Jim Schmidt>,
    );

my @sorted = @butterflies.sort;

put @sorted.join: "\n";

This comes out in alphabetical order, if you consider the Str to be the combination of the sublist elements as a single Str:

如果你认为字符串是子列表元素组合为单个字符串,则按字母顺序排列:

Jane Smith
Jim Schmidt
John Doe
John Smith
Jon Smithers

Change the sort to work only on the second element of each sublist:

将排序更改为仅适用于每个子列表的第二个元素:

my @sorted = @butterflies.sort: *.[1];

put @sorted.join: "\n";

The last names sort in alphabetical order now, but the first names show up out of order (though that may depend on the ordering of your input):

姓氏现在按字母顺序排序,但名字显示不按顺序排列(尽管这可能取决于输入的顺序):

John Doe
Jim Schmidt
John Smith
Jane Smith
Jon Smithers

A more complex comparison can fix that. In each sublist, compare the last names to each other. If they are the same, add another comparison with the logical or:

更复杂的比较可以解决这个问题。在每个子列表中,将姓氏相互比较。如果它们相同,则添加另一个与逻辑 or 的比较:

my @sorted = @butterflies.sort: {
    $^a.[1] leg $^b.[1]  # last name
        or
    $^a.[0] leg $^b.[0]  # first name
    };

When it compares the sublists for (John Smith) and (Jane Smith) it tries the last names and finds that they are the same. It then sorts on the first names and produces the result that you probably want:

当它比较(John Smith)和(Jane Smith)的子列表时,它会尝试姓氏并发现它们是相同的。然后它对名字进行排序并产生你可能想要的结果:

John Doe
Jim Schmidt
Jane Smith
John Smith
Jon Smithers

EXERCISE 6.15 Create a deck of cards and create five hands of five cards each. In each hand sort the cards by their rank. If the ranks are the same sort them by their suits.

练习6.15 创建一副牌并创建五手五张牌。每手按照等级对牌进行排序。如果排名与他们的花色相同。

Summary

The List, Range, and Array types are Positionals, and the Seq type can fake it when it needs to. This allows some amazing lazy features where you don’t have to do anything until you actually need it. Not only that, but with a little practice you won’t even need to think about it.

Once you have your data structures, you have some powerful ways to combine them to make much more complex data structures. Some of these may be daunting at first. Don’t ignore them. You’ll find that your programming career will be easier with judiciously chosen structures that are easy to manipulate.

List, Range, 和 Array 类型是 PositionalSeq 类型可以在需要时伪造它。这允许一些惊人的惰性功能,你不需要做任何事情,直到你真正需要它。不仅如此,通过一些练习,你甚至不需要考虑它。

拥有数据结构后,你可以通过一些强大的方法将它们组合在一起,从而构建更复杂的数据结构。其中一些可能一开始令人生畏。不要忽视它们。你会发现,通过易于操作的明智选择的结构,你的编程生涯将变得更加容易。

comments powered by Disqus