In [7]:
println("Hola mundo")
In [4]:
using DataFrames
benchmark = readtable("../data/benchmark.csv", separator=';')
Out[4]:
In [11]:
using Plots
scatter(benchmark, :Benchmark, [:Julia, :Fortran, :Python, :R], yscale=:log10, ylab="Tiempo relativo a C")
Out[11]:
Julia fue diseñado desde su creación para ser un lenguaje de programación compilado en tiempo real (JIT por just-in-time).
No es necesario recurrir a código en C para obtener velocidad.
Los tipos de datos y métodos/funciones definidos por el usuario son tan rápidos como los que vienen en la biblioteca estándar. De hecho… La biblioteca estándar de Julia está escrita en Julia (es un lenguaje homoicónicos).
El secreto de Julia es que compila cada función de la manera más eficiente y específica para cada tipo de dato sobre la cual se usa o se define.
Cuando más de un método existe con el mismo nombre, Julia elige el método más específico para el tipo de sus argumentos, debido a su diseño basado en multiple dispatch.
La primera vez que se llama a una función, su tiempo de ejecución es más lento porque incluye el tiempo de compilación. Pero la segunda vez que se ejecuta, es casi tan rápida como si corriera en C (dado que ya fue compilada específicamente para el tipo de datos de sus argumentos)
In [61]:
@time sum(rand(100))
Out[61]:
In [62]:
@time sum(rand(100))
Out[62]:
Julia posee varios tipos de datos elementales, los más usados son:
In [63]:
for valor in [42, 1.0, true, 'A', "A", (true, false)]
dump(valor) #
end
In [13]:
lista = [1, λ, π, "Hola mundo"]
Out[13]:
In [14]:
typeof(lista)
Out[14]:
Si un array contiene siempre un mismo tipo de datos, su almacenamiento en memoria es más eficiente si lo declaramos. A su vez las funciones que lo utilizan se ejecutarán de manera más eficiente/rápida (porque el compilador puede predecir el tipo de datos que va a obtener de array).
In [64]:
[62, 95, 99, 30]
Out[64]:
In [65]:
identidad = Float64[62, 95, 99, 30]
Out[65]:
In [16]:
identidad[1] # Los Arrays se acceden desde 1
Out[16]:
In [17]:
identidad[2:3] # Es posible acceder usando rangos start:end
Out[17]:
In [18]:
identidad[end] = 100 # Es posible asignar un elemento a un índice en particular, "end" permite obtener el último ítem
identidad
Out[18]:
Es posible indexar un array usando otro array, por ejemplo usando arrays lógicos
In [19]:
usar = identidad .> 95.0 # .> compara el array elemento a elemento
Out[19]:
In [20]:
identidad[ usar ]
Out[20]:
Existen varias dequeue functions en Julia. Dado que modifican el array que reciben, por convención sus nombres terminan en !
, por ejemplo:
push! # Al final del array
pop!
shift! # Al inicio del array
unshift!
splice! # Toma un valor dentro del array
In [21]:
push!(identidad, 30)
identidad
Out[21]:
In [24]:
matrix = [ 0.5 0.6
0.7 0.8 ]
Out[24]:
In [25]:
matrix[2,1] # Fila 2, Columna 1
Out[25]:
In [26]:
matrix[3] # La matriz se almacena de manera continua en memoria; column-major order.
Out[26]:
In [93]:
dict = Dict('A'=>1, 'B'=>2)
Out[93]:
In [94]:
dict['A'] # Accede al valor de la llave 'A'
Out[94]:
In [95]:
dict['C'] = 30 # Agrega un nuevo par (Pair) llave => valor al diccionario
dict['A'] = 10 # Si la llave ya existe, el valor es reemplazado
dict
Out[95]:
In [96]:
dict['D'] # Error: 'D' no está en map
In [97]:
get(dict, 'D', 0) # Es posible usar get para definir un valor default que evite el error
Out[97]:
In [98]:
dict
Out[98]:
In [101]:
get!(dict, 'D', 0) # gest! es como get, pero agrega el valor si no se encuentra la llave
dict
Out[101]:
In [102]:
identidad = rand() * 100.0
Out[102]:
In [103]:
if identidad == 100.0
println("Idénticas")
elseif identidad >= 30 # Opcional
println("Homólogas")
else # Opcional
println("Twilight")
end
In [147]:
carpeta = "data"
archivos = readdir(carpeta)
Out[147]:
In [148]:
i = 1
while i <= length(archivos)
println(archivos[i])
i += 1 # i = i + 1
end
In [149]:
for archivo in archivos # for numero = numeros
println(archivo)
end
En Julia los for
s son reescritos como while
s, usando las funciones start
para inicializar la iteración, done
para testear si se alcanzó el final de la iteración y next
para obtener el valor de la iteración y el del próximo estado. Uno puede definir estas funciones para cualquier tipo propio que quiera hacer iterable.
In [150]:
state = start(archivos) # state = 1
while !done(archivos, state) # !( state > length(archivos) )
(archivo, state) = next(archivos, state) # archivos[state], state + 1
println(archivo)
end
In [151]:
len = length(archivos)
lista = Array(Int, len)
for i in 1:len
lista[i] = filesize(joinpath(carpeta, archivos[i])) # Tamaño en bytes
end
lista
Out[151]:
In [152]:
lista = [ filesize(joinpath(carpeta, nombre)) for nombre in archivos ]
Out[152]:
Los strings son secuencias finitas de caracteres. En sus principios la bioinformática se trató del análisis de secuencias de caracteres (utilizando la codificación ASCII de 8 bits), lo que hizo popular a Perl en el área. Julia, al igual que Perl, tiene un buen soporte para strings:
In [182]:
cadena_unicode = "∃x ∈ B ∧ x ∈ A"
Out[182]:
In [183]:
typeof(cadena_unicode)
Out[183]:
Es seguro iterar sobre un string (inmutable) para obtener sus caracteres. Si se quiere obtener un Vector{Char}
(Array
de una dimensión, mutable) se puede usar list comprehension o la función collect
.
In [184]:
for char in cadena_unicode
print(char)
end
In [185]:
collect(cadena_unicode) # [ char for char in cadena_unicode ]
Out[185]:
Sin embargo, acceder directamente a un string como si fuera un array no es una acción segura dado que un carácter puede estar codificado por más de un valor de 8 bits. Sólo es seguro hacer eso si la codificación es ASCII, dado que cada carácter está codificado por un sólo número entero de 8 bits. Pero no es seguro hacerlo para otras codificación. Por ejemplo, la codificación UTF-8 de ∃ (\exists<tab>
en la consola) requiere de tres valores de 8 bits:
In [188]:
isascii(cadena_unicode)
Out[188]:
In [190]:
for i in 1:length(cadena_unicode)
try
println(i, " ", cadena_unicode[i])
catch err
println(i, " ", err) # Error al acceder cadena_unicode[i]
end
end
In [153]:
ext_fasta = r"\.fasta$" # r"... permite escribir una expresión regular
Out[153]:
In [154]:
typeof(ext_fasta)
Out[154]:
In [155]:
for nombre in archivos
println(nombre, "\t:\t", ismatch(ext_fasta, nombre)) # ismatch es true si la regex está en el string
end
In [156]:
ismatch(r"^>\w{4}\.\w", ">2trx.A")
Out[156]:
In [158]:
captura = match(r"^>(\w{4})\.(\w)", ">2trx.A")
Out[158]:
In [159]:
if captura != nothing
println("PDB\t", captura[1]) # captura[1] == captura.captures[1]
println("Cadena\t", captura[2])
else
println("No es un PDB ID")
end
In [160]:
captura = match(r"^>(\w{4})\.(\w)", ">PF00085") # nothing no imprime nada en pantalla
In [162]:
if captura != nothing
println("PDB\t", captura[1])
println("Cadena\t", captura[2])
else
println("No cumple con el formato de pdb.cadena")
end
In [163]:
A, B = rand(1:6), rand(1:6)
"Su dado es $A, mientras el dado de IJulia es $B: $( A > B ? "usted gana" : A != B ? "IJulia gana" : "empate")"
Out[163]:
In [166]:
stream = open("data/PF09645_full.fasta", "r")
Out[166]:
In [167]:
for line in eachline(stream) # Itero para cada línea (incluye '\n')
print(line) # como println pero no agrega un salto de línea
end
In [169]:
close(stream)
open( … ) do … asegura que el archivo se cierre si ocurre algún error (implementa un try/catch)
In [170]:
open("data/PF09645_full.fasta", "r") do stream
for line in eachline(stream)
print(line)
end
end
In [171]:
function listaralineamientos(direccion, extension::Regex=r"\.fasta$"; vacios::Bool=false)
alns = ASCIIString[]
for nombre in readdir(direccion)
if ismatch(extension, nombre)
if vacios || filesize(joinpath(direccion, nombre)) >0
push!(alns, nombre)
end
end
end
alns
end
Out[171]:
In [174]:
methods(listaralineamientos)
Out[174]:
In [177]:
listaralineamientos("data")
Out[177]:
In [173]:
listaralineamientos("data", vacios=true)
Out[173]:
In [178]:
listaralineamientos("data", r"\.stockholm$")
Out[178]:
In [179]:
listarstockholm(carpeta) = listaralineamientos(carpeta, r"\.stockholm$")
Out[179]:
In [180]:
listarstockholm("data")
Out[180]:
In [216]:
"""
Documentación de Σ!
-------------------
La manera más simple de agregar un **docstring** es escribirlo en un *string*,
justo antes de la definición de la función.
**Se puede escribir en markdown!** 😄 ( ← *emoji* )
Σ! ( *← unicode* ) tiene el **keyword argument** `valor` que por defecto vale 1.
Simplemente suma `valor` a cada elemento del vector.
"""
function Σ!{T}(vector::Vector{T}; valor::T=one(T))
N = length(vector)
for i in 1:N
vector[i] += 1
end
vector
end
"Σ_simd! es como Σ!, pero usando `@simd` y `@inbounds` (*SIMD*: una instrucción, múltiples datos)"
function Σ_simd!{T}(vector::Vector{T}; valor::T=one(T))
N = length(vector)
@inbounds @simd for i in 1:N
vector[i] += 1
end
vector
end
Out[216]:
In [217]:
?Σ!
Out[217]:
In [218]:
?Σ_simd!
Out[218]:
In [221]:
vector = Array(Int, 200_000); # Con ; no muestra/imprime la salida
In [222]:
Σ!(vector) # compilar...
@elapsed Σ!(vector)
Out[222]:
In [223]:
Σ_simd!(vector)
@elapsed Σ_simd!(vector)
Out[223]:
In [204]:
file = joinpath("data", "benchmark.csv")
Out[204]:
In [205]:
run(`cat $file`)
In [212]:
comandos = pipeline(`cat $file`, `grep -v Benchmark`, `wc -l`)
Out[212]:
In [213]:
run(comandos)
In [214]:
readall(comandos)
Out[214]:
In [192]:
nprocs()
Out[192]:
In [193]:
addprocs(3)
Out[193]:
In [194]:
nprocs()
Out[194]:
In [195]:
@everywhere const times = [1 for i=1:8] # define la variable en todos los procesos
In [197]:
map(sleep, times) # Cada función es compilada la primera vez que se llama
@elapsed map(sleep, times)
Out[197]:
In [198]:
pmap(sleep, times)
@elapsed pmap(sleep, times)
Out[198]:
In [200]:
@time @parallel (+) for i in times
sleep(i)
i
end
Out[200]: