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閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容