Perl 6 的 Setty 和 Baggy 类型

有一个很常见的计数场景。比如说计算 DNA 中各个碱基的个数:

my %counts;
%counts{$_}++ for 'AGTCAGTCAGTCTTTCCCAAAAT'.comb;
say %counts<A T G C>;  # (7 7 3 6)

创建一个哈希。对于每一个你想计数的东西,
每遇到一次就在那个哈希中加 1。所以有什么问题?

Perl 6 通常有特定的更合适的类型来做这种操作; 例如,Bag 类型:

'AGTCAGTCAGTCTTTCCCAAAAT'.comb.Bag<A T G C>.say; # (7 7 3 6)

我们来说说这些类型还有那些时髦的运算符!

注意 Unicode

我将在这篇文章中使用花哨的 Unicode 版本的运算符和符号,因为它们看起来很纯。 然而, 他们都有我们称之为Texas的等同物, 你可以改用它们。

准备. Set. 走起

这些类型中最简单的就是 Set
它将仅保存每个项目之一, 因此如果您有多个相同的对象, 那么重复项将被丢弃:

say set 1, 2, 2, "foo", "a", "a", "a", "a", "b";
# OUTPUT: set(a, foo, b, 1, 2)

集合运算符是强制的, 因此我们不需要显式地创建集合; 他们会为我们做:

say 'Weeee \o/' if 'Zoffix' ∈ <babydrop iBakeCake Zoffix viki>;
# OUTPUT: Weeee \o/

但在使用变形
时要注意:

say 'Weeee \o/' if 42 ∈ <1 42 72>;
# No output

say 'Weeee \o/' if 42 ∈ +«<1 42 72>; # coerce allomorphs to Numeric
# OUTPUT: Weeee \o/

尖括号为数字创建了变形,因此在上面的第一种情况下, 我们的集合包含一堆IntStr 对象,而运算符的左侧有一个常规 Int,因此比较失败。 在第二种情况下, 我们使用超运算符强制变形到它们的数字组件,并且测试成功。

虽然测试成员很令人兴奋, 但是我们可以使用集合做更多的事情!
做集合的交集怎么样?

my $admins = set <Zoffix mst [Coke] lizmat>;
my $live-in-North-America = set <Zoffix [Coke] TimToady huggable>;

my $North-American-admins = $admins ∩ $live-in-North-America;
say $North-American-admins;
# OUTPUT: set(Zoffix, [Coke])

我们用 ∩, U+2229 INTERSECTION, 交集运算符相交两个集合,
并且接收到一个集合, 其仅包含在两个原始集合中都存在的元素。
您还可以链接这些操作, 因此将在链中提供的所有集合中检查成员资格:

say <Zoffix lizmat> ∩ <huggable Zoffix> ∩ <TimToady huggable Zoffix>;
# OUTPUT: set(Zoffix)

另外一个集合运算符是集合差集运算符, 它的 Unicode 外观在我看来有点讨厌: ∖
。不,它不是反斜线(\), 而是一个 U+2216 SET MINUS 符(幸运的是,你可以使用更明显的 (-)) Texas 版本)。

差集运算符的才华弥补了它不算高的颜值:

my @spammers = <spammety@sam.com spam@in-a-can.com yum@spam.com>;
my @senders  = <perl6@perl6.org spammety@sam.com good@guy.com>;

for keys @senders  ∖  @spammers -> $non-spammer {
    say "Message from non-spammer";
}

输出.

Message from perl6@perl6.org
Message from good@guy.com

我们定义了两个数组: 一个包含了一组垃圾邮件发送者的邮件地址,
另外一个包含了一组邮件发送者。怎么得到一组不含垃圾邮件发送者的邮件发送者呢?
那么使用 ∖ ((-)也可以)运算符好了。

然后我们使用 for 循环来迭代结果, 正如你看到的结果一样,
所有的垃圾邮件发送者都被忽略了…​ 但是那里为什么使用 keys 呢?

原因是在那些键拥有键和值的场景中, Setty 和 Mixy 类型很像哈希。
集合类型总是把 True 作为值, 因为我们不需要在我们的循环中迭代
Pair 对象, 所以我们仅仅使用 keys 来获取这个集合的键: 即电子邮件地址。

