12

要在 perl6 中从数组中选择多个元素,很简单:只需使用索引列表:

> my @a = < a b c d e f g >;
> @a[ 1,3,5 ]
(b d f)

但是要取消选择这些元素,我必须使用 Set:

> say @a[ (@a.keys.Set (-) (1,3,5)).keys.sort ]
(a c e g)

我想知道是否有更简单的方法,因为我使用的数组通常很大?

4

5 回答 5

10
sub infix:<not-at> ($elems, @not-ats) {
  my $at = 0;
  flat gather for @not-ats -> $not-at {
    when $at < $not-at { take $at++ xx $not-at - $at } 
    NEXT { $at++ }
    LAST { take $at++ xx $elems - $not-at - 1 }
  }
}

my @a = < a b c d e f g >;
say @a[ * not-at (1, 3, 5) ]; # (a c e g)

如果您知道它使用的每个 P6 构造,我认为操作符代码是不言自明的。如果有人希望在以下内容之外对其进行解释,请在评论中告诉我。

我将从产生对not-at.

*又名Whatever

Whatever文档页面

when*用在术语位置,即作为操作数,与大多数运算符结合使用,编译器会将表达式转换为类型的闭包WhateverCode

*确实在上面用作操作数。在这种情况下,它是我刚刚创建$elems的中缀运算符的左参数(对应于参数)。not-at

下一个问题是,编译器会进行转换吗?编译器根据操作符是否有一个显式*作为参数对应的参数来决定*。如果我写*的不是$elemsthen 那将成为not-at少数几个想要直接处理*并执行它选择做的任何事情的运算符之一,编译器会直接调用它。但我没有。我写了$elems。所以编译器进行了我接下来要描述的转换。

WhateverCode转换围绕封闭表达式构建了一个新表达式,并将其重写Whatever为“它”,也就是主题,也就是$_。所以在这种情况下,它变成了这样:

* not-at (1,3,5)

进入这个:

{ $_ not-at (1,3,5) }

[...]下标有什么作用

[...]in@a[...]是(Positional数组/列表)下标。这强加了几个评估方面,其中两个在这里很重要:

  • “它”又名主题又名$_设置为列表/数组的长度。

  • 如果下标的内容是 aCallable它被调用。如上所述WhateverCode生成的确实是 aCallable所以它被调用。

所以这:

@a[ * not-at (1,3,5) ]

变成这样:

@a[ { $_ not-at [1,3,5] } ]

变成了这样:

 @a[ { infix:not-at(7, [1,3,5]) } ]
于 2019-08-20T15:01:31.573 回答
9

鉴于索引器想要提取元素,我们可以通过将要排除的元素列表转换为要提取的元素范围列表来解决此问题。也就是说,给定:

1, 3, 5

我们会产生相当于:

0..0, 2..2, 4..4, 6..Inf

鉴于:

my @exclude = 1, 3, 5;

我们可以做的:

-1, |@exclude Z^..^ |@exclude, Inf

要将其分解,请使用 zip 压缩(-1, 1, 3, 5)(1, 3, 5, Inf)但使用具有专有端点的范围运算符。对于给定的示例,这导致:

(-1^..^1 1^..^3 3^..^5 5^..^Inf)

这相当于我上面提到的范围。然后我们将其粘贴到索引器中:

my @a = <a b c d e f g>
my @exclude = 1, 3, 5;
say @a[-1, |@exclude Z^..^ |@exclude, Inf].flat

这给出了预期的结果:

(a c e g)

这种方法是 O(n + m)。如果数组很长,它可能会工作得很好,但是要排除的东西的数量相对较少,因为它只生成Range索引所需的对象,然后按范围索引相对优化。

最后,如果flat外面的 被认为很麻烦,也可以把它移到里面:

@a[{ flat -1, |@exclude Z^..^ |@exclude, $_ }]

之所以有效,是因为该块传递了@a.

于 2019-08-20T23:39:10.880 回答
7

这是另一种选择:

my @a = < a b c d e f g >;
say @a[@a.keys.grep(none(1, 3, 5))];

但总而言之,数组并未针对此用例进行优化。它们针对单个元素或所有元素进行了优化,切片为(积极地)按键选择多个元素提供了快捷方式。

如果你告诉我们底层的用例,也许我们可以推荐一个更合适的数据结构。

于 2019-08-20T19:58:14.563 回答
3

对于大型阵列,这可能会很慢,但从逻辑上讲,它更接近您正在寻找的内容:

my @a = <a b c d>;

say (@a ⊖ @a[0,1]).keys; # (c d)

它基本上与您在开始时提出的解决方案相同,使用集合差异,除了它在整个数组而不是索引上使用它。此外,在某些情况下,您可能会直接使用该集合;这取决于你想做什么。

于 2019-08-21T11:40:00.897 回答
2

@raiphs 解决方案与@Jonathan Worthington 的结合:

该运算符对于大量数字和大型 @not-ats 列表应该非常有效,因为它返回一个范围列表,它甚至可以懒惰地创建该范围列表。对于@not-ats,它支持包含和排除边界和无穷大的整数和范围。但它必须是上升的。

$elems 可以是 Range 或 Int。它被解释为 .Int 与 Jonathan Worthington 的解决方案一样支持(但需要 .flat 将其应用于数组切片 - 惰性运算符的性能代价 - 这可以通过在第二行使用 flat gather 而不是惰性聚集来改变)

@a[ (* not-at (1, 3, 5)).flat ];

或新支持

@a[ (* not-at (1, 3^ .. 5, 8 .. 8, 10, 14 .. ^18, 19 .. *)).flat ];

当不是一次对数组进行切片,而是对数组的某些部分进行操作时,可以看到性能的改进,最好使用多线程。

sub infix:<not-at> ($elems, @not-ats) {
    lazy gather {
        my $at = 0;
        for @not-ats {                                               # iterate over @not-ats ranges
            my ($stop, $continue) = do given $_ {
                when Int            { succeed $_,       $_         } # 5
                when !.infinite     { succeed .int-bounds          } # 3..8 | 2^..8 | 3..^9 | 2^..^9
                when !.excludes-min { succeed .min,     $elems.Int } # 4..*
                default             { succeed .min + 1, $elems.Int } # 3^..*
            }
            take $at .. $stop - 1 if $at < $stop;                    # output Range before current $not-at range
            $at = $continue + 1;                                     # continue after current $not-at range
        }
        take $at .. $elems.Int - 1 if $at < $elems;                  # output Range with remaining elements
    }
}
于 2020-03-27T13:53:23.103 回答