作为CLI计算器的Julia

2020-06-05 19:10:04

_(_)_|文档:https://docs.julialang.org(_)|(_)(_)|_||_|键入";?";获取帮助,键入";]?";获取PKG帮助。|/_`|(_|1.4.2(2020-05-23)_/|\__';_|_|\__';_|__/|Julia>;

一年多来,我一直在命令行上使用Julia REPL作为我的日常计算器。令我惊讶的是,我没有看到更多的帖子谈论这种语言对于简单的数值计算和脚本编写是多么强大和可扩展事实上,正如我们稍后将看到的那样,Julia REPL不仅仅是一个简单的计算器。

我以前试过十几种Julia的替代品,比如MATLAB/Octave、Python、R、MATHEMICA,简而言之,还有更老式的BC和DC计算器,但对我来说没有一种比Julia更自然的了。

当然,Julia可以对数字进行简单的数值计算,就像任何其他语言一样。例如,浮点除法打开Julia REPL,从命令行运行Julia,

通常,我需要使用Julia的行话中的对象列表,它是数组,但我更喜欢将其称为列表。例如,向量或数字列表和矩阵,它们可以被视为向量列表,或者这里的2D数字列表,我们将使用它们来表示列向量和行向量,有时甚至是矩阵列表、更高维度的列表,以及其他对象(如字符串或日期)的列表。在这里,我们将使用它们来表示列向量和行向量,有时甚至是矩阵列表、更高维度的列表和其他对象(如字符串或日期)的列表。

Julia是一种域特定语言(Domain Specific Language,DSL),设计用于对列表执行数值计算(当然,它远不止此)。很多人可能已经被告知,Julia是一种高性能的语言,如果您正在寻找性能,Julia可能会因为它的JIT编译滞后而让您失望,但真正闪耀的是Julia的语法。

在这里,我将简要介绍一下我如何将Julia用作列表上的计算器。不要把它当作在Julia中如何编程或如何编写高效程序的教程。不过,希望我能向您展示如何在语法上有意义的代码中使用Julia。这种技能可以转换成其他语言,如Python或MATLAB,尽管这可能需要一些工作。

源代码可以在GitHub上以Jupyter笔记本的形式获得,尽管Jupyter的功能对于代码示例来说太有限了。我仍然建议您在从命令行运行的Julia REPL会话中完成示例。

如果您发现任何错误或有任何建议、意见等,请让我知道。

我最初使用Julia是因为它的语法类似于MATLAB。例如,您可以用与MATLAB完全相同的语法定义一个矩阵,它不是完全相同,但非常接近,

它计算矩阵乘积,如果你尝试在Numpy中做同样的事情,语法中会有更多的噪音。

如果你已经从学术或工作经验中了解了MATLAB,你应该会毫不费力地找到Julia,网上有几个很棒的小抄可以从其他语言转移过来。许多MATLAB语法应该开箱即用。例如,您可以使用传统的MATLAB语法编写for循环。

然而,Julia不是MATLAB的克隆,也不与MATLAB兼容,你可以找到上面提到的几乎每种语言的痕迹,还有更多,特别是Lisp,如果你想去美化Julia的语法,了解Lisp是非常重要的。将Julia视为开放源码、轻量级、可扩展的MATLAB可能会有所帮助,它看起来更像是一种实际的编程语言,但请记住,它能够支持多种编程范例。

如果您熟悉Python,您还可以像这样编写for循环。注意,Julia是1索引的,而不是Python是0索引的。

这是Julia语法的亮点之一。该语言与Unicode字符深度集成,因此,我必须为此页面选择不同的等宽字体。在REPL中,可以键入命令\in,然后按Tab键插入∈字符。

如果您遇到任何不知道如何拼写的字符,例如ℝ,请按?在REPL里。这将引导您进入帮助模式。将字符粘贴到中,然后按Enter键。

您还可以使用相同的方法找到获取特定函数或变量的文档。

大多数情况下,我更喜欢用Julia或Python语法编写for循环,因为Julia中的for循环更类似于Python-它基于生成器。这在MATLAB中是不可能实现的。

还可以在Julia中使用for循环进行列表理解(请注意,它返回一个矩阵)。

到目前为止,我们一直将矩阵视为二维数字列表。大多数情况下,将矩阵视为列向量列表或行向量列表更有意义,因为Julia有列主数组,所以更常见的是使用列向量。