但是, 类哈希语义在集合上不是无用的。 例如, 我们可以取一个切片, 并使用
:k 副词只返回集合包含的元素:

my $meows = set <
   Abyssinian  Aegean  Manx   Siamese  Siberian  Snowshoe
   Sokoke      Sphynx  Suphalak  Thai
>;
say $meows<Sphynx  Raas  Ragamuffin  Thai>:k;

输出.

(Sphynx Thai)

但是如果我们尝试从集合中删除一项会发生什么?

say $meows<Siamese>:delete;

输出.

Cannot call 'DELETE-KEY' on an immutable 'Set'
  in block <unit> at <unknown file> line 1

我们删除不了! 集合类型是不可变的。然而, 就像Map类型 拥有一个可变版本的
Hash 那样, Set类型 也拥有一个可变的版本:SetHash。我们没有一个好用的助手子程序来创建一个SetHash, 所以我们使用构造函数代替:

my $s = SetHash.new: <a a a b c d>;
say $s;
$s<a d>:delete;
say $s;

输出.

SetHash.new(a, c, b, d)
SetHash.new(c, b)

对头! 我们成功地删除了一个切片。 那么, 圣诞老人的包里还有什么好东西?

Gag ’em ‘n’ Bag ’em

跟集合相关的另一种类型是 Bag, 是的,它也是不变的, 相应的可变类型是
BagHash。我们在本文开始时已经看到, 我们可以使用 Bag 来计算东西, 就像集合那样, Bag 也是哈希式的, 这就是为什么我们可以看到四个 DNA 氨基酸的一个切片:

'AGTCAGTCAGTCTTTCCCAAAAT'.comb.Bag<A T G C>.say; # (7 7 3 6)

虽然集合的所有键值设置为 True, 但是 Bag 的键值是整数权重。
如果你把两件相同的东西放到 Bag 里, 那么它们只有一个键, 但是键值为2:

my $recipe = bag 'egg', 'egg', 'cup of milk', 'cup of flour';
say $recipe;

输出.

bag(cup of flour, egg(2), cup of milk)

当然, 我们可以使用我们的灵巧的运算符来组合 bags! 这里, 我们会使用 ⊎,
U+228E MULTISET UNION 运算符, 它的 Texas 版本 (+) 看起来更清楚:

my $pancakes = bag 'egg', 'egg', 'cup of milk', 'cup of flour';
my $omelette = bag 'egg', 'egg',  'egg', 'cup of milk';

my $shopping-bag = $pancakes ⊎ $omelette ⊎ <gum  chocolate>;
say $shopping-bag;

输出.

bag(gum, cup of flour, egg(5), cup of milk(2), chocolate)

我们使用了两个 Bags 加上一个含有俩个项的列表, 它会为我们正确地进行强转,
所以我们不需要做任何事情。

一个更令人印象深刻的运算符是 ≼, U+227C PRECEDES OR EQUAL TO, 它是 ≽,
U+227D SUCCEEDS OR EQUAL TO 的镜像, 它告诉我们该运算符窄边上的 Baggy
是否是另一边 Baggy 的子集; 意味着较小的 Baggy 中的所有对象都存在于较大的那个之中, 并且它们的权重最大。

这里有一个挑战:我们有一些材料和一些我们想要构建的东西。 问题是,
我们没有足够的材料来构建所有的东西, 所以我们想知道的是我们可以构建的东西的组合。让我们使用一些包!

my $materials = bag 'wood' xx 300, 'glass' xx 100, 'brick' xx 3000;
my @wanted =
    bag('wood' xx 200, 'glass' xx 50, 'brick' xx 3000) but 'house',
    bag('wood' xx 100, 'glass' xx 50)                  but 'shed',
    bag('wood' xx 50)                                  but 'dog-house';

say 'We can build...';
.put for @wanted.combinations.grep: { $materials ≽ [⊎] |$^stuff-we-want };

输出.

We can build...

house
shed
dog-house
house shed
house dog-house
shed dog-house

