朱莉娅 字符串和元编程


我们将继续研究年轻而有前途的通用语言Julia 。 这次,我们将更加关注代码行,开始怯of地步入元编程的世界,并教解释器执行符号操作(剪切后只有两张图片,但是有很多语法糖)


线数


通过将字符括在双引号中来创建字符串变量,将单个字符设置为char类型的单个字符。 字符串连接使用乘法“ *”执行:


cats = 4 s1 = "How many cats "; s2 = "is too many cats?"; s3 = s1*s2 Out[]: "How many cats is too many cats?" 

也就是说,求幂的行为如下:


 s1^3 Out[]: "How many cats How many cats How many cats " 

另一方面,对行进行索引:


 s1[3] Out[]: 'w': ASCII/Unicode U+0077 (category Ll: Letter, lowercase) s1[5:13] Out[]: "many cats" s1[13:-1:5] Out[]: "stac ynam" s2[end] Out[]: '?': ASCII/Unicode U+003f (category Po: Punctuation, other) 

可以从字符串和其他类型创建字符串:


 s4 = string(s3, " - I don't know, but ", cats, " is too few.") #   s4 = "$s3 - I don't know, but $cats is too few." Out[]: "How many cats is too many cats? - I don't know, but 4 is too few." 

武库中有许多有用的功能 ,例如搜索元素:


 findfirst( isequal('o'), s4 ) Out[]: 4 findlast( isequal('o'), s4 ) Out[]: 26 findnext( isequal('o'), s4, 7 ) #      Out[]: 11 

因此,您可以轻松实现Caesar的Cipher


 caesar(X, n) = prod( [x += n for x in X] ) str3 = "    "; caesar(str3, 3) Out[]: "####" str4 = caesar(str3, -32) Out[]: "\0\0\0\0" "Latin letters go before Cyrillic" < str4 < str3 Out[]: true 

在此,对字符串的所有字符进行一次运行,将n添加到每个字符的代码中。 结果是由prod()函数粘合在一起的char字符数组,该函数实现了所接收数组的所有元素的乘法。


角色计算


Julia已经准备了不同程度的用于符号计算的软件包,例如:



但是我们以某种方式学会了粘合字符串,为什么不使用矩阵实现符号动作。 假设我们要添加两个不同的数组:


 m1 = [1 1 "a"; 1 0 1] Out[]: 2×3 Array{Any,2}: 1 1 "a" 1 0 1 m3 = [1 2 "ln(3)"; 2 1 0] Out[]: 2×3 Array{Any,2}: 1 2 "ln(3)" 2 1 0 m1+m3 Out[]: MethodError: no method matching +(::String, ::String) ... 

首先,请在基本运算符中使用俏皮的笔:


 import Base: *, -, + 

如果在C ++中这种残缺被称为运算符重载,那么我们在这里只需为现有函数添加一个方法。


 +(a::String, b::String) = a * "+" * b Out[]: + (generic function with 164 methods) 

一百五十多种方法,还有另一种将持续到会话结束。


 m1+m3 Out[]: 2×3 Array{Any,2}: 2 3 "a+ln(3)" 3 1 1 

现在将乘法乘为子,即,我们确定将字符串乘以数字的情况:


 function *(a::String, b::Number) if b == 0 0 else if b == 1 a else "$b" * "*(" * a * ")" end end end Out[]: * (generic function with 344 methods) 

也就是说,如果字符串乘以零,则得到零,如果乘以1,则得到相同的字符串,在其他情况下,我们用“ *”号和字符串来盲目数字(我们加了方括号,以使字符没有不确定性)。 就个人而言,我喜欢该函数的单行视图,这对于三元运算符很有用:


 *(a::String, b::Number) = (b==0) ? 0 : (b==1) ? a : "$b" * "*(" * a * ")" Out[]: 

我们将其余案例暴露给类似的执行,并得到如下结果:


 import Base: *, -, + +(a::String, b::String) = a * "+" * b *(a::String, b::String) = a * "*(" * b * ")" *(a::String, b::Number) = (b==0) ? 0 : (b==1) ? a : "$b" * "*(" * a * ")" *(b::Number, a::String) = (b==0) ? 0 : (b==1) ? a : "$b" * "*(" * a * ")" +(a::String, b::Number) = (b==0) ? a : a * "+" * "$b" +(b::Number, a::String) = (b==0) ? a : a * "+" * "$b" -(b::Number, a::String) = (b==0) ? "-" * a : "$b" * "-" * a -(a::String, b::Number) = (b==0) ? a : a * "-" * "$b" # m1 = [1 1 "a"; 1 0 1] m2 = [2 0 2; 2 "b" 2; 2 2 2] m1*m2 Out[]: 2×3 Array{Any,2}: "2*(a)+4" "b+2*(a)" "2*(a)+4" 4 2 4 

