以下是Charles C Pinter的“摘要代数书”中的练习5.F.2:
让
{e, a, b, b^2, b^3, ab, ab^2, ab^3}
成为其中的a^2 = e
组 生成器满足b^4 = e
,ba = ab^3
,G
。写表G
。 (sub generate(%eqs, $s) { my @results = (); for %eqs.kv -> $key, $val { if $s ~~ /$key/ { @results.push($s.subst(/$key/, $val)); } if $s ~~ /$val/ { @results.push($s.subst(/$val/, $key)); } } for @results -> $result { take $result; } my @arrs = @results.map({ gather generate(%eqs, $_) }); my $i = 0; while (1) { for @arrs -> @arr { take @arr[$i]; } $i++; } } sub table(@G, %eqs) { printf " |"; for @G -> $y { printf "%-5s|", $y; }; say ''; printf "-----|"; for @G -> $y { printf "-----|"; }; say ''; for @G -> $x { printf "%-5s|", $x; for @G -> $y { my $result = (gather generate(%eqs, "$x$y")).first(* (elem) @G); printf "%-5s|", $result; } say '' } } # ---------------------------------------------------------------------- # Pinter 5.F.2 my @G = <e a b bb bbb ab abb abbb>; my %eqs = <aa e bbbb e ba abbb>; %eqs<e> = ''; table @G, %eqs;
称为二面体组D4。)
这是一个小的Perl 6程序,它提供了一个解决方案:
generate
以下是结果表的样子:
让我们关注my @arrs = @results.map({ gather generate(%eqs, $_) });
my $i = 0;
while (1)
{
for @arrs -> @arr { take @arr[$i]; }
$i++;
}
:
generate
对@results
中的每个项目进行zip
的递归调用。然后我们在结果序列上有效地执行手动“zip”。但是,Perl 6有Z
和for ([Z] @results.map({ gather generate(%eqs, $_) })).flat -> $elt { take $elt; }
运算符。
而不是以上几行,我想做这样的事情:
generate
所以这是使用Z
的完整sub generate(%eqs, $s)
{
my @results = ();
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { @results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { @results.push($s.subst(/$val/, $key)); }
}
for @results -> $result { take $result; }
for ([Z] @results.map({ gather generate(%eqs, $_) })).flat -> $elt { take $elt; }
}
:
Z
生成的generate
版本的问题是它挂起......
所以,我的问题是,有没有办法用Z
来写{e, a, b, b^2, b^3, ab, ab^2, ab^3}
?
除了这个核心问题,请随意分享探索和展示Perl 6的练习的替代解决方案。
另一个例子,这是练习5.F.3来自同一本书:
设G为
a^4 = e
组 生成器满足a^2 = b^2
,ba = ab^3
,G
。写下来generate
的表格。 (G称为四元数组。)
上面显示表格的程序:
顺便说一下,这个程序是从C#中的版本转换而来的。以下是 static IEnumerable<string> generate(Dictionary<string,string> eqs, string s)
{
var results = new List<string>();
foreach (var elt in eqs)
{
if (new Regex(elt.Key).IsMatch(s))
results.Add(new Regex(elt.Key).Replace(s, elt.Value, 1));
if (new Regex(elt.Value).IsMatch(s))
results.Add(new Regex(elt.Value).Replace(s, elt.Key, 1));
}
foreach (var result in results) yield return result;
foreach (var elt in ZipMany(results.Select(elt => generate(eqs, elt)), elts => elts).SelectMany(elts => elts))
yield return elt;
}
使用LINQ和ZipMany Eric Lippert的link版本的内容。
<select name="car">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</select>
答案 0 :(得分:8)
zip
不起作用您的代码假定[Z]
(“使用zip运算符减少”)可用于获取列表列表的transpose。
不幸的是,在一般情况下,不起作用 它“通常”有效,但在一个边缘情况下中断:即,列表列表是一个完全一个列表的列表。观察:
my @a = <a b c>, <1 2 3>, <X Y Z>; put [Z~] @a; # a1X b2Y c3Z
my @a = <a b c>, <1 2 3>; put [Z~] @a; # a1 b2 c3
my @a = <a b c>,; put [Z~] @a; # abc
my @a; put [Z~] @a; #
在前两个示例(3和2子列表)中,您可以看到@a
的转置返回正常。第四个例子(0个子列表)也是正确的
但是第三个例子(1个子列表)没有按照人们的预期打印a b c
,即在这种情况下它没有返回@a
的转置,而是(似乎)转置@a[0]
。
可悲的是,这不是一个Rakudo错误(在这种情况下它可以简单地修复),而是两个Perl 6设计决策的不可预见的交互,即:
[ ]
通过使用一个参数(所述元素)调用应用于它的运算符来处理带有单个元素的输入列表。&infix:<Z>( <a b c>, )
。Z
和函数zip
(与其他接受嵌套列表的内置函数一样)遵循所谓的“单参数规则” - 即它的签名使用single-argument slurpy parameter。这意味着当使用单个参数调用它时,它将下降到它并考虑其元素要使用的实际参数。 (另见Slurpy conventions。)zip(<a b c>,)
被视为zip("a", "b", "c")
。这两个功能在许多其他情况下提供了一些不错的便利,但在这种情况下,他们的互动令人遗憾地构成陷阱。
zip
您可以检查@arrs
的元素数量,以及特殊情况下的“确切1个子列表”案例:
my @arrs = @results.map({ gather generate(%eqs, $_) });
if @arrs.elems == 1 {
.take for @arrs[0][];
}
else {
.take for flat [Z] @arrs
}
[]
是一个“zen slice” - 它返回列表不变,但没有父数据包装它的项容器。这是必需的,因为for
循环会将包装在项容器中的任何内容视为单个项目,并且只进行一次迭代。
当然,这个if-else解决方案不是很优雅,这可能会否定你首先尝试使用zip
的理由。
zip
答案 1 :(得分:6)
有可能使用Z
,但对于我可怜的小脑,拉链递归生成的懒惰列表太多了。
相反,我做了一些其他的简化:
sub generate($s, %eqs) {
take $s;
# the given equations normalize the string, ie there's no need to apply
# the inverse relation
for %eqs.kv -> $k, $v {
# make copy of $s so we can use s/// instead of .subst
my $t = $s;
generate $t, %eqs
if $t ~~ s/$k/$v/;
}
}
sub table(@G, %eqs) {
# compute the set only once instead of implicitly on each call to (elem)
my $G = set @G;
# some code golfing
put ['', |@G]>>.fmt('%-5s|').join;
put '-----|' x @G + 1;
for @G -> $x {
printf '%-5s|', $x;
for @G -> $y {
printf '%-5s|', (gather generate("$x$y", %eqs)).first(* (elem) $G);
}
put '';
}
}
my @G = <e a b bb bbb ab abb abbb>;
# use double brackets so we can have empty strings
my %eqs = <<aa e bbbb e ba abbb e ''>>;
table @G, %eqs;
这是一个generate
的紧凑重写,它进行双向替换,仍然没有显式的zip:
sub generate($s, %eqs) {
my @results = do for |%eqs.pairs, |%eqs.antipairs -> (:$key, :$value) {
take $s.subst($key, $value) if $s ~~ /$key/;
}
my @seqs = @results.map: { gather generate($_, %eqs) }
for 0..* -> $i { take .[$i] for @seqs }
}
答案 2 :(得分:5)
以下是使用smls演示的方法的generate
版本:
sub generate(%eqs, $s)
{
my @results = ();
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { @results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { @results.push($s.subst(/$val/, $key)); }
}
for @results -> $result { take $result; }
my @arrs = @results.map({ gather generate(%eqs, $_) });
if @arrs.elems == 1 { .take for @arrs[0][]; }
else { .take for flat [Z] @arrs; }
}
我测试了它,它适用于练习2和3。
正如smls在他的回答中提到的那样,当给定的数组数组只包含一个数组时,zip
没有达到我们所期望的那样。所以,让我们制作一个版本zip
使用一个或多个数组:
sub zip-many (@arrs)
{
if @arrs.elems == 1 { .take for @arrs[0][]; }
else { .take for flat [Z] @arrs; }
}
现在,generate
就zip-many
而言:
sub generate(%eqs, $s)
{
my @results = ();
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { @results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { @results.push($s.subst(/$val/, $key)); }
}
for @results -> $result { take $result; }
zip-many @results.map({ gather generate(%eqs, $_) });
}
看起来很不错。
谢谢smls!
smls在下面的评论中建议zip-many
不要调用take
,将其留给generate
。我们也将flat
从zip-many
移到generate
。
瘦身zip-many
:
sub zip-many (@arrs) { @arrs == 1 ?? @arrs[0][] !! [Z] @arrs }
和generate
同时使用它:
sub generate(%eqs, $s)
{
my @results;
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { @results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { @results.push($s.subst(/$val/, $key)); }
}
.take for @results;
.take for flat zip-many @results.map({ gather generate(%eqs, $_) });
}
答案 3 :(得分:0)
分别测试键和值似乎有点傻;你的字符串不是真正的正则表达式,所以代码中的任何地方都不需要//
。
sub generate($s, @eqs) {
my @results = do for @eqs.kv -> $i, $equation {
take $s.subst($equation, @eqs[ $i +^ 1 ]) if $s.index: $equation
}
my @seqs = @results.map: { gather generate($_, @eqs) }
for 0..* -> $i { take .[$i] for @seqs }
}
显然,对于此版本的generate
,您必须重写table
才能使用@eqs
代替%eqs
。