$materials 是一个含有我们的材料的 Bag。我们使用 xx重复运算符 来指定每种材料的数量。然后我们有一个含有三个 Bags 的 @wanted 数组: 它是我们想要构建的东西。我们还在它们身上使用了 but 运算符为它们混合进名字以覆盖那些 bags 会最后输出。

现在有趣的部分!我们在我们想要的东西的列表上调用 .combinations, 正如名字所示, 我们得到了所有可能的东西的组合。然后, 我们 .grep 结果, 寻找任何组合, 最多需要我们拥有的所有材料(这是≽运算符)。在它的末尾, 我们有我们的 $material Bag 在它较窄的一端, 我们有 ⊎ 运算符, 把我们想要的东西的每个组合的 bags 添加到一块, 除了我们将它作为元操作符[⊎], 这是就像把运算符放在 $^stuff-we-want 的每个项目之间。如果你是新手:那么 $^stuff-we-want 上的 $^twigil 在我们的 .grep 块上创建一个隐式签名, 我们可以给这个变量任意命名。

我们做到了!程序的输出显示我们可以构建任何东西的组合,
除了包含所有三个项目之外。我想我们不能拥有这一切…​

…可是等等!还有更多!

混合起来

让我们回顾一下我们的配方代码。 有一些东西不是很完美:

my $recipe = bag  'egg', 'egg', 'cup of milk', 'cup of flour';
say $recipe';

输出.

bag(cup of flour, egg(2), cup of milk)

如果食谱要求半杯牛奶而不是整杯牛奶怎么办? 如果 Bag 只能有整数权重,
那么我们如何表示四分之一茶匙的盐?

答案是 Mix类型(具有相应的可变版本, MixHash)。 与 Bag 不同, Mix
支持所有 Real 权重, 包括负数权重。 因此, 我们的食谱最好用混合模型。

my $recipe = Mix.new-from-pairs:  'egg'          => 2, 'cup of milk' => ½,
                                  'cup of flour' => ¾, 'salt'        => ¼;
say $recipe;

输出.

mix(salt(0.25), cup of flour(0.75), egg(2), cup of milk(0.5))

一定要用引号引起你的键, 不要使用 colonpair 形式(:42a, 或 :a(42)),
因为那些被视为命名参数。 还有一个混合例程, 但它不像 bag 例程那样具有权重和功能, 除了返回一个 Mix。 当然, 你可以在一个哈希或一组 pairs 上使用 .Mix 强转。

除了令人惊奇的创作, 让我们做一些混合! 假如说, 你是炼金术士。
你想制作一堆令人惊叹的药水, 你要知道你需要的配料总量。

然而, 你意识到一些反应所需的某些成分实际上是你正在做的其他反应的副产物。 那么, 什么是你需要的最有效的东西呢? 混合来拯救你来了!

my %potions =
    immortality  => (:oxium(6.98), :morphics(123.3),  :unobtainium(2)   ).Mix,
    invisibility => (:forma(9.85), :rubidium(56.3),   :unobtainium(−0.3)).Mix,
    strength     => (:forma(9.15), :rubidium(−30.3),  :kuva(0.3)        ).Mix,
    speed        => (:forma(1.35), :nano-spores(1.3), :kuva(1.3)        ).Mix;

say [⊎] %potions.values;

输出.

mix(unobtainium(1.7), nano-spores(1.3), morphics(123.3),forma(20.35), oxium(6.98), rubidium(26), kuva(1.6))

为了方便起见, 我们设置了一个 哈希, 键是药剂的名称, 值是成分的量的混合。 为了产生我们寻求的成分之一的反应, 我们使用负权重, 指定产生的量。

然后, 我们使用了之前看到的相同的⊎集添加运算符, 以它的元格式:[⊎]。
我们提供的哈希值是我们的混合, 它愉快地把我们的所有成分加起来, 我们在输出中会看到。

看看unobtainium和rubidium:集合运算符正确地考虑了反应产生的数量,
那些成分具有负权重!

不朽的药水成功地混合, 我们现在需要做的是弄清楚在接下来的几千年做什么…​编写一些 Perl 6 怎么样

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容