然后计算行列式! 但是,由于内置函数很复杂,因此我们的方法不足以将它提供给具有行的矩阵-我们会遇到错误。 这意味着我们使用Levi-Civita符号鞭打自己的


 ε = zeros(Int, 3,3,3) ε[1,2,3] = ε[2,3,1] = ε[3,1,2] = 1 ε[3,2,1] = ε[1,3,2] = ε[2,1,3] = -1 ε Out[]: 3×3×3 Array{Int64,3}: [:, :, 1] = 0 0 0 0 0 1 0 -1 0 [:, :, 2] = 0 0 -1 0 0 0 1 0 0 [:, :, 3] = 0 1 0 -1 0 0 0 0 0 

混合产品配方


\左[ veca vecb vecc right]= sumijk=13 varepsilonijkaibjck


可用于查找3x3矩阵的行列式,并将abc分别理解为第一行,第二行和第三行(列)


 detrmnt(arr) = sum( ε[i,j,k]*arr[1,i]*arr[2,j]*arr[3,k] for i in 1:3, j in 1:3, k in 1:3 ) detrmnt(["a" 2 "b"; 1 0 1; "c" 2 "d"]) Out[]: "2*(c)+2*(b)+2*(-1*(a))+-2*(d)" 

很容易猜到,对于矩阵而言,它更加复杂,所有内容看起来都非常平凡-有很多括号和数字的乘法运算,因此最好使用几个进行分解的函数。 这将是您的作业。
此外,数组的乘法更为复杂:


 Rx = [1 0 0 0; 0 "cos(ϕ)" "sin(ϕ)" 0; 0 "-sin(ϕ)" "cos(ϕ)" 0; 0 0 0 1]; Rz = ["cos(ϕ)" "sin(ϕ)" 0 0; "-sin(ϕ)" "cos(ϕ)" 0 0; 0 0 1 0; 0 0 0 1]; T = [1 0 0 0; 0 1 0 0; 0 0 1 0; "x0" "y0" "z0" 1]; Rx*Rz Out[]: MethodError: no method matching zero(::String) ... 

将给出一个错误。 明确的业务,对于尺寸大于3的矩阵,使用了更高级和更复杂的方法,我们并未对其调用进行“超载”。 好吧,我们将使用自己的乘法器来介绍现有的方法(当然,不建议使用这种方法,尤其是在或多或少复杂的程序中,它们很可能会发生内部冲突)。


 function *( a::Array{Any,2}, b::Array{Any,2} ) if size(a)[2] == size(b)[1] res = Array{Any}(undef, size(a)[1], size(b)[2] ) fill!(res, 0) for i = 1:size(a)[1], j = 1:size(b)[2], k = 1:size(a)[2] res[i,j] = res[i,j] + a[i,k]*b[k,j] end res else error("Matrices must have same size mult") end end Out[]: * (generic function with 379 methods) 

现在您可以放心地乘以:


 X = Rx*Rz*T Out[]: 4?4 Array{Any,2}: "cos(ϕ)" "sin(ϕ)" 0 0 "cos(ϕ)*(-sin(ϕ))" "cos(ϕ)*(cos(ϕ))" "sin(ϕ)" 0 "-sin(ϕ)*(-sin(ϕ))" "-sin(ϕ)*(cos(ϕ))" "cos(ϕ)" 0 "x0" "y0" "z0" 1 

让我们牢记这一矩阵,但现在,我们将开始


元编程


行情


Julia支持元编程。 这与符号编程类似,在符号编程中,我们处理表达式(例如6 * 7)而不是值(例如42)。 将操作引入解释器中,我们立即获得结果,如我们所见,可以通过以下代码绕开结果:


 x = "6*7" Out[]: "6*7" 

可以使用parse()将字符串转换为表达式,然后使用eval()对其求值:


 eval(Meta.parse(ans)) Out[]: 42 

