= 1 a
1
Algunos conceptos básicos
Para crear una variable, le asignamos un valor. Julia detecta por defecto elemento introducido.
Podemos añadir enteros, reales, o vectores de manera sencilla
= 1 a
1
@show typeof(a);
typeof(a) = Int64
= 1.0 b
1.0
@show typeof(b);
typeof(b) = Float64
Aunque podemos especificar el tipo de float manualmente, por ejemplo un tipo float más corto
= Float32(1.0)
b2 @show b2, typeof(b2);
(b2, typeof(b2)) = (1.0f0, Float32)
Hay que ser cuidado con los valores máximos
@show typemax(Int64);
typemax(Int64) = 9223372036854775807
El tipo Float
incluye Inf
, de modo que
@show typemax(Float64);
typemax(Float64) = Inf
Pero tenemos una opción elegante para comprobar que
@show prevfloat(typemax(Float64));
prevfloat(typemax(Float64)) = 1.7976931348623157e308
Y con algunos compartamiento inesperados como integer overflow
@show typemax(Int64)
@show typemax(Int64) + 1;
typemax(Int64) = 9223372036854775807
typemax(Int64) + 1 = -9223372036854775808
Para evitarlo se puede usar el tipo BigInt
, que no tiene máximo
Muy útil, por ejemplo, para
@show factorial(BigInt(30));
factorial(BigInt(30)) = 265252859812191058636308480000000
Si preguntamos quien es el máximo, tendremos un error
@show typemax(BigInt);
LoadError: MethodError: no method matching typemax(::Type{BigInt})
[0mClosest candidates are:
[0m typemax([91m::Union{Dates.DateTime, Type{Dates.DateTime}}[39m) at /Applications/Julia-1.8.app/Contents/Resources/julia/share/julia/stdlib/v1.8/Dates/src/types.jl:453
[0m typemax([91m::Union{Dates.Date, Type{Dates.Date}}[39m) at /Applications/Julia-1.8.app/Contents/Resources/julia/share/julia/stdlib/v1.8/Dates/src/types.jl:455
[0m typemax([91m::Union{Dates.Time, Type{Dates.Time}}[39m) at /Applications/Julia-1.8.app/Contents/Resources/julia/share/julia/stdlib/v1.8/Dates/src/types.jl:457
[0m ...
De forma similar existe el tipo BigFloat
de números arbitrariamente grandes. Es necesario tener cuidado pues estos números son muy pesados de almacenar.
= big(1.0)
a @show typeof(a);
typeof(a) = BigFloat
@show typemax(BigFloat)
@show prevfloat(typemax(BigFloat));
typemax(BigFloat) = Inf
prevfloat(typemax(BigFloat)) = 5.875653789111587590936911998878442589938516392745498308333779606469323584389875e+1388255822130839282
Lidiamos también con strings
(cadenas de cateres)
= "hola"
palabra @show typeof(palabra);
typeof(palabra) = String
Añadimos también vectores
= [2.1,3.2] c
2-element Vector{Float64}:
2.1
3.2
Y accedemos a la componentes
2] c[
3.2
Esta sintaxis también funciona con strings
3] palabra[
'l': ASCII/Unicode U+006C (category Ll: Letter, lowercase)
Nótese que el vector sabe qué tipo de elementos contiene
Podemos introducir vectores
1, 2] [
2-element Vector{Int64}:
1
2
y matrices
= [1 2; 3 4] A
2×2 Matrix{Int64}:
1 2
3 4
No se deben confundir vectores con matrices fila
1 2] [
1×2 Matrix{Int64}:
1 2
y matrices columna
reshape([1,2],2,1)
2×1 Matrix{Int64}:
1
2
En Julia los vectores son, por defecto, columnas
*c A
2-element Vector{Float64}:
8.5
19.1
'*A c
1×2 adjoint(::Vector{Float64}) with eltype Float64:
11.7 17.0
Y multiplicar en sentido contrario producirá un error
*A c
LoadError: DimensionMismatch: matrix A has dimensions (2,1), matrix B has dimensions (2,2)
A diferencia de en C, se puede añadir elementos a un vector
= [1,2]
a append!(a,3)
3-element Vector{Int64}:
1
2
3
En este caso la notación es más correcta que, por ejemplo, la notación de MATLAB a = [a,1]
Usando esta notación en Julia obtenemos lo que hemos pedimos:
= [1,2]
a = [a,1] a
2-element Vector{Any}:
[1, 2]
1
En Julia, un vector puede contener elementos de varios tipos
Se pueden concatenar vectores con ;
= [1, 2]
v [v;v]
4-element Vector{Int64}:
1
2
1
2
Nótese la diferencia con
= [1 2]
v [v;v]
2×2 Matrix{Int64}:
1 2
1 2
Para concatenar vectores y matrices podemos usar hcat
y vcat
= [1 2; 3 4]
A @show hcat(A, A)
@show [A A];
hcat(A, A) = [1 2 1 2; 3 4 3 4]
[A A] = [1 2 1 2; 3 4 3 4]
Esta sintaxis es equivalente a [A A]
vcat(A, A)
@show [A; A]
[A; A] = [1 2; 3 4; 1 2; 3 4]
4×2 Matrix{Int64}:
1 2
3 4
1 2
3 4
Recordamos que en C, o en MATLAB, el siguiente código
int a = 1
int b = a
a = 2
Se queda en estado a=2
y b=1
En Julia (y en Python) también es así asignaciones completas tanto en escalares
= 1
a = a
b = 2
a @show a,b;
(a, b) = (2, 1)
como en vectores
= [1,1]
a = a
b = [2,1]
a @show a,b;
(a, b) = ([2, 1], [1, 1])
Pero si modificamos una componente
= [1,1]
a = a
b 1] = 2
a[@show a,b;
(a, b) = ([2, 1], [2, 1])
Esto se llama “pass-by-sharing”. Es el comportamiento por defecto de las funciones en Julia. Apéndice
Si queremos evitarlo debemos usar la opción copy
= [1.0,1.0]
a = copy(a)
b 1] = 2.0
a[@show a,b;
(a, b) = ([2.0, 1.0], [1.0, 1.0])
= [1 2]
a = [3 4]
b = b
c = a
c @show b;
b = [3 4]
Primero compartimos b en c, y luego a en c.
La notación .=
asigna puntualmente:
= [1 2]
a = [3 4]
b = b
c .= a
c @show b;
b = [1 2]
Hecha una primera introducción, es recomendable revisar la documentación de Julia
Podemos hacer funciones escalares
= 2.0;
a ^2 a
4.0
@show sqrt(a) , exp(a) , sin(a) ;
(sqrt(a), exp(a), sin(a)) = (1.4142135623730951, 7.38905609893065, 0.9092974268256817)
Y todas estas funciones se pueden aplicar puntualmente a vectores o matrices, indicando que la operación es puntual
= [1,2];
v .^2 v
2-element Vector{Int64}:
1
4
@show sqrt.(v) , exp.(v) , sin.(v) ;
(sqrt.(v), exp.(v), sin.(v)) = ([1.0, 1.4142135623730951], [2.718281828459045, 7.38905609893065], [0.8414709848078965, 0.9092974268256817])
El usuario puede también definir funciones.
Bien de la manera convencional
function suma(x,y)
+y
xend
suma(1,2)
3
O en línea
suma(x,y) = x+y
suma(1,2)
3
Como es natural, esta función no se comportará de manera natural si le pasamos texto
suma("hola, ", "majo")
"hola, majo"
Sin embargo, podemos hacer la función comportarse distinto según el tipo de entradas que tenga. Por ejemplo, es “natural” que suma
concatene cadenas de texto
suma(x::String,y::String) = x * y
suma("aquí ", "estoy")
"aquí estoy"
Pero sigue haciendo lo que debe con números
suma(1,2)
3
Volveremos sobre esta idea más adelante.
function g(x ; n=0 , m=1)
if n==0
return x
else
return x+m
end
end
@show g(1)
@show g(1;n=1)
@show g(1;n=1,m=2)
g(1) = 1
g(1; n = 1) = 2
g(1; n = 1, m = 2) = 3
3
Hay que ser cuidadoso con los argumentos de funciones.
De acuerdo con la documentación de Julia:
Julia function arguments follow a convention sometimes called “pass-by-sharing”, which means that values are not copied when they are passed to functions. Function arguments themselves act as new variable bindings (new locations that can refer to values), but the values they refer to are identical to the passed values. Modifications to mutable values (such as Arrays) made within a function will be visible to the caller. This is the same behavior found in Scheme, most Lisps, Python, Ruby and Perl, among other dynamic languages.
Las variables de tipo Float
e Int
no cambian dentro de funciones
function AumentarUno(a)
+= 1.0;
a return a
end
= 2.0
a display("Primero a = $a.")
display("Calculamos AumentarUno(a) = $(AumentarUno(a))")
display("Ahora a = $a")
"Primero a = 2.0."
"Calculamos AumentarUno(a) = 3.0"
"Ahora a = 2.0"
Se suele decir que Float64 es immutable
, pues modificarlos dentro de funciones no afecta a su valor exterior
Los vectores y matrices, sin embargo, son mutable
.
Veamos en este ejemplo
function AumentarUnoVectorial(v)
= v .+ 1;
v return v
end;
= [1,2];
v
@show v
@show AumentarUnoVectorial(v)
@show v;
v = [1, 2]
AumentarUnoVectorial(v) = [2, 3]
v = [1, 2]
El motivo de esto es que los vectores y matrices suelen ser estructuradas pesadas, y no es recomendable copiarlas por defecto.
La convención es añadir !
al final de la función si modifica su argumento.
Así, el nombre correcto de la función es AumentarUnoVectorial!
Para evitar esta comportamiento, que es por defecto, se puede pasar una copia a la función. Por ejemplo
function AumentarUnoVectorial!(v)
.= v .+ 1;
v return v
end
= [1,2];
v @show v
@show AumentarUnoVectorial!(copy(v))
@show v;
v = [1, 2]
AumentarUnoVectorial!(copy(v)) = [2, 3]
v = [1, 2]
Finalmente cabe resaltar que hay que tener cuidado con el orden de las operaciones en display.
Primero se hacen todos los cálculos, y luego se muestran. Por ejemplo
= [1,2];
v @show v
@show AumentarUnoVectorial!(v)
@show v;
v = [1, 2]
AumentarUnoVectorial!(v) = [2, 3]
v = [2, 3]
Podemos hacer comprobaciones lógicas sencillas
= 1; b = 2;
a == b a
false
@show a < b, a <= b, a != b;
(a < b, a <= b, a != b) = (true, true, true)
if x ≤ 1
x else
+1
xend
La sintaxis if/then/else
tiene una sintaxis abreviada
g(n) = x ≤ 1 ? x : x+1
if x ≤ 1
x elseif x ≤ 2
+1
xelse
+ 2
x end
En Julia podemos crear bucles de tipo for
y while
.
La sintaxis 1:3
corresponde al vector [1,2,3]
for i=1:3
@show i
end
i = 1
i = 2
i = 3
=1
iwhile i<=3
@show i
= i+1;
i end
i = 1
i = 2
i = 3
Y hay algunos comportamientos avanzados
= [4.0,5.1,6.0]
x
for (index, value) in enumerate(x)
display("El valor de x en el índice $index es $value")
end
"El valor de x en el índice 1 es 4.0"
"El valor de x en el índice 2 es 5.1"
"El valor de x en el índice 3 es 6.0"
Ejercicio.
Calcular los cinco primeros términos de la sucesión de Fibonacci \[a_0 = 1, \qquad \qquad a_1 = 1, \qquad \qquad a_{n} = a_{n-1} + a_{n-2}.\] Almacenarlos en un vector.
= 5;
N = Vector{Int}(undef, N)
Fib 1] = 1
Fib[2] = 1
Fib[for n=3:N
= Fib[n-1] + Fib[n-2];
Fib[n] end
display(Fib)
5-element Vector{Int64}:
1
1
2
3
5
Una formulación “mágica”
fib(n::Integer) = n ≤ 2 ? one(n) : fib(n-1) + fib(n-2)
@show fib(5);
fib(5) = 5
Ejercicio.
Calcular los términos de la sucesión de Fibonacci hasta el primero que sea mayor que 25.
Almacenarlos en un vector.
= [1,1]
Fib while Fib[end] <= 25
append!(Fib, Fib[end] + Fib[end-1]);
end
Fib
9-element Vector{Int64}:
1
1
2
3
5
8
13
21
34
Sin embargo, es preferible reservar el tamaño de memoria suficiente para la variable.
function Fibonacci1(N)
= Vector{BigInt}(undef, N)
Fib 1] = 1
Fib[2] = 1
Fib[for n=3:N
= Fib[n-1] + Fib[n-2];
Fib[n] end
return Fib[N]
end
@time aN = Fibonacci1(200);
0.000025 seconds (401 allocations: 12.055 KiB)
function Fibonnaci2(M)
= [BigInt(1), BigInt(1)]
Fib while Fib[end] < M
append!(Fib, Fib[end] + Fib[end-1]);
end
end
@time Fibonnaci2(aN)
0.006980 seconds (10.03 k allocations: 490.464 KiB, 99.35% compilation time)
The strongest legacy of Lisp in the Julia language is its metaprogramming support. Like Lisp, Julia represents its own code as a data structure of the language itself. Since code is represented by objects that can be created and manipulated from within the language, it is possible for a program to transform and generate its own code. This allows sophisticated code generation without extra build steps, and also allows true Lisp-style macros operating at the level of abstract syntax trees. In contrast, preprocessor “macro” systems, like that of C and C++, perform textual manipulation and substitution before any actual parsing or interpretation occurs. Because all data types and code in Julia are represented by Julia data structures, powerful reflection capabilities are available to explore the internals of a program and its types just like any other data.
= "1 + 1"
prog
= Meta.parse(prog) ex1
:(1 + 1)
typeof(ex1)
Expr
eval(ex1)
2
ex1.head
:call
ex1.args
3-element Vector{Any}:
:+
1
1
= Expr(:call, :+, 1, 1)
ex2 == ex2 ex1
true
macro sayhello()
return :( println("Hello, world!") )
end
@sayhello (macro with 2 methods)
@sayhello()
Hello, world!
macro sayhello(name)
return :( println("Hello, ", $name) )
end
@sayhello (macro with 2 methods)
@sayhello("human")
Hello, human