Már régen, a Vectors fogalomban megjegyeztük, hogy „a tömbök tetszőleges méretűek lehetnek (csak a hardver memóriakorlátai szabnak határt), és tetszőleges számú dimenziójuk lehet.”
Azóta nagyrészt figyelmen kívül hagytuk az egynél több dimenziós tömböket, hogy egyszerűen tartsuk a dolgokat. Ez a döntés érthetőbb lesz, ha megpróbálod elolvasni a Julia referenciadokumentumokat, teljes bonyolultságukban.
Ugyanakkor a magasabb dimenziós tömbök nagyon-nagyon fontosak a tudományos számításokban, ezért meg kell értenünk őket.
Nevezéktan: A matematika évszázados hagyományát követve az 1-D tömböket Vectors-nak, a 2-D tömböket Matrices-nak nevezzük.
Ebben a dokumentumban a példák többnyire mátrixok. A 3 vagy több dimenzióval való munka szintaktikailag szinte azonos, de a kimenet nehezen és zavaróan olvasható (egy 2-D képernyőn).
Rengeteg vektort hoztunk létre úgy, hogy vesszővel elválasztott listát tettünk szögletes zárójelbe. A pontosvessző is használható elválasztóként.
julia> v = [1, 2, 3]
3-element Vector{Int64}:
1
2
3
julia> w = [1; 2; 3]
3-element Vector{Int64}:
1
2
3
julia> v == w
true
Ha szóközt (vagy tabulátort) használunk elválasztóként, az eredmény más lesz.
julia> u = [1 2 3]
1×3 Matrix{Int64}:
1 2 3
Ezt a Julia most 1×3-as mátrixként definiálja (más összefüggésben row vector-nak, azaz sorevektornak neveznénk).
Általában a szóközök vízszintesen fűzik össze a dolgokat, a pontosvesszők (vagy az újsorok) pedig függőlegesen.
A „dolgok” kifejezés szándékosan homályos, mivel a Julia megpróbál bármivel dolgozni, amit adsz neki.
julia> [v 2v]
3×2 Matrix{Int64}:
1 2
2 4
3 6
julia> [v; 2v]
6-element Vector{Int64}:
1
2
3
2
4
6
Léteznek hcat() és vcat() függvények, amelyek ugyanezt teszik, világosabbá téve, hogy ezek vízszintes és függőleges összefűzések.
A magasabb dimenziós általánosítás a cat() függvény.
jjulia> hcat(v, 2v)
3×2 Matrix{Int64}:
1 2
2 4
3 6
julia> vcat(v, 2v)
6-element Vector{Int64}:
1
2
3
2
4
6
Explicit mátrixok beírása kényelmesen sor-major sorrendben történik, mert ez illeszkedik az emberi intuícióhoz (könnyebb ránézni, a vízszintes írású kultúrákban):
julia> m = [1 2 3; 4 5 6]
2×3 Matrix{Int64}:
1 2 3
4 5 6
Jegyezd meg azonban, hogy a Julia (a Fortranhoz, R-hez és Matlabhoz hasonlóan, de ellentétben a C/C++-szal vagy a NumPy-val) oszlop-major sorrendben tárolja az N-dimenziós tömböket, és ez hatalmas teljesítménykülönbséget jelenthet, ha végigiterálsz az elemeken. Segíts a CPU cache-nek, hogy segíthessen neked!
# put these integers in 2 rows and 3 columns
julia> reshape(collect(1:6), 2, 3)
2×3 Matrix{Int64}:
1 3 5
2 4 6
A fenti példa az 1-től 6-ig terjedő egészeket veszi, és oszloponként tölti fel velük a 2×3-as mátrixot.
Különféle segédfüggvények léteznek gyakori tömbtípusok létrehozására (egyenletes vagy véletlen).
julia> zeros(2, 3) # see also ones()
2×3 Matrix{Float64}:
0.0 0.0 0.0
0.0 0.0 0.0
julia> falses(2, 2) # booleans, see also trues()
2×2 BitMatrix:
0 0
0 0
julia> rand(Float32, 2, 3) # random numbers in the interval [0, 1)
2×3 Matrix{Float32}:
0.768823 0.169633 0.632565
0.388451 0.109176 0.850381
Egy 2-D tömb esetén általában két indexet használunk [row, col] sorrendben.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> m[1, 2] # row 1, col 2
2
# Stay within bounds! There is no row 3.
julia> m[3, 1]
ERROR: BoundsError: attempt to access 2×3 Matrix{Int64} at index [3, 1]
julia> m[3]
2
Az utolsó példa talán meglepő: egy egyszerű index nem hiba, és egyetlen elemet ad vissza.
A magyarázat visszavezet a column-major sorrendről szóló megjegyzéshez: a Julia lefelé halad az 1. oszlopban, majd a 2. oszlopban, amíg meg nem találja a 3. elemet a memóriában.
Légy óvatos: ennek vannak bizonyos felhasználásai általános célú könyvtárak írásakor, de nagyobb valószínűséggel zavaró!
Hasonló probléma merül fel egy tömb méretének lekérdezésekor.
A length() az elemek teljes számát adja, a size() pedig egy ndims() elemű tuplet ad vissza az egyes dimenziók hosszával.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> length(m)
6
julia> size(m) # 2 rows, 3 cols
(2, 3)
julia> ndims(m) # how many dimensions? Like `m |> size |> length`
2
Könnyedén másolhatunk részmátrixot.
Az alábbi példában a reshape() függvény a megadott dimenziókra kényszeríti a tömböt, oszloponként feltöltve, majd felszeleteljük.
julia> m12 = reshape(collect(1:12), 4, 3)
4×3 Matrix{Int64}:
1 5 9
2 6 10
3 7 11
4 8 12
julia> m12[2:4, 1:2]
3×2 Matrix{Int64}:
2 6
3 7
4 8
# some rows, all columns
julia> m12[2:4, :]
3×3 Matrix{Int64}:
2 6 10
3 7 11
4 8 12
Egy önmagában álló : azt jelenti, hogy „másolj le mindent ebben a dimenzióban”.
Nem egymást követő sorokhoz/oszlopokhoz vektorokat használj:
julia> m12[[1, 3], :] # rows 1 and 3
2×3 Matrix{Int64}:
1 5 9
3 7 11
Korábban már láttunk olyan aggregációs függvényeket, mint a sum() és a maximum(), 1-D gyűjteményekre alkalmazva, ahol az összes elemen működnek, és skaláris eredményt adnak vissza.
Ez magasabb dimenziókban is működik.
Előfordulhat azonban, hogy a függvényt csak egy dimenzióra szeretnénk alkalmazni, például lefelé vagy keresztben összegezve, hogy egy 1-es méretű singleton dimension-nel rendelkező tömböt kapjunk.
Ehhez van egy opcionális dims kulcsszó argumentum.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> sum(m) # sum everything
21
julia> sum(m; dims=1) # sum down
1×3 Matrix{Int64}:
5 7 9
julia> sum(m; dims=2) # sum across
2×1 Matrix{Int64}:
6
15
# other aggregation functions are similar
julia> maximum(m; dims=2)
2×1 Matrix{Int64}:
3
6
A dims értéke az a dimenzió, amely singletonná válik, tehát a fenti példákban dims=1 => 1×3 és dims=2 => 2×1.
Kiterjesztésképpen a magasabb dimenziós tömbök több dimenziót is redukálhatnak, ha a dims értéke tömb vagy tartomány (a kimenet megértése ismét némi gondolkodást igényelhet!).
A dims kulcsszó argumentum gyakori a beépített függvényekben, mint a sum(), de hogyan írjunk ehhez hasonlót a saját kódunkban?
Az egyik jó válasz a magasabb rendű függvények, például a reduce() használata, amit egy későbbi fogalomban részletesebben is tárgyalunk.
Alternatívaként a Julia több függvényt is kínál, amelyek segítségével az N-dimenziós tömböket úgy kezelhetjük, mintha vektorok egymásba ágyazott vektorai lennének.
Mátrixokhoz a eachrow() és az eachcol() kényelmes, de az általánosabb függvény az eachslice(), amely tetszőleges dimenziókat képes kezelni.
# m is as in the previous examples
julia> eachrow(m)
2-element RowSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}:
[1, 2, 3]
[4, 5, 6]
julia> eachcol(m)
3-element ColumnSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}:
[1, 4]
[2, 5]
[3, 6]
A típus kissé ijesztő, de csak azért, mert ez egy nézet az eredeti tömbbe, ami lehetővé teszi, hogy másolás nélkül dolgozzunk rajta. A Julia tömbök akár több terabájt méretűek is lehetnek, így a másolás potenciális teljesítményrémálom!
Egy ilyen nézet használható ciklusokhoz, broadcastinghez, vagy bármely más művelethez, amit eddig a tantervben láttunk.
A tömbkifejezések (comprehensions) is hatékonyak és sokoldalúak lehetnek tömbbemenettel.
Az egyszerű eseteket a Loops fogalomban említettük, de egy későbbi fogalomban bővebb tárgyalás következik.
Az 1-D esetet a Vector Operations fogalomban tárgyaltuk.
julia> v = [1.2, 1.5, 1.7]
3-element Vector{Float64}:
1.2
1.5
1.7
julia> v .- 0.5
3-element Vector{Float64}:
0.7
1.0
1.2
A pontozott .- operátor a 0.5 singletont [0.5, 0.5, 0.5]-tel egyenértékűnek tekinti úgy, hogy broadcasting-gal a dimenziókhoz igazítja, majd elemenként elvégzi a kivonást.
Ennek kiterjesztése magasabb dimenziókra valójában csak ugyanaz ismételve.
Például amikor egy 2x1-es sorevektort [1.0 1.5 2.0] összeszorzunk a 2x3-as mátrixszal [1 2 3; 4 5 6], az egyenértékű azzal, hogy a [1.0 1.5 2.0]-t [1.0 1.5 2.0; 1.0 1.5 2.0]-re broadcastoljuk, majd a szorzást elemenként végezzük el.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> m .* [1.0 1.5 2.0] # broadcast a 1x3 row vector down
2×3 Matrix{Float64}:
1.0 3.0 6.0
4.0 7.5 12.0
julia> m .* [1.0 1.5 2.0; 1.0 1.5 2.0] # 2x3 elementwise multiplication
2×3 Matrix{Float64}:
1.0 3.0 6.0
4.0 7.5 12.0
julia> m ./ [1, 2] # broadcast a 2x1 column vector across
2×3 Matrix{Float64}:
1.0 2.0 3.0
2.0 2.5 3.0
julia> m ./ [1 1 1; 2 2 2] # 2x3 elementwise division
2×3 Matrix{Float64}:
1.0 2.0 3.0
2.0 2.5 3.0
Megjegyzés: A nem-singleton dimenzióknak egyezniük kell méretben (pl. 2x3 Matrix .* 2x1 Matrix) a broadcastinghez.
Továbbá egy függvény ugyanúgy broadcastolható egy Matrix minden elemére, mint ahogy egy Vector esetében tesszük.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> (x -> x^2).(m) # broadcast a function to all elements
2×3 Matrix{Int64}:
1 4 9
16 25 36
Eleinte úgy érezheted, hogy a programozói zavar lehetősége exponenciálisan nő a dimenziók számával, de a gyakorlat sokat segít. Továbbá a NumPy tömbökkel való ismeretség meglehetősen jól átültethető Juliára, a szintaxis eltérései ellenére.
A pontmátrix egy kétdimenziós kép, amely pontokból és üres helyekből áll. A pontok alkotják a kívánt képet az üres helyek hátterén.
A pontmátrixos kép a memóriában kétdimenziós Matrix-ként tárolható és manipulálható.
A következőkben a mátrixokban az „üres helyeket” 0 jelöli, a „pontok” pedig nem nullák.
Matrix-otA Matrix így néz ki, ahol a 0 az üres hely, az 1 pedig a pont:
[
0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0;
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0;
0 1 0 0 1 0 1 0 0 0 0 1 0 1 0 0 1 0;
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1;
0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0;
0 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0;
0 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0;
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0;
]
Ha „rendereled”, így néz ki:
XX XX
X X
X X X X
X X X X X X
X X
X X
X X X X
X X X X
X XX X
X X
XX XX
Definiáld a frown!() és frown() függvényeket, amelyek az Exercism logó Matrix-ot kapják.
Adj vissza egy Matrix-ot, amelyben a mosolygó száj helyett komor száj szerepel.
A kapott Matrix renderelve így néz ki:
XX XX
X X
X X X X
X X X X X X
X X
X X
X XX X
X X X X
X X X X
X X
XX XX
Definiáld a stickerwall() függvényt, amely bemenetként az Exercism mátrixot kapja.
Adj vissza egy Matrix-ot, amely renderelve így néz ki:
XX XX XX XX
X X X X
X X X X X X X X
X X X X X X X X X X X X
X X X X
X XX X
X X X X X XX X
X X X X X X X X
X XX X X X X X
X X X X
XX XX XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX XX XX XX
X X X X
X X X X X X X X
X X X X X X X X X X X X
X X X X
X XX X
X XX X X X X X
X X X X X X X X
X X X X X XX X
X X X X
XX XX XX XX
Nem vagyunk arra korlátozva, hogy a pont értéke 1 legyen, így ha akarjuk, más hasznos információt is kódolhatunk vele.
Definiáld a colpixelcount() függvényt, amely bemenetként bármilyen, 1-es pontokból álló pontmátrixot kaphat.
Adj vissza egy ugyanolyan méretű pontmátrixot, amelyben minden oszlop pontjainak értéke az adott oszlopban lévő pontok száma.
Ha az Exercism logó Matrix-ot adjuk be bemenetként, a kimenet a következő Matrix:
[
0 0 2 2 0 0 0 0 0 0 0 0 0 0 2 2 0 0;
0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
0 8 0 0 0 1 0 0 0 0 0 0 1 0 0 0 8 0;
0 8 0 0 1 0 2 0 0 0 0 2 0 1 0 0 8 0;
0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1;
0 8 0 0 0 0 2 0 0 0 0 2 0 0 0 0 8 0;
0 8 0 0 0 0 0 1 0 0 1 0 0 0 0 0 8 0;
0 8 0 0 0 0 0 0 1 1 0 0 0 0 0 0 8 0;
0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
0 0 2 2 0 0 0 0 0 0 0 0 0 0 2 2 0 0;
]
Ahelyett, hogy csak beszélnénk a kreációink rendereléséről, szeretnénk ezt ténylegesen is meg tudni tenni, hogy könnyen megnézhessük őket.
Definiáld a render() függvényt, amely bemenetként egy pontmátrixot kap.
Adj vissza egy stringet, amelyben a pontok 'X'-ként, a 0-k ' '-ként jelennek meg, és az egyes sorokat újsorok kötik össze.
julia> render(E)
" XX XX \n X X \n X X X X \n X X X X X X \n X X \nX X\n X X X X \n X X X X \n X XX X \n X X \n XX XX "
Kiírva az Exercism Matrix a várt módon jelenik meg.
julia> print(render(E))
XX XX
X X
X X X X
X X X X X X
X X
X X
X X X X
X X X X
X XX X
X X
XX XX
Ennek akkor is működnie kell, ha a „pontok” nem 1-esek:
julia> print(render(colpixelcount(E)))
XX XX
X X
X X X X
X X X X X X
X X
X X
X X X X
X X X X
X XX X
X X
XX XX
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) Julia nyelvet 35 fogalom128 feladat segítségével, valódi emberi mentorálással, mindez ingyen.