Julia Language
Krotki
Szukaj…
Składnia
- za,
- a, b
- a, b = xs
- ()
- (za,)
- (a, b)
- (a, b ...)
- Tuple {T, U, V}
- NTuple {N, T}
- Tuple {T, U, Vararg {V}}
Uwagi
Krotki mają znacznie lepszą wydajność środowiska wykonawczego niż tablice z dwóch powodów: ich typy są bardziej precyzyjne, a ich niezmienność umożliwia przydzielanie ich na stosie zamiast na stosie. Jednak to bardziej precyzyjne pisanie wiąże się zarówno z większym nakładem czasu kompilacji, jak i trudnościami w osiągnięciu stabilności tekstu.
Wprowadzenie do krotek
Tuple
są niezmiennie uporządkowanymi kolekcjami dowolnych odrębnych obiektów, tego samego lub różnych typów . Krotki są zwykle tworzone przy użyciu składni (x, y)
.
julia> tup = (1, 1.0, "Hello, World!")
(1,1.0,"Hello, World!")
Poszczególne obiekty krotki można odzyskać za pomocą składni indeksowania:
julia> tup[1]
1
julia> tup[2]
1.0
julia> tup[3]
"Hello, World!"
Implementują iterowalny interfejs i dlatego można je powtarzać za pomocą pętli for
:
julia> for item in tup
println(item)
end
1
1.0
Hello, World!
Krotki obsługują również różne ogólne funkcje kolekcji, takie jak reverse
lub length
:
julia> reverse(tup)
("Hello, World!",1.0,1)
julia> length(tup)
3
Ponadto krotki obsługują różne operacje związane z kolekcjami wyższego rzędu , w tym any
, all
, map
lub broadcast
:
julia> map(typeof, tup)
(Int64,Float64,String)
julia> all(x -> x < 2, (1, 2, 3))
false
julia> all(x -> x < 4, (1, 2, 3))
true
julia> any(x -> x < 2, (1, 2, 3))
true
Pustą krotkę można zbudować za pomocą ()
:
julia> ()
()
julia> isempty(ans)
true
Jednak aby zbudować krotkę jednego elementu, wymagany jest przecinek końcowy. Wynika to z tego, że nawiasy ( (
i )
) byłyby inaczej traktowane jako operacje grupujące razem zamiast tworzenia krotki.
julia> (1)
1
julia> (1,)
(1,)
W celu zachowania spójności dozwolony jest także przecinek końcowy dla krotek z więcej niż jednym elementem.
julia> (1, 2, 3,)
(1,2,3)
Typy krotek
typeof
krotki jest podtypem Tuple
:
julia> typeof((1, 2, 3))
Tuple{Int64,Int64,Int64}
julia> typeof((1.0, :x, (1, 2)))
Tuple{Float64,Symbol,Tuple{Int64,Int64}}
W przeciwieństwie do innych typów danych, typy Tuple
są kowariantne . Inne typy danych w Julii są zazwyczaj niezmienne. A zatem,
julia> Tuple{Int, Int} <: Tuple{Number, Number}
true
julia> Vector{Int} <: Vector{Number}
false
Dzieje się tak, ponieważ wszędzie Tuple{Number, Number}
jest Tuple{Number, Number}
, podobnie jak Tuple{Int, Int}
, ponieważ ma ona również dwa elementy, z których oba są liczbami. Nie jest tak w przypadku Vector{Int}
kontra Vector{Number}
, ponieważ funkcja akceptująca Vector{Number}
może chcieć przechowywać zmiennoprzecinkowy (np. 1.0
) lub liczbę zespoloną (np. 1+3im
) wektor.
Kowariancja typów krotek oznacza, że Tuple{Number}
(znowu w przeciwieństwie do Vector{Number}
) jest w rzeczywistości typem abstrakcyjnym:
julia> isleaftype(Tuple{Number})
false
julia> isleaftype(Vector{Number})
true
Konkretne podtypy Tuple{Number}
obejmują Tuple{Int}
, Tuple{Float64}
, Tuple{Rational{BigInt}}
i tak dalej.
Typy Tuple
mogą zawierać kończący Vararg
jako ostatni parametr wskazujący nieokreśloną liczbę obiektów. Na przykład Tuple{Vararg{Int}}
jest rodzajem wszystkich krotek zawierających dowolną liczbę Int
, prawdopodobnie zero:
julia> isa((), Tuple{Vararg{Int}})
true
julia> isa((1,), Tuple{Vararg{Int}})
true
julia> isa((1,2,3,4,5), Tuple{Vararg{Int}})
true
julia> isa((1.0,), Tuple{Vararg{Int}})
false
podczas gdy Tuple{String, Vararg{Int}}
akceptuje krotki składające się z łańcucha , po którym następuje dowolna liczba (ewentualnie zero) Int
.
julia> isa(("x", 1, 2), Tuple{String, Vararg{Int}})
true
julia> isa((1, 2), Tuple{String, Vararg{Int}})
false
W połączeniu z ko-wariancją oznacza to, że Tuple{Vararg{Any}}
opisuje każdą krotkę. Rzeczywiście, Tuple{Vararg{Any}}
to tylko inny sposób na powiedzenie Tuple
:
julia> Tuple{Vararg{Any}} == Tuple
true
Vararg
akceptuje drugi parametr typu numerycznego wskazujący, ile razy dokładnie powinien wystąpić jego pierwszy parametr typu. (Domyślnie, jeśli nie jest określony, ten drugi typ parametru jest typem maszynowym, który może przyjmować dowolną wartość, dlatego dowolna liczba Int
jest akceptowana w Vararg
powyżej.) Typy Tuple
kończące się na określonym Vararg
zostaną automatycznie rozwinięte do żądana liczba elementów:
julia> Tuple{String,Vararg{Int, 3}}
Tuple{String,Int64,Int64,Int64}
Istnieje notacja dla krotek jednorodnych z określonym Vararg
: NTuple{N, T}
. W tej notacji N
oznacza liczbę elementów w krotce, a T
oznacza zaakceptowany typ. Na przykład,
julia> NTuple{3, Int}
Tuple{Int64,Int64,Int64}
julia> NTuple{10, Int}
NTuple{10,Int64}
julia> ans.types
svec(Int64,Int64,Int64,Int64,Int64,Int64,Int64,Int64,Int64,Int64)
Zauważ, że NTuple
poza określonym rozmiarem są pokazane po prostu jako NTuple{N, T}
, zamiast rozszerzonej formy Tuple
, ale wciąż są tego samego typu:
julia> Tuple{Int,Int,Int,Int,Int,Int,Int,Int,Int,Int}
NTuple{10,Int64}
Wysyłka na krotki
Ponieważ listy parametrów funkcji Julii same są krotkami, wysyłanie różnych rodzajów krotek jest często łatwiejsze dzięki samym parametrom metody, często przy swobodnym użyciu operatora „splatting” ...
Rozważmy na przykład implementację reverse
dla krotek z Base
:
revargs() = ()
revargs(x, r...) = (revargs(r...)..., x)
reverse(t::Tuple) = revargs(t...)
Implementowanie metod krotek w ten sposób zachowuje stabilność typu , co ma kluczowe znaczenie dla wydajności. Widzimy, że nie ma narzutu na to podejście przy użyciu makra @code_warntype
:
julia> @code_warntype reverse((1, 2, 3))
Variables:
#self#::Base.#reverse
t::Tuple{Int64,Int64,Int64}
Body:
begin
SSAValue(1) = (Core.getfield)(t::Tuple{Int64,Int64,Int64},2)::Int64
SSAValue(2) = (Core.getfield)(t::Tuple{Int64,Int64,Int64},3)::Int64
return (Core.tuple)(SSAValue(2),SSAValue(1),(Core.getfield)(t::Tuple{Int64,Int64,Int64},1)::Int64)::Tuple{Int64,Int64,Int64}
end::Tuple{Int64,Int64,Int64}
Chociaż nieco trudny do odczytania, kod tutaj po prostu zaczyna tworzyć nową krotkę z wartościami odpowiednio 3, 2 i 1 elementu oryginalnej krotki. Na wielu komputerach kompiluje się to do niezwykle wydajnego kodu LLVM, który składa się z obciążeń i zapasów.
julia> @code_llvm reverse((1, 2, 3))
define void @julia_reverse_71456([3 x i64]* noalias sret, [3 x i64]*) #0 {
top:
%2 = getelementptr inbounds [3 x i64], [3 x i64]* %1, i64 0, i64 1
%3 = getelementptr inbounds [3 x i64], [3 x i64]* %1, i64 0, i64 2
%4 = load i64, i64* %3, align 1
%5 = load i64, i64* %2, align 1
%6 = getelementptr inbounds [3 x i64], [3 x i64]* %1, i64 0, i64 0
%7 = load i64, i64* %6, align 1
%.sroa.0.0..sroa_idx = getelementptr inbounds [3 x i64], [3 x i64]* %0, i64 0, i64 0
store i64 %4, i64* %.sroa.0.0..sroa_idx, align 8
%.sroa.2.0..sroa_idx1 = getelementptr inbounds [3 x i64], [3 x i64]* %0, i64 0, i64 1
store i64 %5, i64* %.sroa.2.0..sroa_idx1, align 8
%.sroa.3.0..sroa_idx2 = getelementptr inbounds [3 x i64], [3 x i64]* %0, i64 0, i64 2
store i64 %7, i64* %.sroa.3.0..sroa_idx2, align 8
ret void
}
Wiele zwracanych wartości
Krotki są często używane do wielu zwracanych wartości. Znaczna część standardowej biblioteki, w tym dwie funkcje iterowalnego interfejsu ( next
i done
), zwraca krotki zawierające dwie powiązane, ale odrębne wartości.
W niektórych sytuacjach nawiasy wokół krotek można pominąć, co ułatwia wdrożenie wielu wartości zwracanych. Na przykład możemy utworzyć funkcję zwracającą zarówno dodatnie, jak i ujemne pierwiastki kwadratowe liczby rzeczywistej:
julia> pmsqrt(x::Real) = sqrt(x), -sqrt(x)
pmsqrt (generic function with 1 method)
julia> pmsqrt(4)
(2.0,-2.0)
Przypisania destrukcyjnego można użyć do rozpakowania wielu zwracanych wartości. Aby zapisać pierwiastkowania w zmiennych i a
b
, wystarczy napisać:
julia> a, b = pmsqrt(9.0)
(3.0,-3.0)
julia> a
3.0
julia> b
-3.0
Innym przykładem tego są funkcje divrem
i fldmod
, które wykonują divrem
na divrem
całkowitych (odpowiednio obcinanie lub zmiennoprzecinkowe) i pozostałych operacji jednocześnie:
julia> q, r = divrem(10, 3)
(3,1)
julia> q
3
julia> r
1