为什么所有这些困难? 诀窍在于,当我们有一个表达式时 ,我们可以用各种有趣的方式对其进行修改:


 x = replace(x, "*" => "+") eval(Meta.parse(x)) Out[]: 13 

为了避免大惊小怪,提供了一个皱着眉头的操作符:()


 y = :(2^8-1) Out[]: :(2^8-1) eval(y) Out[]: 255 

您可以“引用”表达式,函数,代码块...


 quote x = 2 + 2 hypot(x, 5) end Out[]: quote #= In[13]:2 =# x = 2 + 2 #= In[13]:3 =# hypot(x, 5) end :(function mysum(xs) sum = 0 for x in xs sum += x end end) Out[]: :(function mysum(xs) #= In[14]:2 =# sum = 0 #= In[14]:3 =# for x = xs #= In[14]:4 =# sum += x end end) 

现在,我们将解析先前计算的矩阵(最好在上一节之后开始一个新的会话,我丑陋的重载很容易与插件包冲突):


 X = [ "cos(ϕ)" "sin(ϕ)" 0 0; "cos(ϕ)*(-sin(ϕ))" "cos(ϕ)*(cos(ϕ))" "sin(ϕ)" 0; "-sin(ϕ)*(-sin(ϕ))" "-sin(ϕ)*(cos(ϕ))" "cos(ϕ)" 0; "x0" "y0" "z0" 1; ] for i = 1:size(X,1), j = 1:size(X,2) if typeof(X[i,j]) == String X[i,j] = Meta.parse(X[i,j]) end end X Out[]: 4×4 Array{Any,2}: :(cos(ϕ)) :(sin(ϕ)) 0 0 :(cos(ϕ) * -(sin(ϕ))) :(cos(ϕ) * cos(ϕ)) :(sin(ϕ)) 0 :(-(sin(ϕ)) * -(sin(ϕ))) :(-(sin(ϕ)) * cos(ϕ)) :(cos(ϕ)) 0 :x0 :y0 :z0 1 

正如某些人猜测的那样,这是一个转换矩阵,我们现在仅针对三维坐标将其分类。 我们将计算特定值:


 ϕ = 20*pi/180 x0 = 4 y0 = -0.5 z0 = 1.3 Xtr = [ eval(x) for x in X] Out[]: 4×4 Array{Real,2}: 0.939693 0.34202 0 0 -0.321394 0.883022 0.34202 0 0.116978 -0.321394 0.939693 0 4 -0.5 1.3 1 

该矩阵绕XZ轴旋转 20 circ并转移到 \ vec {R} = \ {x0,y0,z0 \}


 x = [-1 -1 1 1 -1 -1 1 1 -1 -1]; y = [-1 -1 -1 -1 -1 1 1 1 1 -1]; z = [1 -1 -1 1 1 1 1 -1 -1 -1] R = [ x' y' z' ones( length(x) ) ] plot(x', y', z', w = 3) 


 R2 = R*Xtr plot!( R2[:,1], R2[:,2], R2[:,3], w = 3) 


表达树的果实


正如我们已经看到的,字符串支持“插值”,这使我们能够轻松地从较小的组件创建大型字符串。


 x = "" print("$x $x $x  ...") Out[]:     ... 

带引号(引号)的同一个故事:


 x = :(6*7) y = :($x + $x) Out[]: :(6 * 7 + 6 * 7) eval(y) Out[]: 84 

万物之源


eval()不仅返回表达式的值。 让我们尝试引用函数声明:


 ex = :( cats() = println("Meow!") ) cats() Out[]: UndefVarError: cats not defined 

现在让她复活:


 eval(ex) Out[]: cats (generic function with 1 method) cats() Out[]: Meow! 

使用插值,我们可以动态地建立函数的定义。 实际上,我们可以立即执行许多功能。


 for name in [:dog, :bird, :mushroom] println(:($name() = println($("I'm $(name)!")))) end Out[]: dog() = begin #= In[27]:2 =# println("I'm dog!") end bird() = begin #= In[27]:2 =# println("I'm bird!") end mushroom() = begin #= In[27]:2 =# println("I'm mushroom!") end for name in [:dog, :bird, :mushroom] eval(:($name() = println($("I'm $(name)!")))) end dog() Out[]: I'm dog! mushroom() Out[]: I'm mushroom! 

