Perl 6 calculate the average of an int array at once using reduce

Rom*_*rio 9 functional-programming perl6 raku

I'm trying to calculate the average of an integer array using the reduce function in one step. I can't do this:

say (reduce {($^a + $^b)}, <1 2 3>) / <1 2 3>.elems;
Run Code Online (Sandbox Code Playgroud)

because it calculates the average in 2 separate pieces.

I need to do it like:

say reduce {($^a + $^b) / .elems}, <1 2 3>;
Run Code Online (Sandbox Code Playgroud)

but it doesn't work of course.

How to do it in one step? (Using map or some other function is welcomed.)

rai*_*iph 12

TL; DR该答案以惯用的方式开始,它是在讨论P6风格的“默认”编程并提高简洁性之前编写等效代码的方式。我还在他们对您的问题的第一条评论中添加了有关Håkon++超操作的“奖励”脚注。5

也许不是您想要的,而是最初的惯用解决方案

我们将从一个简单的解决方案开始。1个

P6内置了例程2,可以满足您的要求。这是使用内置subs 的一种方法:

say { sum($_) / elems($_) }(<1 2 3>); # 2
Run Code Online (Sandbox Code Playgroud)

这里使用了对应的3 method s:

say { .sum / .elems }(<1 2 3>); # 2
Run Code Online (Sandbox Code Playgroud)

那“函数式编程”呢?

首先,让我们.sum用显式减少代替:

.reduce(&[+]) / .elems
Run Code Online (Sandbox Code Playgroud)

&在P6表达式的开始使用你知道表达是指Callable作为头等公民

将中缀+运算符称为函数值的一种简便方法是&infix:<+>。速记方法是&[+]

如您所知,reduce例程将二进制运算作为参数,并将其应用于值列表。在方法形式(invocant.reduce)中,“发起者”是列表。

上面的代码调用了两个方法- .reduce.elems-没有明确的倡导者。这是“默认”编程的一种形式;以这种方式隐式(或“默认地”)编写的方法使用$ _(又称“主题”或简称为“ it”)作为其倡导者。

主题化(明确地确定“它”是什么)

given将单个值绑定到$_(也称为“ it”)单个语句或块。

(这就是全部 given。许多其他关键字也进行了主题化,但也做了其他事情。例如,for一系列值绑定到$_,而不仅仅是一个。)

因此,您可以编写:

say .reduce(&[+]) / .elems given <1 2 3>; # 2
Run Code Online (Sandbox Code Playgroud)

要么:

$_ = <1 2 3>;
say .reduce(&[+]) / .elems; # 2
Run Code Online (Sandbox Code Playgroud)

但是考虑到您的重点是FP,还有另一种方法应该知道。

代码块和“它”

首先,将代码包装在一个块中:

{ .reduce(&[+]) / .elems }
Run Code Online (Sandbox Code Playgroud)

以上是Block,因此是lambda。没有签名的Lambda会获得默认签名,该默认签名接受一个可选参数。

现在我们可以再次使用given,例如:

say do { .reduce(&[+]) / .elems } given <1 2 3>; # 2
Run Code Online (Sandbox Code Playgroud)

但是我们也可以只使用普通的函数调用语法:

say { .reduce(&[+]) / .elems }(<1 2 3>)
Run Code Online (Sandbox Code Playgroud)

因为后缀在其左侧(...)调用Callable,并且在上述情况下,一个参数在括号中传递给需要一个参数的块,所以最终结果与代码的前do4行givenin 相同。

内置插件简洁

这是另一种写法:

<1 2 3>.&{.sum/.elems}.say; #2
Run Code Online (Sandbox Code Playgroud)

这将调用一个块,就好像它是一个方法一样。Imo仍然非常可读,特别是如果您了解P6基础。

或者您可以开始变得愚蠢:

<1 2 3>.&{.sum/$_}.say; #2
Run Code Online (Sandbox Code Playgroud)

