第十四章. Junctions 和集合

Junctions and Sets

声明

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

第十四章. Junctions 和集合

Junctions

A Junction is a combination of values that is mostly indistinguishable from a single value. They have their roots in the math of quantum mechanics. You may have heard of Schrödinger’s cat, who is both dead and alive at the same time—an analogy that physicist used to show how ridiculous this all is. Well, the joke was on him.

Junction 是一组值,它们与单个值几乎无法区分。它们根植于于量子力学中的数学。你可能听说过薛定谔的猫,同一个时间它既死又活着 - 这个物理学家用来表示这一切是多么荒谬。那个笑话现在应在 Junction 身上了。

any

The first Junction is the any. This “any” is lowercase and is not related to the type Any. It creates a value that can act like, well, any of the ones you gave it:

第一个 Junctionany。这个“any”是小写的,与 Any 类型无关。它会创建一个值,就像你给它的任何一个一样:

my $first-junction = any( 1, 3, 7 );

You can make a Junction from an Array or any other Positional:

您可以从数组或任何其他 Positional 创建一个 Junction

my $junction = any( @array   );   # Array
my $junction = any( 1 ..  10 );   # Range
my $junction = any( 1 ... 10 );   # Sequence

Now you have a Junction of three values. It will only ever have three values. You can’t take one away or add one. There’s no interface to extract them or count them. You’re not supposed to know—or even care—which values are in there. In fact, Junction is the only builtin type that does not inherit from Any:

现在你有一个三个值的 Junction。它只会有三个值。你不能拿走一个或添加一个。没有接口可以提取它们或计算它们。你不应该知道 - 甚至关心 - 那里有哪些观。事实上, Junction 是唯一不从 Any 继承的内置类型:

% raku
To exit type 'exit' or '^D'
> my $first-junction = any( 1, 3, 7 );
any(1, 3, 7)
> $first-junction.^name
Junction
> $first-junction.^mro
((Junction) (Mu))

These are quite handy in complex conditions. Consider the annoying code you’ve had to write to test if a value is one of three possible numbers:

这些在复杂条件下非常方便。考虑一下你必须编写的令人讨厌的代码,以测试值是否是三个可能的数字之一:

my $n = any( 1, 3, 7 );

if $n == 1 || $n == 3 || $n == 7 {
    put "n is one of those values";
    }

Being clever with a Hash doesn’t actually feel that much more clever:

聪明的 Hash 实际上并没有感觉更聪明:

my Int %hash = map { $_ => True }, (1, 3, 7);
if %hash{$n}:exists {
    put "n is one of those values";
    }

Not only does the Junction equal any of those values, but it also equals all of them. This looks like a Blockthat would never execute, but it does:

Junction 不仅等于这些值中的任何一个,而且它也等于所有这些值。这看起来像一个永远不会执行的 Block,但它确实:

if $n == 1 && $n == 3 && $n == 7 {
    put "n is all of those values";
    }

A Junction is much closer to how you’d probably describe this in speech:

Junction 更接近你可能在演讲中描述的方式:

if $n == any( 1, 3, 7 ) {
    put "n is one of those values";
    }

When you operate on a Junction your code may distribute (autothread) that operation over all of its values to produce a Junction of intermediate results. The first step might look like this:

当您在 Junction 上操作时,您的代码可以在其所有值上分配(自动读取)该操作,以产生中间结果的 Junction。第一步可能如下所示:

if any( 1 == $n, 3 == $n, 7 == $n ) {
    put "n is one of those values";
    }

These evaluate to their Boolean values. If $n is 3 one of the comparisons is True:

这些求值为其布尔值。如果 $n3 ,则其中一个比较为 True

my $n = 3;
if any( False, True, False ) {
    put "n is one of those values";
    }

Any True makes the entire junctive expression True:

任何一个为 True 会使整个正则表达式为 True

my $n = 3;
if True {
    put "n is one of those values";
    }

You don’t have to define the Junction in the condition. It might already be in a variable and ready for use:

您不必在条件中定义 Junction。它可能已经在变量中并且可以使用:

my $any = any( 1, 3, 7 );
if $n == $any {
    put "n is one of those values";
    }

Here’s the beauty of Junctions—you don’t have to know that you are using one. Here’s an Array that has some “normal” values and one that is a Junction:

这是 Junction 的美之所在 - 你不知不觉就在使用它。这是一个具有一些“正常”值的数组,其中一个元素是 Junction

my @array = 5, any( 1, 7 ), 8, 9;
for @array -> $item {
    put "$item was odd" unless $item %% 2;
    }

The loop works with single “normal” values as well as Junctions. Notice that the Junction creates two lines of output. The stringification happened for each value:

在这个循环中,单个“正常”值以及 Junction 都起作用。请注意,Junction 创建了两行输出。每个值都发生了字符串化:

5 was odd
1 was odd
7 was odd
9 was odd

That multiple stringification could change in the future; it wasn’t this way when I started the book and it might change again. The .gist on $item prevents that:

多重字符串化可能会在未来发生变化; 当我开始写这本书时它不是这样的,它可能会再次改变。 $item 上的 .gist 可以防止多重字符串化:

my @array = 5, any( 1, 7 ), 8, 9;
for @array -> $item {
    put "{$item.gist} was odd" unless $item %% 2;
    }

5 was odd
any(1, 7) was odd
9 was odd

EXERCISE 14.1Make an any Junction of the prime numbers between 1 and 10 (so, 2, 3, 5, and 7). Use that Junction to note which numbers from 1 to 10 are prime.

练习14.1使用 1 到 10 之间的素数制作一个 any Junction(因此,2,3,5和7)。使用该 Junction 来记录从1到10的哪些数字是素数。

There’s a symbolic notation for any. The | between values creates a Junction. It looks similar to the || for the logical OR operator but is not related:

any 有一个象征性的符号。 值之间的 | 创建一个 Junction。它看起来类似于 || 对于逻辑OR运算符但不相关:

my $n = 3;
my $any = 1 | 3 | 7;
if $n == $any {
    put "n is one of those values";
    }
NOTE

Raku uses |, &, and ^ to create Junctions. You might be used to these as numeric bit operators in other languages. You’ll find those now are called +|, +^, and +&. That leading + denotes the numeric flavor.

Raku 使用 |^ 来创建 Junction。您可能习惯使用其他语言中的数字位运算符。您会发现现在称为 +|+^+& 。前导 + 表示数字风味。

You can change Junctions by affecting their values. Numerically adding to a Junction adds to every value in it:

您可以通过影响其值来更改 Junction。以数字方式添加到 Junction 会增加其中的每个值:

my $junction = any( 1, 3, 7 );
$junction += 1;
if $junction %% 2 {
    put "{$junction.gist} is even";
    }

The output shows that you were able to add one to each of the values:

输出显示您可以为每个值加一:

any(2, 4, 8) is even

This generally applies to all of the operations, and you can get quite creative with that. What if you add two anyJunctions? Think about this for a minute before you read ahead to the output:

这通常适用于所有操作,您可以从中获得相当的创意。如果你将两个 any Junction 相加会怎样?在继续读到输出之前,请考虑一分钟:

my $any-any = any( 6, 7 ) + any( 9, 11 )
put "Result is $any-any";

Now figure out what this means:

现在弄清楚这意味着什么:

Result is any(any(15, 17), any(16, 18))

That’s an any of anys! Suppose you wanted to check if that value was less than 17. This virtual series of steps finds the answer:

这是一个里面含有 anyany。假设您要检查该值是否小于 17。此虚拟系列步骤可找到答案:

$any-any < 17

any( any(15, 17), any(16, 18) ) < 17

any( any(15, 17) < 17, any(16, 18) < 17 )

any( any(15 < 17, 17 < 17), any(16 < 17, 18 < 17) )

any( any(True,False), any(True, False) )

any( True, True )

True

This has the same effect as any( 15, 16, 17, 18 ) but with more steps involved. That’s a warning. If you aren’t careful you could have an explosion of Junctions in there.

