
Continuamos estudiando el joven y prometedor lenguaje de propósito general Julia . Esta vez prestaremos más atención a las líneas, comenzaremos tímidos pasos en el mundo de la metaprogramación y enseñaremos al intérprete a realizar operaciones simbólicas (solo hay dos imágenes debajo del corte, pero mucha azúcar sintáctica)
Líneas
Las variables de cadena se crean encerrando caracteres entre comillas dobles, los caracteres simples se configuran como caracteres individuales de tipo char . La concatenación de cadenas se realiza utilizando la multiplicación "*":
cats = 4 s1 = "How many cats "; s2 = "is too many cats?"; s3 = s1*s2 Out[]: "How many cats is too many cats?"
Es decir, la exponenciación actúa así:
s1^3 Out[]: "How many cats How many cats How many cats "
Por otro lado, las filas están indexadas:
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)
Las cadenas se pueden crear a partir de cadenas y otros tipos:
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."
Hay muchas funciones útiles en el arsenal, por ejemplo, buscar un elemento:
findfirst( isequal('o'), s4 ) Out[]: 4 findlast( isequal('o'), s4 ) Out[]: 26 findnext( isequal('o'), s4, 7 ) # Out[]: 11
Y así, puedes implementar fácilmente el Cifrado de César
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
Aquí, se produce una ejecución sobre todos los caracteres de la cadena, n se agrega al código de cada uno de ellos. Resulta una matriz de caracteres char que están unidos por la función prod () , que implementa la multiplicación de todos los elementos de la matriz recibida.
Computación de personajes
Julia ya tiene paquetes de diversos grados de preparación para cálculos simbólicos, por ejemplo:
pero de alguna manera aprendimos a pegar cadenas, por qué no implementar acciones simbólicas con matrices. Digamos que queríamos agregar dos matrices dispares:
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) ...
Para comenzar, ejecuta tus bolígrafos juguetones en los operadores básicos:
import Base: *, -, +
Si en C ++ dicha mutilación se llamaba sobrecarga del operador, entonces aquí simplemente agregamos un método para una función existente.
+(a::String, b::String) = a * "+" * b Out[]: + (generic function with 164 methods)
Más de mil quinientos métodos, y aquí hay otro que vivirá hasta el final de la sesión.
m1+m3 Out[]: 2×3 Array{Any,2}: 2 3 "a+ln(3)" 3 1 1
Ahora subporta la multiplicación, es decir, determinamos el caso de multiplicar una cadena por un número:
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)
Es decir, si la cadena se multiplica por cero, obtenemos cero, si es uno, entonces obtenemos la misma cadena, y en otros casos cegamos el número con el signo "*" y la cadena (agregamos corchetes para que no haya incertidumbres con los caracteres). Personalmente, me gusta la vista de una línea de la función, que es útil para el operador ternario:
*(a::String, b::Number) = (b==0) ? 0 : (b==1) ? a : "$b" * "*(" * a * ")" Out[]:
Expondremos los casos restantes a ejecuciones similares y obtendremos algo como:
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
¡Y calculemos el determinante! Pero dado que la función integrada es complicada, nuestros métodos no son suficientes para alimentarla con una matriz con filas; obtenemos un error. Significa que preparamos el nuestro usando el símbolo 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
Fórmula de producto mixto
puede usarse para encontrar el determinante de una matriz 3x3, entendiendo a , b , c como la primera, segunda y tercera fila (columna), respectivamente
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)"
Es fácil adivinar que para las matrices es más complicado, todo se verá muy no trivial: hay muchos corchetes y multiplicaciones de números, por lo que sería bueno hacer un par de funciones que conducen a la desfactorización. Esta será tu tarea.
Además, la multiplicación de matrices es más complicada:
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) ...
dará un error Negocio claro, para matrices con una dimensión de más de tres, se utilizan métodos más avanzados y complejos, cuyas llamadas no "sobrecargamos". Bueno, cubriremos los métodos ya existentes con nuestro propio multiplicador (esto, por supuesto, no se recomienda, especialmente en programas más o menos complejos, donde es probable que surjan conflictos internos).
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)
Ahora puedes multiplicar con seguridad:
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
Tengamos en cuenta esta matriz, pero por ahora, comenzaremos los primeros pasos en
Cotizaciones
Julia admite metaprogramación. Esto es similar a la programación simbólica, donde tratamos con expresiones (por ejemplo, 6 * 7) en lugar de valores (por ejemplo, 42). Al introducir operaciones en el intérprete, obtenemos inmediatamente el resultado, que, como hemos visto, se puede evitar con la ayuda de las líneas:
x = "6*7" Out[]: "6*7"
Las cadenas pueden convertirse en expresiones usando parse () , y luego evaluarse usando eval () :
eval(Meta.parse(ans)) Out[]: 42
¿Por qué todas estas dificultades? El truco es que cuando tenemos una expresión , podemos modificarla de varias maneras interesantes:
x = replace(x, "*" => "+") eval(Meta.parse(x)) Out[]: 13
Para evitar problemas con las líneas, se proporciona un operador de cara fruncida :()
y = :(2^8-1) Out[]: :(2^8-1) eval(y) Out[]: 255
Puede "citar" una expresión, función, bloque de código ...
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)
Y ahora analizaremos la matriz calculada previamente (es mejor comenzar una nueva sesión después de la sección anterior, mis sobrecargas feas pueden entrar fácilmente en conflicto con los paquetes de complementos):
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
Como algunos han adivinado, esta es una matriz de transformaciones, ya hemos resuelto esto , solo que ahora para coordenadas tridimensionales. Calcularemos para valores específicos:
ϕ = 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
Esta matriz gira alrededor de los ejes X y Z por y transferir a \ 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)

