Julia Language
tupler
Sök…
Syntax
- a,
- a, b
- a, b = xs
- ()
- (A,)
- (a, b)
- (a, b ...)
- Tuple {T, U, V}
- NTuple {N, T}
- Tuple {T, U, Vararg {V}}
Anmärkningar
Tuples har mycket bättre runtime-prestanda än matriser av två skäl: deras typer är mer exakta, och deras immutabilitet gör att de kan fördelas på stacken istället för högen. Men den mer exakta typen kommer med både mer kompileringstidskostnader och svårare att uppnå typstabilitet .
Introduktion till Tuples
Tuple
är oföränderliga ordnade samlingar av godtyckliga distinkta objekt, antingen av samma typ eller av olika typer . Vanligtvis är tuples konstruerade med (x, y)
syntax.
julia> tup = (1, 1.0, "Hello, World!")
(1,1.0,"Hello, World!")
De enskilda objekten i en tupel kan hämtas med indexeringssyntax:
julia> tup[1]
1
julia> tup[2]
1.0
julia> tup[3]
"Hello, World!"
De implementerar det iterabla gränssnittet och kan därför itereras över att använda for
slingor :
julia> for item in tup
println(item)
end
1
1.0
Hello, World!
Tuples stöder också en mängd olika generiska kollektionsfunktioner, till exempel reverse
eller length
:
julia> reverse(tup)
("Hello, World!",1.0,1)
julia> length(tup)
3
Dessutom stöder tuples en mängd olika ordningar med högre ordning , inklusive any
, all
, map
eller 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
Den tomma tupeln kan konstrueras med ()
:
julia> ()
()
julia> isempty(ans)
true
För att konstruera en tupel av ett element krävs emellertid en efterföljande komma. Detta beror på att parenteserna ( (
och )
) annars skulle behandlas som grupperingar i stället för att konstruera en tupel.
julia> (1)
1
julia> (1,)
(1,)
För konsistens tillåts också en efterföljande komma för tupler med mer än ett element.
julia> (1, 2, 3,)
(1,2,3)
Tupeltyper
typeof
en tupel är en subtyp av Tuple
:
julia> typeof((1, 2, 3))
Tuple{Int64,Int64,Int64}
julia> typeof((1.0, :x, (1, 2)))
Tuple{Float64,Symbol,Tuple{Int64,Int64}}
Till skillnad från andra datatyper är Tuple
typer kovarianta . Andra datatyper i Julia är i allmänhet invariant. Således,
julia> Tuple{Int, Int} <: Tuple{Number, Number}
true
julia> Vector{Int} <: Vector{Number}
false
Detta är fallet eftersom överallt en Tuple{Number, Number}
accepteras, också en Tuple{Int, Int}
, eftersom den också har två element, som båda är nummer. Det är inte fallet för en Vector{Int}
kontra en Vector{Number}
, eftersom en funktion som accepterar en Vector{Number}
kanske vill lagra en flytande punkt (t.ex. 1.0
) eller ett komplext nummer (t.ex. 1+3im
) i sådan en vektor.
Överensstämmelsen mellan tupeltyper innebär att Tuple{Number}
(igen till skillnad från Vector{Number}
) faktiskt är en abstrakt typ:
julia> isleaftype(Tuple{Number})
false
julia> isleaftype(Vector{Number})
true
Konkreta undertyper av Tuple{Number}
inkluderar Tuple{Int}
, Tuple{Float64}
, Tuple{Rational{BigInt}}
och så vidare.
Tuple
kan innehålla en avslutande Vararg
som deras sista parameter för att indikera ett obestämt antal objekt. Till exempel är Tuple{Vararg{Int}}
typen av alla tupler som innehåller valfritt antal Int
, eventuellt noll:
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
Medan Tuple{String, Vararg{Int}}
accepterar tuples som består av en sträng , följt av valfritt antal (möjligen noll) Int
.
julia> isa(("x", 1, 2), Tuple{String, Vararg{Int}})
true
julia> isa((1, 2), Tuple{String, Vararg{Int}})
false
Kombinerat med co-varians betyder detta att Tuple{Vararg{Any}}
beskriver vilken tupel som helst. Faktum är att Tuple{Vararg{Any}}
bara är ett annat sätt att säga Tuple
:
julia> Tuple{Vararg{Any}} == Tuple
true
Vararg
accepterar en andra parameter för numerisk typ som anger hur många gånger exakt den första typparametern ska inträffa. (Som standard, om ospecificerade, är denna andra typ parametern en typevar som kan ta vilket som helst värde, varför varje antal Int
s accepteras i Vararg
s ovan.) Tuple
typer som slutar i ett specificerat Vararg
automatiskt utvidgas till önskat antal element:
julia> Tuple{String,Vararg{Int, 3}}
Tuple{String,Int64,Int64,Int64}
Det finns notering för homogena tupler med en specifik Vararg
: NTuple{N, T}
. I denna notering betecknar N
antalet element i tupeln och T
anger den accepterade typen. Till exempel,
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)
Observera att NTuple
s utöver en viss storlek visas helt enkelt som NTuple{N, T}
, i stället för den utvidgade Tuple
formen, men de är fortfarande av samma typ:
julia> Tuple{Int,Int,Int,Int,Int,Int,Int,Int,Int,Int}
NTuple{10,Int64}
Skickas på tupeltyper
Eftersom Julia-funktionsparameterlistorna själva är tuplar, är det ofta lättare att skicka ut olika typer av tupplar genom själva metodparametrarna, ofta med liberal användning för operatören "splitting" ...
Tänk till exempel på implementeringen av reverse
för tuples, från Base
:
revargs() = ()
revargs(x, r...) = (revargs(r...)..., x)
reverse(t::Tuple) = revargs(t...)
Genomförande av metoder på tupplar på detta sätt bevarar typstabilitet , vilket är avgörande för prestanda. Vi kan se att det inte finns några omkostnader för den här metoden med hjälp av makro @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}
Även om det är svårt att läsa, är koden här helt enkelt att skapa en ny tupel med värden 3: e, 2: a och 1: a elementen i den ursprungliga tupeln. På många maskiner sammanställs detta till extremt effektiv LLVM-kod, som består av laster och butiker.
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
}
Flera returvärden
Tuples används ofta för flera värden på retur. Mycket av standardbiblioteket, inklusive två av funktionerna för det iterable gränssnittet ( next
och done
), returnerar tuples som innehåller två relaterade men distinkta värden.
Parenteserna kring tuplerna kan utelämnas i vissa situationer, vilket gör att flera värden för retur är enklare att implementera. Vi kan till exempel skapa en funktion för att returnera både positiva och negativa kvadratrötter av ett reellt tal:
julia> pmsqrt(x::Real) = sqrt(x), -sqrt(x)
pmsqrt (generic function with 1 method)
julia> pmsqrt(4)
(2.0,-2.0)
Destruktureringsuppdrag kan användas för att packa upp flera värden för retur. För att lagra kvadratroten i variablerna a
och b
räcker det att skriva:
julia> a, b = pmsqrt(9.0)
(3.0,-3.0)
julia> a
3.0
julia> b
-3.0
Ett annat exempel på detta är divrem
och fldmod
funktionerna, som gör en heltal (trunkering respektive floored), delning och återstående operation samtidigt:
julia> q, r = divrem(10, 3)
(3,1)
julia> q
3
julia> r
1