这与任何 any( 15, 16, 17, 18 ) 具有相同的效果,但涉及更多步骤。这是一个警告。如果你不小心,你可能会爆发 Junction

all

An all Junction requires that each of its values satisfy the condition or method you apply:

all Junction 要求其每个值满足你应用的条件或方法:

my $all-of-u = all( <Danaus Bicyclus Amauris> );
if $all-of-u.contains: 'u' {
    put "Everyone has a u";
    }

Perhaps you want to check that all of the values are a particular type. In this example there’s a Str in @mixed-types:

也许您想要检查所有值是否为特定类型。在这个例子中,@mixed-types 中有一个 Str

my @mixed-types = <1 2/3 4+8i Hello>;
if all(@mixed-types) ~~ Numeric {
    put "Every value is a numeric thingy";
    }
else {
    put "One of these things is not like the others";
    }

The Hello cannot become a number, so a smart match against Numeric fails. The entire Junction evaluates to False because one of its values does.

Hello 无法成为数字,因此与 Numeric 的智能匹配失败。整个 Junction 的计算结果为 False,因为其值之一是 False

The all is much easier to read than almost anything else that might accomplish the task. Comparing the result of a .grep to the original number of elements in the source is too much typing:

all 都比其他任何可能完成任务的东西更容易阅读。将 .grep 的结果与源中元素的原始数量进行比较打字太多了:

if @mixed-types.grep( * !~~ Numeric ) == +@mixed-types {
    put "One of these things is not a number";
    }

You can create an all Junction with the &:

您可以使用 & 创建一个 all Junction

my $all-of-u = 'Danaus' & 'Bicyclus' & 'Amauris';
if $all-of-u.contains: 'u' {
    put "Everyone has a u";
    }

EXERCISE 14.2Using all, test if all the numbers you specify on the command line are prime.

练习14.2使用 all,测试你在命令行中指定的所有数字是否为素数。

one

The one Junction allows only one of its values to satisfy its condition. If more than one would make the condition True the Junction fails:

one Junction 只允许其中一个值满足其条件。如果不止一个条件为 True,则 Junction 失败:

put one( 1, 2, 3 ) %% 2 ??     # True
    "Exactly one is even"
        !!
    "More (or less) than one is even";

If more than one thing in the one is True, then the entire Junction is False:

如果 one 中的多个是 True,则整个 JunctionFalse

one( True, True, False ).so    # False;

You can create a one Junction with the ^:

您可以使用 ^ 创建 oneJunction

put ( 1 ^ 2 ^ 3 ) %% 2 ??      # True
    "Exactly one is even"
        !!
    "More (or less) than one is even";

none

The none Junction requires that all of the values cause its condition to be False. That means that everything should evaluate to False. There’s no symbolic operator version for this type:

none Junction 要求所有值都导致其条件为 False。这意味着一切都应该计算为 False。该类型没有符号运算符版本:

put none( 1, 2, 3 ) %% 5 ??      True
    "Exactly one is even"
        !!
    "More (or less) than one is even";

EXERCISE 14.3Use none to test if no numbers you specify on the command line are prime. Once you’ve done that, use noneto test that some numbers in an Array are prime.

练习14.3如果您在命令行中指定的数字不是素数,则使用 none 来测试。完成后,使用 none 测试,数组中的某些数字是素数。

Some Junctive Tricks

Junctions aren’t designed for introspection and you aren’t supposed to care if the value is in a Junction. This isn’t too hard to work around, though.

Junction 不是为内省而设计的,你不应该关心这个值是否在一个 Junction 中。不过,这并不难解决。

You can apply an operation to each value with a hyperoperator (Chapter 6). This one adds one to each element:

您可以使用超运算符对每个值应用操作(第6章)。这个为每个元素加一:

my $junction = any( 1, -3, 7 );
say $junction »+« 1;

The new Junction has new values. You still aren’t supposed to know what these new values are, but something must know what they are to add one to them:

新的 Junction 有新的值。你仍然不应该知道这些新值是什么,但肯定知道它们加一之后是什么:

any(2, -2, 8)

The »+« surrounds the + because that’s an infix operator and expects arguments on either side of it. You can call a method (a postfix thing) on each item:

»+« 包围着 +,因为这是一个中缀运算符,并期望它的任何一边都有参数。你可以在每一项上调用一个方法(一个后缀的东西):

$junction>>.is-prime; # any((True), (False), (False))

That method could be .take, which adds the value to the list that gather makes. This means that the values can escape the Junction:

该方法可以是 .take,它将值添加到 gather 制作的列表中。这意味着值可以避开 Junction

my $junction = any( 1, -3, 7 );
my @values = gather $junction».take;
put "Values are @values[]";

Don’t make a habit of this because it’s slightly naughty. You aren’t supposed to know how to do this.

不要养成这个习惯,因为它有点顽皮。你不应该知道如何做到这一点。

A Junction is handy to allow a combination of types in a type constraint. Use the subset of the Junctionof both types as the constraint:

Junction 可以方便地在类型约束中组合类型。使用两种类型的 Junctionsubset 子集作为约束:

subset IntInf where Int | Inf;
sub add ( IntInf $a, IntInf $b ) { $a + $b }

put add( 1, 3 );   # 4
put add( 1, Inf ); # Inf

EXERCISE 14.4Rewrite the number-guessing game from Chapter 2 to have three secret numbers. This time the hints are a bit trickier. If any of the secret numbers are smaller than the guess, tell the person that one or some of them are smaller. Do the same with larger numbers. For a single guess, some numbers may be larger and others smaller. When the person has guessed all of the secret numbers, end the game. Is it easier to use given-when or if? Using all of the Junction types may make this easier.

练习14.4 重写第2章的猜数游戏,得到三个秘密数字。这次提示有点棘手。如果任何一个秘密数字小于猜测的值,请告诉该人其中一个或一些较小。用更大的数字做同样的事情。对于单个猜测,一些数字可能更大而其他数字更小。当该人猜出所有秘密数字时,结束游戏。是否更容易使用 - given-whenif?使用所有 Junction 类型可以使这更容易。

Table 14-1 provides a summary of Junctions.

Junction Operator Description
any | Any of the values will work.
all & All of the values must work.
one ^ Exactly one of the values will work.
none None of the values can work.

Sets

Sets are another way to combine values. They aren’t like Junctions, where several values can act like one value; they combine zero or more values as its own thingy that you can inspect. Each value can be in the Setonly once (although there are weighted Sets I won’t write about), and once created the Set is fixed.

Set 是另一种组合值的方法。它们不像 Junction,其中几个值可以像一个值一样;它们将零个或多个值组合为您可以检查的自己的东西。每个值都只可以在 Set 中一次(虽然有加权集合我不会写),一旦创建了集合就固定了。

注意

A Set is a type of Associative, so many of the things you already know about those work on Sets.

集合是一种关联类型,因此您已经了解的很多关于集合的内容。

You can create a Set with a routine or a coercer. Each thingy in the List is a member of the Set. These are the same:

您可以使用例程或强制器创建集合列表中的每个东西都是 集合成员。这些都是一样的:

set( 1, 2, 3 )
(1, 2, 3).Set

You can store any combination or mixture of thingys, including type objects:

您可以存储任何组合或混合物,包括类型对象:

set( <♠ ♣ ♥ ♦> )
set( Int, 3, Inf, 'Hamadryas', $(1,2,3) )

A Set stores a thingy only once. It’s either in the Set or it isn’t, so it doesn’t need duplicates:

集合只存储一次东西。它可以在集合中,也可以不在,因此不需要重复:

put set( 1, 2, 3 ).elems;          # 3
put set( 1, 2, 2, 3, 3, 3 ).elems; # 3

You can check that a value is in the Set with the (elem) operator:

您可以使用(elem)运算符检查集合中的值:

my $set = <♠ ♣ ♥ ♦>.Set;
put 'Number is in the set' if '♥' (elem) $set;