Fruto del árbol de expresión
Como ya hemos visto, las cadenas admiten "interpolación", lo que nos permite crear fácilmente cadenas grandes a partir de componentes más pequeños.
x = "" print("$x $x $x ...") Out[]: ...
Con comillas (comillas) - la misma historia:
x = :(6*7) y = :($x + $x) Out[]: :(6 * 7 + 6 * 7) eval(y) Out[]: 84
La raíz de todos los Eval
eval()
no solo devuelve el valor de una expresión. Intentemos citar la declaración de función:
ex = :( cats() = println("Meow!") ) cats() Out[]: UndefVarError: cats not defined
Ahora revívela:
eval(ex) Out[]: cats (generic function with 1 method) cats() Out[]: Meow!
Usando la interpolación, podemos construir la definición de una función sobre la marcha; de hecho, podemos hacer varias funciones de inmediato.
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!
Esto puede ser extremadamente útil cuando se ajusta una API (por ejemplo, desde una biblioteca C o mediante HTTP). Las API a menudo definen una lista de funciones disponibles, por lo que puede capturarlas y crear un shell completo a la vez. Ver ejemplos de Clang.jl, TensorFlow.jl o Basic Linear Algebra.
Pecado original
Aquí hay un ejemplo más práctico. Considere la siguiente definición de la función sin()
basada en la serie Taylor:
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
Ahora es mucho más lento de lo que podría ser. La razón es que iteramos sobre k, que es relativamente costoso. La escritura explícita es mucho más rápida:
mysin(x) = x - x^3/6 + x^5/120 # + ...
Pero esto es tedioso de escribir y ya no se parece a la serie original de Taylor. Además, existe un alto riesgo de retoños. ¿Hay alguna manera de matar a una liebre con dos disparos? ¿Qué tal si Julia nos escribe este código? Primero, considere la versión simbólica de la función +.
plus(a, b) = :($a + $b) plus(1, 2) Out[]: :(1 + 2)
Con plus()
podemos hacer muchas cosas interesantes, por ejemplo, una suma simbólica:
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)
Esto nos da una parte importante del rompecabezas, pero también tenemos que descubrir qué estamos resumiendo. Creemos una versión simbólica de la serie de Taylor anterior que interpola el valor de k.
k = 2 :($((-1)^k) * x^$(1+2k) / $(factorial(1+2k))) Out[]: :((1 * x ^ 5) / 120)
Ahora puede remachar los elementos de la fila como en un transportador:
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)
Y resumirlos:
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
¡No está mal para una implementación ingenua! Antes de que el fotón tenga tiempo de correr cien metros y medio, ¡y el seno ya ha sido calculado!
Este es el momento de terminar. Para aquellos que quieran resolver este tema, les aconsejaré un tutorial interactivo ejecutado en Jupyter , cuya traducción se usó principalmente aquí, un curso de video del sitio oficial y la sección correspondiente de la documentación. Para los recién llegados, le aconsejo que camine por el centro , ya que ya hay una cantidad aceptable de materiales.