让我们来看一个我以前遇到过几次的实际例子。假设我们想要在TikZ中逆时针方向围绕原点按弧度旋转一个三角形。

\BEGIN{tikzimage}[>;=stealth]%轴\Draw[->;](-2,0)--(2,0);\Draw[->;](0,-2)--(0,2);三角形的顶点百分比\坐标(V1)在(0.5,0.5);\坐标(V2)在(1.5,0.5);\坐标(V3)在(0.5,1.5);%CONNECT边\绘制[非常粗](V1)--(V2)--(V3)--循环;\end{tikzPicture}。

当然,在TikZ中可以直接做到这一点,但现在我们已经知道了顶点的坐标,所以使用一些简单的计算就可以很容易地计算出旋转坐标,而不是搜索1300多页的手册。让我们首先将坐标输入到Julia中,如果您知道如何使用Vim中的可视块模式,这会很容易。

看见?。现在我们有了一个向量列表,或者一个矩阵,矩阵和向量列表之间有一点细微的区别,稍后我们将讨论这一点。

在我们继续之前,让我们先定义旋转函数。要将2D矢量绕原点逆时针旋转弧度,我们只需将其与左侧的旋转矩阵相乘即可。如果旋转后的向量是,请注意旋转矩阵以自变量为输入,也就是说,我们需要定义一个函数,该函数以角度为输入,并输出矩阵。

#ℝ→ℝ^(2×2)R(θ)=[cos(θ)-sin(θ);sin(θ)cos(θ)]。

因此,我们可以这样定义旋转函数。你们注意到把数学思想传递给Julia是多么容易吗?

Rotate函数接受一个向量并返回另一个向量,也就是说,如果它的类型是Julia确实有一个类型系统,但是它是动态的,因此上面的Rotate函数也会很高兴地接受标量值,并且因为(可选的)类型签名是内联声明的,所以如果您想要将Julia用作简单的计算器,它可能会变得相当混乱。因此,我将使用隐式类型签名作为注释。

要在V上应用旋转函数,有相当多的方法,每种方法都会有不同的风格。

如果您熟悉函数式编程,您可能会立即认识到Rotate是一个纯函数,并可能希望这样做。

是的,Julia确实有一个映射函数,但它会将矩阵视为2D数字列表。例如,如果我们有一个使用lambda表示法定义的函数dup,

这会将一个数字复制到一个2D矢量中。使用MAP在V上应用DUP将给我们一个矢量的2D列表,

2×3 Array{Array{Float64},2}:[0.5,0.5,0.5,0.5][1.5,1.5,1.5,1.5][0.5,0.5,0.5,0.5][0.5,0.5,0.5,0.5][0.5,0.5,0.5,0.5][1.5,1.5,1.5,1.5]。

这是因为如果v1和v2不是标量,则语法[v1;v2]将垂直连接向量。DUP可以应用于任何向量,它将返回一个向量,甚至可以应用于矩阵。

编译器将根据参数的类型将语法[v1;v2]的底层函数(Vcat)分派到其相应的实现。更重要的示例是,特殊矩阵(例如对称矩阵)用类型进行标记,以便将它们分派到最有效的实现中。@Which宏可用于查看使用了哪个实现。

[a,b;c,d]的底层函数hvcat的多重分派使您可以轻松地编写块矩阵。我们稍后将详细介绍这一点。

4×4阵列{Float64,2}:0.5 1.5 0.5 0.5 0.5 1.5 1.5 1.5 0.5 1.5 1.5 0.5 0.5 0.5 1.5。

让我们回到我们的主要话题。要将函数应用于矩阵的每个列向量,也有多种方法。让我们先来看一个更简单的。

为了映射矩阵的每一列,Julia提供了一个名为eachol的函数。

输出的确切含义并不重要,我们只需要知道它是一个生成器。它与Python中的生成器基本相同。要将生成器转换为列表,我们可以使用Collect函数

同样,类型签名在这里并不重要,我们只需要知道它将矩阵转换为列向量列表。

事实上,我们不需要将生成器转换为列表,map可以直接映射到生成器上,所以我们可以将Apply Our Rotate写为符号上的附注,\Prime(‘)与';不同。

它所做的正是它声称的-将函数Rotate映射到V的每一列。这正是我们所需要的。事实上,有一种使用点语法编写它的更简单的方法。