There’s also the fancy Unicode operator that tests if the thingy is in the set (or is a “member” of the set):

还有花哨的 Unicode 运算符,它测试东西是否在集合中(或者是集合的“成员”):

put 'Number is in the set' if '♥' ∈ $set;

These operators know that they need Sets, so they coerce what you give them:

这些运算符知道他们需要集合,所以他们强迫你给他们的东西:

put 'Number is in the set' if '♥' ∈ <♠ ♣ ♥ ♦>;

With that operator the Set is the second operand. The order of operands is reversed for the (cont) and operators. Now you test that a Set contains an element:

使用该运算符,集合是第二个操作数。对于(cont) 运算符,操作数的顺序是相反的。现在,你测试一个集合包含一个元素:

put 'Number is in the set' if $set (cont) '♥';
put 'Number is in the set' if $set ∋ '♥';

You can test that a thingy is not a member of a Set by either prefacing the ASCII operator with a ! or using the Unicode version with the line through it:

你可以通过在 ASCII 操作符前面添加一个 ! 来测试一个东西不是一个集合的成员!或者使用带有直线穿过的Unicode 版本:

put 'Number is not in the set' if '♥' !(elem) $set;
put 'Number is not in the set' if '♥' ∉ $set;
put 'Number is not in the set' if $set !(cont) '♥';
put 'Number is not in the set' if $set ∌ '♥';

You can compare Sets to other Sets. Another Set that contains only some of the members is a subset. A “strict” or “proper” subset is one that is smaller than the Set and only contains elements of the Set. Another way to say that is a proper subset is always smaller. The (<) (or ) operator does that with the opening of the angle toward the larger Set. The order of elements does not matter:

您可以将集合与其他集合进行比较。另一个仅包含部分成员的集合是子集。 “严格”或“适当”子集是小于集合的子集,仅包含集合的元素。另一种说法是适当的子集总是更小。 (<)(或 )运算符通过朝向较大的集合开放角度来执行此操作。元素的顺序无关紧要:

set( 1, 3  )    (<) set( 1, 3, 7 );  # True
set( 3, 1, 7 )  (<) set( 1, 3  );    # False (not smaller)
set( 5, 7 )     ⊂  set( 1, 3, 7 );   # False (5 not in set)

A ! in front of the ASCII operator or a line through the Unicode operator negates the condition:

ASCII 运算符前面的 ! 或通过带有直线穿过的 Unicode 运算符否定条件:

set( 1, 3  )   !(<)   set( 1, 3, 7 );  # False
set( 3, 1, 7 ) !(<)   set( 1, 3  );    # True
set( 5, 7 )     ⊄    set( 1, 3, 7 );  # True

Use the (>=) or operators if you want to allow the Sets to be the same size:

如果要允许集合具有相同的大小,请使用((>=) 运算符:

set( 1, 3  )   (<=)  set( 1, 3, 7 );  # True
set( 1, 3, 7 ) (<=)  set( 1, 3, 7 );  # True
set( 3, 1, 7 )  ⊆   set( 1, 3  );    # False (subset has 7)

Negate those in the same way:

以同样的方式否定这些:

set( 1, 3  )   !(<=)  set( 1, 3, 7 );  # False
set( 1, 3, 7 ) !(<=)  set( 1, 3, 7 );  # False
set( 3, 1, 7 )   ⊈   set( 1, 3  );    # True

You can also have supersets. That’s just a matter of which one you allow to be the larger Set. So far you’ve seen examples where you expected the larger Set to be to the right of the operator. Flip those operators around so you expect the larger Set to be on the left:

你也可以有超集。这只是一个你允许成为更大的集合的问题。到目前为止,您已经看到过您希望较大的集合位于运算符右侧的示例。翻转这些运算符,以便您希望较大的集合位于左侧:

set( 3, 1, 7 )  (>) set( 1, 3  );    # False (not smaller)
set( 3, 1, 7 )  ⊃   set( 1, 3  );    # False (not smaller)

set( 3, 1, 7 ) !(>)   set( 1, 3  );    # True
set( 3, 1, 7 )  ⊅    set( 1, 3  );    # True

Table 14-2 shows the rest of the Set operations.

Operation Operator Code number Description
$a (elem) $set U+2208 $a is a member of $set
$a !(elem) $set U+2209 $a is not a member of $set
$set (cont) $a U+220B $set contains $a
$set !(cont) $a U+220C $set does not contain $a
$set-a (<) $set-b U+2282 $set-a is a proper subset of $set-b
$set-a !(<) $set-b! U+2284 $set-a is not a proper subset of $set-b
$set-a (<=) $set-b! U+2286 $set-a is the same or is a subset of $set-b
$set-a !(<=) $set-b! U+2288 $set-a is not the same and isn’t a subset of $set-b
$set-a (>) $set-b! U+2283 $set-a is a proper superset of $set-b
$set-a !(>) $set-b! U+2285 $set-a is not a proper superset of $set-b
$set-a (>=) $set-b! U+2287 $set-a is the same or is a superset of $set-b
$set-a !(>=) $set-b! U+2289 $set-a is not the same and isn’t a superset of $set-b

EXERCISE 14.5In Chapter 9 you used a Map to check allowed values. Do the same thing with a List. Prompt for some starting colors (perhaps all on one line that you break up into elements). Continue to prompt for colors and report if the color was one of the initial colors. Can you do this ignoring case?

练习14.5 在第9章中,您使用了 Map 来检查允许的值。用列表做同样的事情。提示一些起始颜色(可能在一行中分解为元素)。继续提示颜色并报告颜色是否为初始颜色之一。你能忽略这个案子吗?

Set Operations

You can operate on two Sets to create new Sets. A union is a combination of two Sets. Each element still shows up only once:

您可以操作两个集合来创建新集合。并集是两个集合的组合。每个元素仍然只显示一次:

set(1,2) (|) set(3,7);  # set(1 2 3 7)
set(1,2)  ∪  set(3,7);  # set(1 2 3 7)

The intersection makes the Set of the elements they have in common:

交集制作一个它们共有的元素集合

set(1,3) (&) set(3,7);  # set(3)
set(1,2)  ∩  set(3,7);  # set()

The set difference creates a Set made up of the elements from the first Set that aren’t in the second. The isn’t the ASCII backslash; it’s (U+2216 SET MINUS):

差集创建一个由第一个集合中不在第二个集合中的元素组成的集合 不是 ASCII 反斜杠; 它是(U+2216 SET MINUS):

set( <a b> ) (-) set( <b c> );  # set(a)
set( <A b> )  ∖  set( <x y> );  # set(A b)

The symmetric set difference does a similar thing in both directions. It creates a Set containing all the elements of either Set that don’t show up in the other:

对称差集在两个方向上做类似的事情。它创建一个集合,其中包含任何集合中未显示在另一个集合中的所有元素:

set( <a b> ) (^) set( <b c> );  # set(a c)
set( <A b> )  ⊖  set( <x y> );  # set(A b x y)

These operations are summarized in Table 14-3.

Operation Operator Code number Description
(|) U+222A Union (combination)
(&) U+2229 Intersection (overlap)
(-) U+2216 Set difference
(^) U+2296 Symmetric set difference

EXERCISE 14.6Create two Sets of 10 numbers between 1 and 50. Find their intersection and union.

练习14.6在1到50之间创建两组10个数字。找到它们的交集和联合。

Summary

Junctions make several values pretend to be a single value, in such a way that you can’t tell which value it is or how many values there are. You create the Junction in a way that makes the values all work together or separately. A Set also combines values but lets you look inside to see what those values are. You can combine Sets in various ways to create new ones. This is handy to tell what’s in, out, or common.

Junction使多个值假装成单个值,这样你就无法判断它是哪个值或者有多少个值。您可以通过使值全部一起或单独工作的方式创建 Junction集合还可以组合值,但是让您查看这些值是什么。您可以通过各种方式组合集合来创建新的集合。这可以很方便地分辨出进出的内容。

comments powered by Disqus