【问题标题】:perl6 What is a quick way to de-select array or list elements?perl6 取消选择数组或列表元素的快速方法是什么?
【发布时间】:2019-12-25 09:11:54
【问题描述】:

要在 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)

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

【问题讨论】:

  • 也许数组不是做这些事情的最佳方式?也许哈希会更好,使用索引作为键?
  • 哦,这听起来像是自定义后修复操作员的绝佳机会。也许@a-[ … ]-(或相反,@a[- … -]
  • 谢谢 Elizabeth Mattijsen 和 guifa!

标签: arrays set raku deselect


【解决方案1】:
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 构造,我认为操作符代码是不言自明的。如果有人希望在以下内容之外对其进行解释,请在 cmets 中告诉我。

我将从生成对not-at 的调用的两个方面开始。

*又名Whatever

来自Whatever doc page

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

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

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

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

* not-at (1,3,5)

进入这个:

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

[...] 作为下标的作用

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

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

  • 如果下标的内容是Callable,它会被调用。如上所述生成的WhateverCode 确实是Callable,因此它会被调用。

所以这个:

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

变成这样:

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

变成这样:

 @a[ { infix:not-at(7, [1,3,5]) } ]

【讨论】:

  • 我仍然投票支持否定的后缀,但 +1 为其余所有
  • 我尝试过,但未能成功。 sub postcircumfix:&lt;[ ]-&gt; (@positional, *@index) { @positional[infix:not-at(@positional.elems, @index)] 和变体会产生编译时错误。如果我能让它工作,我会把它添加到我的答案中。但我也觉得我想要一种适用于广泛且可扩展的索引选择操作的方法,* foo-at (indices) 在我看来,它会以适当且易读的方式完成这项工作。
  • raiph:啊,有趣。无论如何,你的回答很好地展示了[ ]circumfix 中的任何东西的力量。如果没有pickroll 的存在,很容易想象通过这种方法启用它。
  • 就个人而言,我更喜欢 { take $at++ xx ( $not-at - $at ) } 。请您解释一下为什么需要缓存?
  • @p6steve cache 是早期更糟糕的代码遗留下来的。这是不必要的,我已将其删除。谢谢。 :)
【解决方案2】:

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

1, 3, 5

我们会产生相当于:

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

给定:

my @exclude = 1, 3, 5;

我们可以做到:

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

要将其分解,请将(-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 中的元素数。

【讨论】:

  • 感谢乔纳森·沃辛顿从不同的角度发出光芒!!!
【解决方案3】:

这是另一个选择:

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

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

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

【讨论】:

  • 对于底层用例,我正在对巨大的文本文件进行数据挖掘,选择每条非常长的行中的某些元素,将它们移到前面,并将其余部分移到行的后面。即,将 @a[1,5,9,1001] 移到前面,将 @a[without 1,5,9,1001] 移到行的后面;谢谢。
  • @lisprogtor 您可能要考虑使用splice 删除元素,并单独处理它们。
【解决方案4】:

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

my @a = <a b c d>;

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

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

【讨论】:

    【解决方案5】:

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

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

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

    @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
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-11
      • 2014-11-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多