对于生成器和具有只接受1个参数的函数的1D对象列表,点语法的含义与map完全相同,但对于更高的维度,它是在幕后广播的。MATLAB有类似的语法,但它只适用于几个运算符。在Julia中,此语法适用于任何函数。

点语法也适用于具有更多参数的二元运算符和函数。对于中缀运算符,点在运算符前面。

为了在TikZ图中使用旋转矢量V‘,我们必须进行一些清理。

对于每个向量v,我们首先将每个元素四舍五入为5位数,然后用逗号连接。

对于这样的函数组合,Julia还借用了由F#R推广的前向管道运算符|>;,它还通过magrittr包拥有类似的运算符。

特别地,x|>;f等价于f(X)。由于点语法,我们还有.|>;运算符,它允许我们在列表上应用函数。

或者,在幕后播放一些小把戏,在列表的每个元素上应用多个函数。

如果您现在不理解上面的示例,请不要担心,我们将在下一节中讨论矩阵和向量列表之间的区别,但也有相似之处。

为了好玩,让我们实现一个凯撒密码,它简单地将字符串中的所有字符移位固定的量,使用管道操作符,您可以使用Esc+Enter键在REPL中插入一个换行符。

#(String,ℤ)→String ENCRYPT(STR,KEY)=(STR|&>;Collect.|>;(x->;x-';a';).|>;(x->;mod(x+key,26)).|>;(x->;x+';a';)|>;Join。

请注意,.|>;运算符的优先级高于|>;,因此我们必须在此处使用括号。此外,由于|>;和.|>;都比->;具有更高的优先级,

我们需要用括号对匿名函数进行分组,感谢多个攻击者指出这一点,尽管对于我们的加密函数来说,这并不重要。

使用前向管道操作符,我们甚至不需要额外的清理函数就可以映射到V‘上。我们可以直接使用管道进行清理。

不要担心输出中的Nothing,它是println的返回值,类似于Haskell中的IO()。我们可以通过在末尾添加分号来取消它。

不过,我对Julia的抱怨之一是,它不支持部分应用程序和默认的curing。例如,在Haskell中,您可以编写如下内容。

但这在朱莉娅身上是不可能的。标准库中有一个名为Base.Fix2的函数,您可以将其重命名为curry以模仿语法,

但是还是不太方便。有一个正在进行的解决这个问题的提案,但它还没有合并。一旦合并,我们就可以写出更干净的东西了。

但是,现在我们已经定义了Cleanup函数,它只接受一个参数,现在有一种更巧妙的方法来完成我们到目前为止所做的事情。诀窍是管道操作员|>;也乐于接受发电机。

\开始{tikz图片}[>;=隐形]%轴\绘制[->;](-2,0)--(2,0);\绘制[->;](0,-2)--(0,2);三角形的顶点百分比\坐标(V1)位于(-0.18301,0.68301);坐标(V2)位于(0.31699,1.54904);\坐标(V3)位于(-1.04904,1.18301);%CONNECT边\绘制[非常粗](V1)--(V2)--(V3)--循环;\end{tikzPicture}。

正如您可能已经注意到的,矩阵的类型为Array{T,2}=Matrix{T},

要理解这一点,查看以下三种语法之间的区别会更容易一些。

这就是Julia与MATLAB的矩阵语法不兼容的原因。这在MATLAB中是有效的,

然而,在Julia中,(数字的)列向量实际上等同于一个数字列表,因此我们也可以对数字列表应用矩阵乘法。

但是,向量列表的列表语法和列向量语法之间有很大的不同。

然而,在数学表示法中,如果我们使用;语法定义矩阵,

结果是一个平面矩阵,正如我们所看到的,将;符号称为垂直连接(VCAT)非常合适,而不是创建一个列。同样,空格字符也称为水平连接(HCAT)。

这个对象应该是可视化的,如果我们有CAT列向量,我们就会得到一个矩阵。

#list(Array)[a,b,c]#垂直拼接[a;b;c]#水平拼接[a b c]#列表列表[[a],[b],[c]]#列表垂直拼接[[a];[b];[c]]#列表水平拼接[[a][b][c]]#列表水平拼接[[a][b]];[。

#列表的水平连接的垂直连接#单个数字的列表[[a][b];[c][d]]

#的矩阵乘法#(列表的水平连接的垂直连接#单个数字的列表的垂直连接)#和#(单个数字的列表的垂直连接)[[a][b];[c][d]]*[a;b]。

