【问题标题】:Assign a Seq(Seq) into arrays将 Seq(Seq) 分配到数组中
【发布时间】:2019-06-05 20:15:07
【问题描述】:

将 Seq(Seq) 分配给多个类型数组而不首先将 Seq 分配给标量的正确语法是什么? Seq 是否以某种方式变平?这失败了:

class A { has Int $.r }

my A (@ra1, @ra2);

#create two arrays with 5 random numbers below a certain limit

#Fails: Type check failed in assignment to @ra1; expected A but got Seq($((A.new(r => 3), A.n...)
(@ra1, @ra2) =
   <10 20>.map( -> $up_limit {
        (^5).map({A.new( r => (^$up_limit).pick ) })
    });

【问题讨论】:

    标签: raku


    【解决方案1】:

    TL;DR 绑定比赋值更快,所以也许这是解决您问题的最佳实践解决方案:

    :(@ra1, @ra2) := <10 20>.map(...);
    

    虽然比已接受答案中的解决方案更丑陋,但这在算法上更快,因为绑定是O(1),而在被绑定列表的长度中分配的O(N)

    分配/复制

    简化,您的非工作代码是:

    (@listvar1, @listvar2) = list1, list2;
    

    在 Raku 中缀= 表示从= 右侧的赋值/复制= 左侧的一个或多个container 变量。

    如果左边的变量绑定到Scalar container,那么它将分配右边的值之一。然后赋值过程从左边的下一个容器变量和右边的下一个值开始。

    如果左侧的变量绑定到Array container,则它将用完右侧的所有剩余值。所以你的 first 数组变量同时接收到list1list2。这不是你想要的。


    简单来说,这是 Christoph 的回答:

    @listvar1, @listvar2 Z= list1, list2;
    

    暂时搁置=Zthe zip routine 的中缀版本。就像 (a physical zip 将连续参数在其左右配对。当与运算符一起使用时,它将该运算符应用于该对。因此您可以将上面的Z= 阅读为:

    @listvar1 = list1;
    @listvar2 = list2;
    

    工作完成了吗?


    分配 Array 容器需要:

    • 单独复制与每个列表中的一样多的单个项目容器中。 (在您的示例代码中,list1list2 各包含 5 个元素,因此总共有 10 个复制操作。)

    • 强制容器根据需要调整大小以容纳项目。

    • 将项目使用的内存加倍(原始列表元素和复制到 Array 元素中的副本)。

    • 检查每个项目的类型是否匹配元素类型约束。

    分配通常比绑定要慢得多,而且占用更多内存...

    绑定

    :(@listvar1, @listvar2) := list1, list2;
    

    := 运算符 binds 左边的参数到右边的参数。

    如果左边有一个变量,那么事情就特别简单。绑定后,变量现在精确地引用右边的内容。 (这特别简单和快速——快速类型检查就完成了。)

    但我们的情况并非如此。

    Binding 还接受其左侧的独立签名文字。我的答案中的 :(...) 是独立的 Signature 文字。

    (签名通常附加到不带冒号前缀的例程中。例如,在sub foo (@var1, @var2) {} 中,(@var1, @var2) 部分是附加到例程foo 的签名。但正如您所看到的,可以编写签名分开,并通过在一对括号前面加上冒号来让 Raku 知道它是一个签名。一个关键的区别是签名中列出的任何变量都必须已经声明。)

    当左边有一个签名文字时,绑定会根据与在例程调用中绑定参数相同的逻辑发生在接收例程的签名中。

    所以最终结果是变量获得了它们在这个 sub 中的值:

    sub foo (@listvar1, @listvar2) { }
    foo list1, list2;
    

    也就是说效果是一样的:

    @listvar1 := list1;
    @listvar2 := list2;
    

    再一次,就像克里斯托夫的回答一样,工作完成了。

    但是这样我们就可以避免分配开销。

    【讨论】:

    • 感谢您的澄清!我想知道Cannot use bind operator with this left-hand side 的原因是什么?另一件事:我尝试了 my (@a, @b) := [1], [2, 3] 并且它有效,但我无法使这种方法与 OP 的 map 一起使用。例如:(@ra1, @ra2) := [&lt;10 20&gt;.map( -&gt; $up_limit {[ (^5).map({A.new( r =&gt; (^$up_limit).pick ) })] --> Cannot use bind ...
    • 嗨@HåkonHægland :) "One-pass parsing is darn near mandatory" ... "for an extensible, braided language"。一旦解析器到达并解析了:=,它就会承诺将左边的东西解释为Listmy (@a, @b) 中的 (@a, @b)Signature,非常类似于 :(@ra1, @ra2) 签名文字,正如我的回答中所解释的那样,尽管没有前面的冒号。相比之下,(@ra1, @ra2)List
    • 嗨@raiph。感谢您再次回到此评论。经过多一点测试,我的问题可以简化为:my A (@a, @b); (@a, @b) := [A.new(r =&gt;1)], [A.new(r =&gt; 2)]; --> 不起作用。但是,如果我不预先声明 @a@b: my A (@a, @b) := [A.new(r =&gt;1)], [A.new(r =&gt; 2)]; 它可以工作!我在这里错过了什么?
    • my 让解析器知道你在声明一些东西。 A 似乎是一种类型,因此解析器允许这样做并且可以解决。接下来,它看到一个左括号,所以它认为“啊,必须是一个正在声明的标识符列表”。果然,当它到达正确的括号时,它决定“是的”,这就是my A (...) 的标志性事情。但是,当您编写以左括号开头而没有前面的my 的代码时,解析器会认为“我们这里有一个表达式”。到最后,它被解析为List。当它到达:=时,改变它为时已晚。
    • 我们可能可以将其设为 DWIM,这样如果:= 发现它的左侧有一个列表,它会将其转换为签名,或者可能会出现还有另一种绑定方式,专门用于绑定到列表,但现在该语言只是抱怨,本质上是说“如果你想像调用例程时那样绑定,请在前面加上冒号”。奇怪的是,my (...) 样式签名与 :(...) 样式签名的行为并不完全相同(与 sub foo (...) 样式签名不太一样)。我真的不认为我们想要另一个......
    【解决方案2】:

    不完全确定它是否是设计使然,但似乎发生的情况是您的两个序列都存储在@ra1 中,而@ra2 仍然为空。这违反了类型约束。

    什么是有效的

    @ra1, @ra2 Z= <10 20>.map(...);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-29
      • 1970-01-01
      • 1970-01-01
      • 2019-12-13
      • 2019-05-26
      • 2020-08-12
      • 2015-06-01
      相关资源
      最近更新 更多