如果您知道P6,这仍然可读。的/是一个数值(除法)运算符。数字运算符将其操作数强制为数字。在上面$_绑定到<1 2 3>哪个列表。在Perls中,数字上下文中的集合是元素的数量。

更改P6以适合您

到目前为止,我坚持使用标准P6。

您当然可以写subs或methods并使用任何Unicode字母命名。如果要使用单字母别名sumelems然后继续:

my (&s, &e) = &sum, &elems;
Run Code Online (Sandbox Code Playgroud)

但是您也可以根据需要扩展或更改语言。例如,您可以创建用户定义的运算符:

#| LHS ? RHS.
#| LHS is an arbitrary list of input values.
#| RHS is a list of reducer function, then functions to be reduced.
sub infix:<?> (@lhs, *@rhs (&reducer, *@fns where *.all ~~ Callable)) {
  reduce &reducer, @fns».(@lhs)
}
say <1 2 3> ? (&[/], &sum, &elems); # 2
Run Code Online (Sandbox Code Playgroud)

我现在就不麻烦解释了。(请随时在评论中提出问题。)我的意思只是强调您可以引入任意(前缀,中缀,后缀等)运算符。

并且如果自定义运算符还不够,您可以更改其余语法中的任何一个。cf “辫子”

脚注

1这就是我通常编写代码以执行问题中要求的计算的方式。@ timotimo ++的评论促使我改变了我的演示文稿,从此开始,然后才换档专注于更多的FPish解决方案。

2在P6中,所有内置函数都由通用术语“例程”引用,并且是-的子类的实例,Routine通常是a SubMethod

3并非所有内置sub例程都有相应命名的method例程。反之亦然。相反,有时相应的命名惯例,但他们不工作完全一样的方式(最常见的区别是第一个参数是否sub相同的方法形式“调用者”。)此外,您可以调用子例程,就好像它是使用已.&foo命名Sub.&{ ... }匿名语法的方法一样Block,或者foo看起来像使用该语法的子例程调用的方式调用方法foo invocant:或者foo invocant: arg2, arg3它具有超出调用方的参数。

4如果在明显应调用的地方使用了块,则将其调用。如果未调用它,则可以使用显式do语句前缀来调用它。

5哈康对你的问题的第一个评论用“超运算”。通过仅一个易于识别和记住的“元”(用于一元操作)或一对(用于二元操作),超操作将一个操作分配到数据结构的所有“叶” 6(用于一元)或创建一个一个新的基于配对一对数据结构的“叶子”(用于二进制操作)。注意 超级操作并行进行7

6什么是“叶”用于超运算由操作的组合来确定被施加(见is nodal性状)和一个特定的元素是否为Iterable

7至少在语义上并行应用超级操作。超级操作假设8 “叶”上的操作没有相互干扰的副作用-也就是说,相对于将操作应用于任何“叶”,可以安全地忽略将操作应用于一个“叶”时的任何副作用另一个“叶子”。

8通过使用超级操作,开发人员声明没有有意义的副作用的假设是正确的。编译器将根据实际情况进行操作,但不会检查其是否正确。从安全的角度来看,这就像是有条件的循环。即使结果是无限循环,编译器也将遵循开发人员的指令。

  • 别忘了还有.sum,它比.reduce(&[+])更短,也许更清晰 (3认同)
  • @timotimo是的,这是我的第一个想法,但是...好吧,不,我已经重写了答案。谢谢。:) (2认同)
  • @raiph是的我正在寻求以最短的,同时又最富表现力的方式执行列表操作的功能方法。“一次”实际上意味着:)我第一次看到了将do和given二重奏转换为普通函数,在这种情况下它非常有用。 (2认同)

Håk*_*and 7

Here is an example using given and the reduction meta operator:

given <1 2 3> { say ([+] $_)/$_.elems } ;
Run Code Online (Sandbox Code Playgroud)