从视觉上看,每一步都可以在数学上得到证明,尽管它涉及两个向量空间,一个是所有矩阵的向量空间,另一个是。

事实上,有一种更简单的方式来理解这一点。我们可以看一下每个符号的抽象语法树(AST)。

在这里,函数Meta.show_sexpr可以将任何表达式显示为Lisp样式的S表达式。如果您想去掉Julia的语法,这是非常有用的。正如我们所看到的,[a;b]被解析为:vcat,而[a b]被解析为:hcat。它们实际上只是指串联。

符号:vcat表示Julia中的符号,它类似于Lisp中的原子/符号,语法:类似于Lisp中的引号,例如。

符号:(1+1)表示表达式,其中:()语法类似于Lisp中的准引号。

Julia对这些有如此一流支持的原因之一是因为它支持元编程,所以我们可以直接转换AST。另一个原因是Julia编译器的一部分是用一种称为Femtolisp的自定义Lisp方言编写的。例如,有一个@。用于将每个函数调用转换为点语法的宏。

我们可以注意到,它被解释为两行的垂直连接。我们已经知道可以使用@Which宏来查看哪个函数负责创建数组,但是我们还可以更进一步,使用@edit宏跳转到实现的源代码

函数hvcat(行::元组{vararg{Int}},xs::t.)。其中T<;:number nr=长度(行)nc=row[1]a=Matrix{T}(undef,nr,nc)if length(A)!=length(Xs)抛出(ArgumentError(";参数计数与指定形状不匹配(预期$(length(A)),get$(length(Xs)";)end k=1@i=1的入界:nr if nc!=rows[i]抛出(ArgumentError(";参数计数不匹配指定形状(预期$(length(A),get$(length(Xs)";)end k=1@i=1的入界。行$(I)的列数不匹配(预期为$nc,获得$(rows[i])";)end for j=1:nc a[i,j]=Xs[k]k+=1 end a end。

正如我们所看到的,尽管解析器将矩阵解释为行的垂直连接,但是函数hvcat的这个特定实例负责实际创建矩阵。

如果您将来遇到任何奇怪的Julia语法,检查AST和源代码可能是一个很好的选择。对于那些饿了的人,吃点零食怎么样?

在上一节中,我们讨论了如何将函数映射到维数为2的矩阵或2D列表上。

为了将函数映射到更高维度的列表,还有一个名为EachSlice的函数。下面的值相当于Eachcol值。

然而,要理解它,我们必须先谈论切片。它与MATLAB和Python中的切片基本相同。

回到我们定义V的时候,REPL告诉我们V的维度是2×3。

矩阵中的每个元素通过注意,即使Julia是列长的,第一个索引仍然是行号。我们可以使用方括号从矩阵中选择单个元素,

我们可以通过将列表作为索引传递来选择多个元素,您也可以在此处使用1:2或1:end。

这将选择对应于和的元素,并以列表或列向量的形式返回。返回的向量将具有与索引相同的形状,因此您也可以使用行向量进行索引。

您可以使用map来理解此特定情况的结果,但是请注意,如果第二个槽中的索引不是标量,则这种解释是不正确的。

符号:可用于选择给定维度中的所有索引,并以与前面提到的列表或列向量相同的形状返回结果。

EachSlice函数基本上将索引的每个槽替换为:,除了由dims表示的槽。上面的情况相当于不完全是,底层实现在幕后使用视图,以避免复制并允许修改。

还有一个称为mapslices的更灵活的函数,它可用于将函数按原样映射到矩阵上,而无需将其转换为向量列表。

这里的dims=1表示对于每个j∈1:3,要旋转的参数是V[:,j],其中冒号:在第一个槽中。

类似地,如果我们设置DIMS=2,对于I∈1:2,参数将是V[i,:],其中:在第二个槽中。不过,需要注意的是,请记住V[i,:]也是一个列向量,因为它的形状与列表相同。

这不同于每个切片。对于每个切片,dims=2表示冒号:在除第二个插槽之外的所有插槽中。

与Python和MATLAB不同,由于JIT编译,使用循环迭代大型矩阵是非常好的,有时甚至是首选的。

回想一下,每个EACCHCOL返回一个生成器,所以我们也可以在for循环中使用它。

注意语法是多么的干净和描述性,尽管没有我们的函数方法那么干净:)。您也可以使用以下命令执行相同的操作。

..