当包装API(例如,从C库或通过HTTP)包装API时,这可能非常有用。 API通常会定义可用功能的列表,因此您可以捕获它们并立即创建整个外壳! 请参见Clang.jl,TensorFlow.jl或Basic Linear Algebra的示例。


原罪


这是一个更实际的例子。 考虑以下基于泰勒级数的sin()函数的定义:


sinx= sumk=1 infty frac1k1+2kx1+2k


 mysin(x) = sum((-1)^k/factorial(1+2*k) * x^(1+2k) for k = 0:5) mysin(0.5), sin(0.5) Out[]: (0.4794255386041834, 0.479425538604203) using BenchmarkTools @benchmark mysin(0.5) Out[]: BenchmarkTools.Trial: memory estimate: 112 bytes allocs estimate: 6 -------------- minimum time: 1.105 μs (0.00% GC) median time: 1.224 μs (0.00% GC) mean time: 1.302 μs (0.00% GC) maximum time: 9.473 μs (0.00% GC) -------------- samples: 10000 evals/sample: 10 

现在,它比可能要慢得多。 原因是我们迭代了k,这相对昂贵。 显式写入要快得多:


 mysin(x) = x - x^3/6 + x^5/120 # + ... 

但这写起来很乏味,不再看起来像原来的泰勒系列。 另外,树苗的风险很高。 有办法杀死两枪野兔吗? 朱莉娅(Julia)向我们写这段代码怎么样? 首先,考虑+函数的符号版本。


 plus(a, b) = :($a + $b) plus(1, 2) Out[]: :(1 + 2) 

使用plus()我们可以做很多有趣的事情,例如,符号和:


 reduce(+, 1:10) Out[]: 55 reduce(plus, 1:10) Out[]: :(((((((((1 + 2) + 3) + 4) + 5) + 6) + 7) + 8) + 9) + 10) eval(ans) Out[]: 55 reduce(plus, [:(x^2), :x, 1]) Out[]: :((x ^ 2 + x) + 1) 

这给了我们难题的重要组成部分,但我们还需要弄清楚我们要总结的内容。 让我们创建上述泰勒级数的符号版本,以内插k的值。


 k = 2 :($((-1)^k) * x^$(1+2k) / $(factorial(1+2k))) Out[]: :((1 * x ^ 5) / 120) 

现在,您可以像在传送带上一样铆接行中的元素:


 terms = [:($((-1)^k) * x^$(1+2k) / $(factorial(1+2k))) for k = 0:5] Out[]: 6-element Array{Expr,1}: :((1 * x ^ 1) / 1) :((-1 * x ^ 3) / 6) :((1 * x ^ 5) / 120) :((-1 * x ^ 7) / 5040) :((1 * x ^ 9) / 362880) :((-1 * x ^ 11) / 39916800) 

并总结一下:


 reduce(plus, ans) Out[]: :((((((1 * x ^ 1) / 1 + (-1 * x ^ 3) / 6) + (1 * x ^ 5) / 120) + (-1 * x ^ 7) / 5040) + (1 * x ^ 9) / 362880) + (-1 * x ^ 11) / 39916800) :(mysin(x) = $ans) Out[]: :(mysin(x) = begin #= In[52]:1 =# (((((1 * x ^ 1) / 1 + (-1 * x ^ 3) / 6) + (1 * x ^ 5) / 120) + (-1 * x ^ 7) / 5040) + (1 * x ^ 9) / 362880) + (-1 * x ^ 11) / 39916800 end) eval(ans) mysin(0.5), sin(0.5) Out[]: (0.4794255386041834, 0.479425538604203) @benchmark mysin(0.5) Out[]: BenchmarkTools.Trial: memory estimate: 0 bytes allocs estimate: 0 -------------- minimum time: 414.363 ns (0.00% GC) median time: 416.328 ns (0.00% GC) mean time: 431.885 ns (0.00% GC) maximum time: 3.352 μs (0.00% GC) -------------- samples: 10000 evals/sample: 201 

对于幼稚的实现来说还不错! 光子没有时间运行一百五十米,并且正弦已经计算出来!
现在该结束了。 对于那些想进一步梳理这个主题的人,我将为您提供一个在Jupyter中执行的交互式教程的建议, 该教程的翻译主要在此处使用,它是官方网站的视频课程 ,以及文档的相应部分 。 对于新来者,我建议您绕着枢纽走走,因为已经有可以接受的材料量。

Source: https://habr.com/ru/post/zh-CN431438/


All Articles