Julia Language
Iterables
Szukaj…
Składnia
- start (itr)
- dalej (itr, s)
- zrobione (itr, s)
- weź (itr, n)
- drop (itr, n)
- cykl (itr)
- Base.product (xs, ys)
Parametry
Parametr | Detale |
---|---|
Dla | Wszystkie funkcje |
itr | Iterowalny w obsłudze. |
Dla | next i done |
s | Stan iteratora opisujący bieżącą pozycję iteracji. |
Dla | take i drop |
n | Liczba elementów do pobrania lub upuszczenia. |
Dla | Base.product |
xs | Iterowalne do wzięcia pierwszych elementów par. |
ys | Iterowalne pobieranie drugich elementów par. |
... | (Zauważ, że product akceptuje dowolną liczbę argumentów; jeśli podano więcej niż dwa, utworzy krotki o długości większej niż dwa). |
Nowy typ iterowalny
W Julia, gdy zapętlenie poprzez iterowalny obiektu I
odbywa się za pomocą for
składni:
for i = I # or "for i in I" # body end
Za kulisami jest to tłumaczone na:
state = start(I) while !done(I, state) (i, state) = next(I, state) # body end
Dlatego jeśli chcesz, I
był iterowalny, musisz zdefiniować metody start
, next
i done
dla tego typu. Załóżmy, że zdefiniujesz typ Foo
zawierający tablicę jako jedno z pól:
type Foo bar::Array{Int,1} end
Tworzymy instancję obiektu Foo
, wykonując:
julia> I = Foo([1,2,3])
Foo([1,2,3])
julia> I.bar
3-element Array{Int64,1}:
1
2
3
Jeśli chcemy iterować przez Foo
, a każdy bar
elementów jest zwracany przez każdą iterację, definiujemy metody:
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
Zauważ, że ponieważ te funkcje należą do modułu Base
, musimy najpierw import
ich nazwy przed dodaniem do nich nowych metod.
Po zdefiniowaniu metod Foo
jest kompatybilny z interfejsem iteratora:
julia> for i in I println(i) end 1 2 3
Łączenie Lazy Iterables
Standardowa biblioteka zawiera bogatą kolekcję leniwych iteracji (a biblioteki takie jak Iterators.jl zapewniają jeszcze więcej). Leniwe iteracje można komponować, aby tworzyć mocniejsze iteracje w stałym czasie. Najważniejszymi leniwymi iterami są wzięcia i upuszczenia , z których można utworzyć wiele innych funkcji.
Lazily pokroić iterowalny
Tablice można kroić w notację plastra. Na przykład następujące zwraca 10 do 15 elementów tablicy, w tym:
A[10:15]
Notacja wycinka nie działa jednak ze wszystkimi iteracjami. Na przykład nie możemy pokroić wyrażenia generatora:
julia> (i^2 for i in 1:10)[3:5]
ERROR: MethodError: no method matching getindex(::Base.Generator{UnitRange{Int64},##1#2}, ::UnitRange{Int64})
Krojenie łańcuchów może nie mieć oczekiwanego zachowania Unicode:
julia> "αααα"[2:3]
ERROR: UnicodeError: invalid character index
in getindex(::String, ::UnitRange{Int64}) at ./strings/string.jl:130
julia> "αααα"[3:4]
"α"
Możemy zdefiniować funkcję lazysub(itr, range::UnitRange)
do wykonywania tego rodzaju lazysub(itr, range::UnitRange)
na dowolnych iteracjach. Jest to definiowane jako take
i drop
:
lazysub(itr, r::UnitRange) = take(drop(itr, first(r) - 1), last(r) - first(r) + 1)
Implementacja tutaj działa, ponieważ dla wartości UnitRange
a:b
wykonywane są następujące kroki:
- upuszcza pierwsze elementy
a-1
- wykonuje
a
elementu TH,a+1
tego elementu, i tak dalej, aż doa+(ba)=b
-tego elementu
Łącznie pobierane są elementy ba
. Możemy potwierdzić, że nasze wdrożenie jest poprawne w każdym przypadku powyżej:
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
Leniwie przesuwa iterowalny cyklicznie
circshift
operacji na macierzy przesuwa tablicy, jak gdyby były koło, następnie relinearize go. Na przykład,
julia> circshift(1:10, 3)
10-element Array{Int64,1}:
8
9
10
1
2
3
4
5
6
7
Czy możemy to zrobić leniwie dla wszystkich iteracji? Możemy użyć cycle
, drop
i take
iterable do wdrożenia tej funkcji.
lazycircshift(itr, n) = take(drop(cycle(itr), length(itr) - n), length(itr))
Oprócz tego, że leniwe typy są bardziej wydajne w wielu sytuacjach, pozwala nam to circshift
funkcje podobne do circshift
na typach, które w innym przypadku nie byłyby obsługiwane:
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"
Tworzenie tabliczki mnożenia
Stwórzmy tabliczkę mnożenia używając leniwych iterowalnych funkcji do stworzenia macierzy.
Kluczowe funkcje, których można tu użyć, to:
-
Base.product
, który oblicza iloczyn kartezjański . -
prod
, który oblicza zwykły produkt (jak w mnożeniu) -
:
, który tworzy zakres -
map
, która jest funkcją wyższego rzędu stosującą funkcję do każdego elementu kolekcji
Rozwiązaniem jest:
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
Listy leniwie oceniane
Możliwe jest utworzenie prostej leniwie ocenianej listy przy użyciu zmiennych typów i zamknięć . Leniwie oceniana lista to lista, której elementy nie są oceniane podczas konstruowania, ale raczej przy dostępie. Korzyści z leniwie ocenianych list obejmują możliwość bycia nieskończonym.
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()
Co rzeczywiście działa tak, jak w języku takim jak Haskell , w którym wszystkie listy są leniwie oceniane:
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
W praktyce lepiej jest użyć pakietu Lazy.jl. Jednak implementacja powyższej leniwej listy rzuca światło na ważne szczegóły dotyczące tego, jak zbudować własny typ iterowalny.