Julia Language
confronti
Ricerca…
Sintassi
- x <y # se
x
è strettamente inferiore ay
- x> y # se
x
è strettamente maggiore diy
- x == y # se
x
è uguale ay
- x === y # in alternativa
x ≡ y
, sex
è uguale ay
- x ≤ y # in alternativa
x <= y
, sex
è minore o uguale ay
- x ≥ y # in alternativa
x >= y
, sex
è maggiore o uguale ay
- x ≠ y # in alternativa
x != y
, sex
non è uguale ay
- x ≈ y # se
x
è approssimativamente uguale ay
Osservazioni
Stai attento a lanciare i segni di confronto in giro. Julia definisce molte funzioni di confronto per impostazione predefinita senza definire la corrispondente versione capovolta. Ad esempio, si può correre
julia> Set(1:3) ⊆ Set(0:5)
true
ma non funziona
julia> Set(0:5) ⊇ Set(1:3)
ERROR: UndefVarError: ⊇ not defined
Confronti concatenati
Gli operatori di confronto multipli utilizzati insieme sono concatenati, come se fossero collegati tramite l' operatore &&
. Questo può essere utile per catene di confronto leggibili e matematicamente concise, come
# same as 0 < i && i <= length(A)
isinbounds(A, i) = 0 < i ≤ length(A)
# same as Set() != x && issubset(x, y)
isnonemptysubset(x, y) = Set() ≠ x ⊆ y
Tuttavia, c'è una differenza importante tra a > b > c
e a > b && b > c
; nel secondo caso, il termine b
viene valutato due volte. Questo non importa molto per i semplici simboli vecchi, ma potrebbe importare se i termini stessi hanno effetti collaterali. Per esempio,
julia> f(x) = (println(x); 2)
f (generic function with 1 method)
julia> 3 > f("test") > 1
test
true
julia> 3 > f("test") && f("test") > 1
test
test
true
Diamo un'occhiata più approfondita ai confronti concatenati e al loro funzionamento, osservando come vengono analizzati e ridotti in espressioni . Innanzitutto, considera il semplice confronto, che possiamo vedere solo una semplice chiamata di funzione vecchia:
julia> dump(:(a > b))
Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol >
2: Symbol a
3: Symbol b
typ: Any
Ora se eseguiamo il paragone, notiamo che l'analisi è cambiata:
julia> dump(:(a > b >= c))
Expr
head: Symbol comparison
args: Array{Any}((5,))
1: Symbol a
2: Symbol >
3: Symbol b
4: Symbol >=
5: Symbol c
typ: Any
Dopo l'analisi, l'espressione viene quindi abbassata nella sua forma finale:
julia> expand(:(a > b >= c))
:(begin
unless a > b goto 3
return b >= c
3:
return false
end)
e notiamo infatti che questo è lo stesso di a > b && b >= c
:
julia> expand(:(a > b && b >= c))
:(begin
unless a > b goto 3
return b >= c
3:
return false
end)
Numeri ordinali
Vedremo come implementare confronti personalizzati implementando un tipo personalizzato, numeri ordinali . Per semplificare l'implementazione, ci concentreremo su un piccolo sottoinsieme di questi numeri: tutti i numeri ordinali fino a ma non incluso ε₀. La nostra implementazione è focalizzata sulla semplicità, non sulla velocità; tuttavia, anche l'implementazione non è lenta.
Memorizziamo numeri ordinali secondo la loro forma normale di Cantor . Poiché l'aritmetica ordinale non è commutativa, adotteremo la convenzione comune di memorizzare prima i termini più significativi.
immutable OrdinalNumber <: Number
βs::Vector{OrdinalNumber}
cs::Vector{Int}
end
Poiché la forma normale di Cantor è unica, possiamo testare l'uguaglianza semplicemente attraverso l'uguaglianza ricorsiva:
Nella versione v0.5, c'è una sintassi molto bella per farlo in modo compatto:
import Base: ==
α::OrdinalNumber == β::OrdinalNumber = α.βs == β.βs && α.cs == β.cs
Altrimenti, definire la funzione come più tipica:
import Base: ==
==(α::OrdinalNumber, β::OrdinalNumber) = α.βs == β.βs && α.cs == β.cs
Per completare il nostro ordine, poiché questo tipo ha un ordine totale, dovremmo sovraccaricare la funzione isless
:
import Base: isless
function isless(α::OrdinalNumber, β::OrdinalNumber)
for i in 1:min(length(α.cs), length(β.cs))
if α.βs[i] < β.βs[i]
return true
elseif α.βs[i] == β.βs[i] && α.cs[i] < β.cs[i]
return true
end
end
return length(α.cs) < length(β.cs)
end
Per testare il nostro ordine, possiamo creare alcuni metodi per creare numeri ordinali. Zero, ovviamente, si ottiene non avendo termini nella forma normale di Cantor:
const ORDINAL_ZERO = OrdinalNumber([], [])
Base.zero(::Type{OrdinalNumber}) = ORDINAL_ZERO
Possiamo definire un expω
per calcolare ω^α
, e usarlo per calcolare 1 e ω:
expω(α) = OrdinalNumber([α], [1])
const ORDINAL_ONE = expω(ORDINAL_ZERO)
Base.one(::Type{OrdinalNumber}) = ORDINAL_ONE
const ω = expω(ORDINAL_ONE)
Ora abbiamo una funzione di ordinamento completamente funzionale sui numeri ordinali:
julia> ORDINAL_ZERO < ORDINAL_ONE < ω < expω(ω)
true
julia> ORDINAL_ONE > ORDINAL_ZERO
true
julia> sort([ORDINAL_ONE, ω, expω(ω), ORDINAL_ZERO])
4-element Array{OrdinalNumber,1}:
OrdinalNumber(OrdinalNumber[],Int64[])
OrdinalNumber(OrdinalNumber[OrdinalNumber(OrdinalNumber[],Int64[])],[1])
OrdinalNumber(OrdinalNumber[OrdinalNumber(OrdinalNumber[OrdinalNumber(OrdinalNumber[],Int64[])],[1])],[1])
OrdinalNumber(OrdinalNumber[OrdinalNumber(OrdinalNumber[OrdinalNumber(OrdinalNumber[OrdinalNumber(OrdinalNumber[],Int64[])],[1])],[1])],[1])
Nell'ultimo esempio, vediamo che la stampa dei numeri ordinali potrebbe essere migliore, ma il risultato è come previsto.
Operatori standard
Julia supporta un insieme molto grande di operatori di confronto. Questi includono
- Tutte le seguenti sequenze Unicode:
> < >= ≥ <= ≤ == === ≡ != ≠ !== ≢ ∈ ∉ ∋ ∌ ⊆ ⊈ ⊂ ⊄ ⊊ ∝ ∊ ∍ ∥ ∦ ∷ ∺ ∻ ∽ ∾ ≁ ≃ ≄ ≅ ≆ ≇ ≈ ≉ ≊ ≋ ≌ ≍ ≎ ≐ ≑ ≒ ≓ ≔ ≕ ≖ ≗ ≘ ≙ ≚ ≛ ≜ ≝ ≞ ≟ ≣ ≦ ≧ ≨ ≩ ≪ ≫ ≬ ≭ ≮ ≯ ≰ ≱ ≲ ≳ ≴ ≵ ≶ ≷ ≸ ≹ ≺ ≻ ≼ ≽ ≾ ≿ ⊀ ⊁ ⊃ ⊅ ⊇ ⊉ ⊋ ⊏ ⊐ ⊑ ⊒ ⊜ ⊩ ⊬ ⊮ ⊰ ⊱ ⊲ ⊳ ⊴ ⊵ ⊶ ⊷ ⋍ ⋐ ⋑ ⋕ ⋖ ⋗ ⋘ ⋙ ⋚ ⋛ ⋜ ⋝ ⋞ ⋟ ⋠ ⋡ ⋢ ⋣ ⋤ ⋥ ⋦ ⋧ ⋨ ⋩ ⋪ ⋫ ⋬ ⋭ ⋲ ⋳ ⋴ ⋵ ⋶ ⋷ ⋸ ⋹ ⋺ ⋻ ⋼ ⋽ ⋾ ⋿ ⟈ ⟉ ⟒ ⦷ ⧀ ⧁ ⧡ ⧣ ⧤ ⧥ ⩦ ⩧ ⩪ ⩫ ⩬ ⩭ ⩮ ⩯ ⩰ ⩱ ⩲ ⩳ ⩴ ⩵ ⩶ ⩷ ⩸ ⩹ ⩺ ⩻ ⩼ ⩽ ⩾ ⩿ ⪀ ⪁ ⪂ ⪃ ⪄ ⪅ ⪆ ⪇ ⪈ ⪉ ⪊ ⪋ ⪌ ⪍ ⪎ ⪏ ⪐ ⪑ ⪒ ⪓ ⪔ ⪕ ⪖ ⪗ ⪘ ⪙ ⪚ ⪛ ⪜ ⪝ ⪞ ⪟ ⪠ ⪡ ⪢ ⪣ ⪤ ⪥ ⪦ ⪧ ⪨ ⪩ ⪪ ⪫ ⪬ ⪭ ⪮ ⪯ ⪰ ⪱ ⪲ ⪳ ⪴ ⪵ ⪶ ⪷ ⪸ ⪹ ⪺ ⪻ ⪼ ⪽ ⪾ ⪿ ⫀ ⫁ ⫂ ⫃ ⫄ ⫅ ⫆ ⫇ ⫈ ⫉ ⫊ ⫋ ⫌ ⫍ ⫎ ⫏ ⫐ ⫑ ⫒ ⫓ ⫔ ⫕ ⫖ ⫗ ⫘ ⫙ ⫷ ⫸ ⫹ ⫺ ⊢ ⊣
; - Tutti i simboli nel punto 1, preceduti da un punto (
.
) Da rendere elementwise; - Gli operatori
<:
,>:
,.!
ein
, che non possono essere preceduti da un punto (.
).
Non tutti hanno una definizione nella libreria Base
standard. Tuttavia, sono disponibili per altri pacchetti da definire e utilizzare come appropriato.
Nell'uso quotidiano, la maggior parte di questi operatori di confronto non sono rilevanti. I più comuni utilizzati sono le funzioni matematiche standard per l'ordinazione; vedere la sezione Sintassi per un elenco.
Come la maggior parte degli altri operatori di Julia, gli operatori di confronto sono funzioni e possono essere chiamati come funzioni. Ad esempio, (<)(1, 2)
è identico nel significato a 1 < 2
.
Usando ==, === e isequal
Esistono tre operatori di uguaglianza: ==
, ===
e isequal
. (L'ultimo non è realmente un operatore, ma è una funzione e tutti gli operatori sono funzioni.)
Quando usare ==
==
è l'uguaglianza di valore . Restituisce true
quando due oggetti rappresentano, nel loro stato attuale, lo stesso valore.
Ad esempio, è ovvio che
julia> 1 == 1
true
ma inoltre
julia> 1 == 1.0
true
julia> 1 == 1.0 + 0.0im
true
julia> 1 == 1//1
true
I lati di destra di ogni uguaglianza sopra sono di un tipo diverso, ma rappresentano sempre lo stesso valore.
Per oggetti mutabili, come gli array , ==
confronta il loro valore attuale.
julia> A = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> B = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> C = [1, 3, 2]
3-element Array{Int64,1}:
1
3
2
julia> A == B
true
julia> A == C
false
julia> A[2], A[3] = A[3], A[2] # swap 2nd and 3rd elements of A
(3,2)
julia> A
3-element Array{Int64,1}:
1
3
2
julia> A == B
false
julia> A == C
true
Il più delle volte, ==
è la scelta giusta.
Quando usare ===
===
è un'operazione molto più rigida di ==
. Invece dell'uguaglianza di valore, misura l'eguaglianza. Due oggetti sono eguali se non possono essere distinti l'uno dall'altro dal programma stesso. Così abbiamo
julia> 1 === 1
true
in quanto non c'è modo di distinguere 1
dall'altra 1
. Ma
julia> 1 === 1.0
false
perché sebbene 1
e 1.0
abbiano lo stesso valore, sono di tipi diversi e quindi il programma può distinguerli.
Inoltre,
julia> A = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> B = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> A === B
false
julia> A === A
true
che a prima vista può sembrare sorprendente! Come potrebbe il programma distinguere tra i due vettori A
e B
? Poiché i vettori sono mutabili, potrebbe modificare A
e quindi si comporterebbe diversamente da B
Ma non importa come modifica A
, A
si comporterà sempre come A
stesso. Quindi A
è egale ad A
, ma non egale a B
Continuando su questa linea, osserva
julia> C = A
3-element Array{Int64,1}:
1
2
3
julia> A === C
true
Assegnando A
a C
, diciamo che C
ha alias A
Cioè, è diventato solo un altro nome per A
Anche le modifiche apportate ad A
saranno osservate da C
Pertanto, non c'è modo di dire la differenza tra A
e C
, quindi sono uguali.
Quando usare isequal
La differenza tra ==
e isequal
è molto sottile. La più grande differenza riguarda la modalità di gestione dei numeri in virgola mobile:
julia> NaN == NaN
false
Questo risultato, forse sorprendente, è definito dallo standard IEEE per i tipi a virgola mobile (IEEE-754). Ma questo non è utile in alcuni casi, come l'ordinamento. isequal
è fornito per quei casi:
julia> isequal(NaN, NaN)
true
Dall'altra parte dello spettro, ==
tratta lo zero negativo IEEE e lo zero positivo come lo stesso valore (anche come specificato da IEEE-754). Questi valori hanno rappresentazioni distinte in memoria, tuttavia.
julia> 0.0
0.0
julia> -0.0
-0.0
julia> 0.0 == -0.0
true
Ancora una volta per scopi di smistamento, isequal
distingue tra loro.
julia> isequal(0.0, -0.0)
false