Julia Language
Iterables
Buscar..
Sintaxis
- empezar (itr)
- siguiente (itr, s)
- hecho (itr, s)
- tomar (itr, n)
- soltar (itr, n)
- ciclo (itr)
- Producto base (xs, ys)
Parámetros
Parámetro | Detalles |
---|---|
por | Todas las funciones |
itr | Lo iterable para operar. |
por | next y done |
s | Un estado de iterador que describe la posición actual de la iteración. |
por | take y drop |
n | El número de elementos a tomar o soltar. |
por | Base.product |
xs | Lo iterable para sacar los primeros elementos de las parejas. |
ys | Lo iterable para tomar segundos elementos de pares. |
... | (Tenga en cuenta que el product acepta cualquier número de argumentos; si se proporcionan más de dos, se construirán tuplas de longitud mayor que dos). |
Nuevo tipo iterable
En Julia, cuando se realiza un bucle a través de un objeto iterable I
se hace con la sintaxis for
:
for i = I # or "for i in I" # body end
Detrás de escena, esto se traduce a:
state = start(I) while !done(I, state) (i, state) = next(I, state) # body end
Por lo tanto, si desea I
sea un iterable, debe definir los métodos de start
, next
y done
para su tipo. Supongamos que define un tipo Foo
contiene una matriz como uno de los campos:
type Foo bar::Array{Int,1} end
Creamos una instancia de un objeto Foo
haciendo:
julia> I = Foo([1,2,3])
Foo([1,2,3])
julia> I.bar
3-element Array{Int64,1}:
1
2
3
Si queremos iterar a través de Foo
, con cada bar
elementos que devuelve cada iteración, definimos los métodos:
import Base: start, next, done start(I::Foo) = 1 next(I::Foo, state) = (I.bar[state], state+1) function done(I::Foo, state) if state == length(I.bar) return true end return false end
Tenga en cuenta que dado que estas funciones pertenecen al módulo Base
, primero debemos import
sus nombres antes de agregarles nuevos métodos.
Después de definir los métodos, Foo
es compatible con la interfaz del iterador:
julia> for i in I println(i) end 1 2 3
Combinando Lazy Iterables
La biblioteca estándar viene con una rica colección de iterables (y bibliotecas como Iterators.jl proporcionan aún más). Los iterables perezosos se pueden componer para crear iterables más potentes en tiempo constante. Los iterables perezosos más importantes son tomar y soltar , a partir de los cuales se pueden crear muchas otras funciones.
Perezamente cortar una iterable
Los arreglos pueden ser cortados con notación de corte. Por ejemplo, lo siguiente devuelve los elementos del 10 al 15 de una matriz, inclusive:
A[10:15]
Sin embargo, la notación de segmento no funciona con todos los iterables. Por ejemplo, no podemos cortar una expresión del generador:
julia> (i^2 for i in 1:10)[3:5]
ERROR: MethodError: no method matching getindex(::Base.Generator{UnitRange{Int64},##1#2}, ::UnitRange{Int64})
Cortar cadenas puede no tener el comportamiento esperado de Unicode:
julia> "αααα"[2:3]
ERROR: UnicodeError: invalid character index
in getindex(::String, ::UnitRange{Int64}) at ./strings/string.jl:130
julia> "αααα"[3:4]
"α"
Podemos definir una función lazysub(itr, range::UnitRange)
para hacer este tipo de corte en iterables arbitrarios. Esto se define en términos de take
y drop
:
lazysub(itr, r::UnitRange) = take(drop(itr, first(r) - 1), last(r) - first(r) + 1)
La implementación aquí funciona porque para el valor de UnitRange
a:b
, se realizan los siguientes pasos:
- Deja caer los primeros elementos a
a-1
- toma la
a
ésimo elemento,a+1
-ésimo elemento, y así sucesivamente, hasta que ela+(ba)=b
-ésimo elemento
En total, se toman elementos ba
. Podemos confirmar que nuestra implementación es correcta en cada caso anterior:
julia> collect(lazysub("αααα", 2:3))
2-element Array{Char,1}:
'α'
'α'
julia> collect(lazysub((i^2 for i in 1:10), 3:5))
3-element Array{Int64,1}:
9
16
25
Cambia perezosamente un iterable circularmente
La operación de cambio de circshift
en los arreglos cambiará el arreglo como si fuera un círculo, y luego lo volverá a alinear. Por ejemplo,
julia> circshift(1:10, 3)
10-element Array{Int64,1}:
8
9
10
1
2
3
4
5
6
7
¿Podemos hacer esto perezosamente para todos los iterables? Podemos usar el cycle
, drop
y take
iterables para implementar esta funcionalidad.
lazycircshift(itr, n) = take(drop(cycle(itr), length(itr) - n), length(itr))
Además de que los tipos perezosos son más circshift
en muchas situaciones, esto nos permite realizar circshift
función similar a la de circshift
en tipos que de otro modo no lo circshift
:
julia> circshift("Hello, World!", 3)
ERROR: MethodError: no method matching circshift(::String, ::Int64)
Closest candidates are:
circshift(::AbstractArray{T,N}, ::Real) at abstractarraymath.jl:162
circshift(::AbstractArray{T,N}, ::Any) at abstractarraymath.jl:195
julia> String(collect(lazycircshift("Hello, World!", 3)))
"ld!Hello, Wor"
Haciendo una tabla de multiplicar
Hagamos una tabla de multiplicar usando funciones iterables para crear una matriz.
Las funciones clave a utilizar aquí son:
-
Base.product
, que calcula un producto cartesiano . -
prod
, que calcula un producto regular (como en la multiplicación) -
:
, lo que crea un rango -
map
, que es una función de orden superior que aplica una función a cada elemento de una colección.
La solucion es:
julia> map(prod, Base.product(1:10, 1:10))
10×10 Array{Int64,2}:
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
Listas de pereza evaluadas
Es posible hacer una lista simple y perezosamente evaluada utilizando tipos y cierres mutables. Una lista evaluada perezosamente es una lista cuyos elementos no se evalúan cuando se construye, sino cuando se accede a ella. Los beneficios de las listas evaluadas perezosamente incluyen la posibilidad de ser infinitas.
import Base: getindex
type Lazy
thunk
value
Lazy(thunk) = new(thunk)
end
evaluate!(lazy::Lazy) = (lazy.value = lazy.thunk(); lazy.value)
getindex(lazy::Lazy) = isdefined(lazy, :value) ? lazy.value : evaluate!(lazy)
import Base: first, tail, start, next, done, iteratorsize, HasLength, SizeUnknown
abstract List
immutable Cons <: List
head
tail::Lazy
end
immutable Nil <: List end
macro cons(x, y)
quote
Cons($(esc(x)), Lazy(() -> $(esc(y))))
end
end
first(xs::Cons) = xs.head
tail(xs::Cons) = xs.tail[]
start(xs::Cons) = xs
next(::Cons, xs) = first(xs), tail(xs)
done(::List, ::Cons) = false
done(::List, ::Nil) = true
iteratorsize(::Nil) = HasLength()
iteratorsize(::Cons) = SizeUnknown()
Lo que de hecho funciona como lo haría en un lenguaje como Haskell , donde todas las listas se evalúan perezosamente:
julia> xs = @cons(1, ys)
Cons(1,Lazy(false,#3,#undef))
julia> ys = @cons(2, xs)
Cons(2,Lazy(false,#5,#undef))
julia> [take(xs, 5)...]
5-element Array{Int64,1}:
1
2
1
2
1
En la práctica, es mejor usar el paquete Lazy.jl. Sin embargo, la implementación de la lista perezosa de arriba arroja luces sobre detalles importantes sobre cómo construir el propio tipo de iterable.