Kurzusok
/
Julia
Julia
/
Tanterv
/
Vektorműveletek
Ve

Vektorműveletek ebben a kurzusban: Julia

2 feladat

A(z) Vektorműveletek fogalomról

A Vectors fogalomban azt mondtuk, hogy „a tömbök a Julia nyelv szívében vannak”, és a vektor egy egydimenziós tömb.

Ezt figyelembe véve joggal remélhetjük, hogy a nyelv sok sokoldalú és hatékony módot kínál arra, hogy dolgokat csináljunk a vektorokkal, bármit is jelentsen ez.

Egy megjegyzés a terminológiához: bár ez a dokumentum sokat beszél a „vektorokról”, nagy része bármilyen iterálható típusra is vonatkozik: tartományok, tuple-ök, halmazok és egyéb másokra.

Vektor bemenetet váró függvények

Néhány nagyon egyszerű függvény vektor bemenetet vesz, és (egydimenziós bemenet esetén) skalár kimenetet ad vissza.

v = [2, 3, 4]
length(v)  # => 3
sum(v)  # => 9

Amikor eljutunk a többdimenziós tömbök fogalmához, világosabb lesz, hogy ez dimenziócsökkentés, nem pedig feltétlenül skalár visszaadása. Ha ez most nem világos, egyelőre ne aggódj miatta.

Van még sok ilyen típusú függvény. Nézd meg a Statistics fogalmat néhány példáért.

Vannak olyan függvények is, amelyek több vektoron működnek, például a (nagyon hasznos) zip.

julia> z = zip( 1:3, ['a', 'b', 'c'], ["I", "make", "tuples"] )
zip([1, 2, 3], ['a', 'b', 'c'], ["I", "make", "tuples"])

# convert iterator to vector
julia> collect(z)
3-element Vector{Tuple{Int64, Char, String}}:
 (1, 'a', "I")
 (2, 'b', "make")
 (3, 'c', "tuples")

A zip() tetszőleges számú vektorszerű bemenetet vesz, és tuple-ök iterátorát adja vissza.

A bemenetek általában ugyanolyan hosszúak. Ha az egyik rövidebb, a többit a legrövidebb hosszára vágja: lehet, hogy ezt akartad, de gyakrabban ez hiba a kódodban.

Aritmetika

Tegyük fel, hogy van egy numerikus vektorod, és minden értékből le akarsz vonni 0,5-öt.

julia> v = [1.2, 1.5, 1.7]
3-element Vector{Float64}:
 1.2
 1.5
 1.7

julia> v - 0.5
ERROR: MethodError: no method matching -(::Vector{Float64}, ::Float64)

Ez nem működik, szóval mi a helyzet egy másik vektor kivonásával?

julia> v - [0.5, 0.5, 0.5]
3-element Vector{Float64}:
 0.7
 1.0
 1.2

Sikeres, de elég unalmas és memóriaigényes, ahogy a vektorok hosszabbak lesznek.

Attól függően, hogy hol tartasz a tantervben, valószínűleg más megközelítéseket is ki tudsz találni:

  • Írj egy ciklust, bár ez bőbeszédű és nehézkes lenne.
  • Használj comprehensiont: [x - 0.5 for x in v] megadja a kívánt eredményt (Python-stílus).
  • Használj magasabb rendű függvényt: map(x -> x - 0.5, v) szintén működik (Haskell-stílus, bár sok nyelvben gyakori).

Szerencsére a Juliának van egy „varázslatos” pontja, amellyel nagyon egyszerűen megoldható ez a probléma: csak v .- 0.5 kell.

A következő szakasz elmagyarázza, miért.

Broadcasting

Tehát a v - 0.5 hibát ad, a v .- 0.5 viszont sikeres, és meg kell értenünk, mit csinál a pont.

Két dolog, amelyek együtt adják a kívánt eredményt.

1) Elemenkénti alkalmazás

Először is, ha egy pontot bármely infix operátor elé teszel, az azt jelenti, hogy „ezt a műveletet külön-külön minden elemre alkalmazd”.

Hasonlóképpen, ha egy függvénynév után teszel egy pontot, az „vektorizálja” a függvényt, még akkor is, ha skalár bemenetekre írták.

julia> sqrt.([1, 4, 9])
3-element Vector{Float64}:
 1.0
 2.0
 3.0

Zárójelben jegyezzük meg, hogy az infix operátorok valójában csak szintaktikai cukor a mögöttes függvényhez.

Ez azt jelenti, hogy például a [1, 5, 10] .% 3 kifejezést az értelmező mod.([1, 5, 10], 3)-ra fordítja, majd a mod. szintaxis lefut (mindkét változat [1, 2, 1]-t ad vissza).

2) Egyelemű kiterjesztés

Egy korábbi példában láttuk, hogy ki tudunk vonni egyenlő hosszú vektorokat, de kérlek, értsd meg, hogy a .- biztonságosabb operátor, mint a -, mert egyértelművé teszi az elemenkénti szándékot.

julia> v .- [0.5, 0.5, 0.5]
3-element Vector{Float64}:
 0.7
 1.0
 1.2

Mi a helyzet a nem egyenlő hosszú vektorokkal?

julia> v .- [0.5, 0.5]
ERROR: DimensionMismatch: arrays could not be broadcast to a common size

julia> v .- [0.5,]
3-element Vector{Float64}:
 0.7
 1.0
 1.2

Általában a nem egyenlő hosszúságok hiba, kivéve, ha az egyik hossza 1 (technikailag „egyelemű” dimenzió).

Az olyan egyelemű értékeket, mint a [0.5,] vagy egyszerűen a 0.5, a rendszer automatikusan kiterjeszti a szükséges hosszra ismétléssel. Ez van a broadcasting szívében.

Aki aggódik az ebből az „ismétlésből” származó memóriahasználat miatt, megnyugodhat: nagyon hatékony módon van megvalósítva, amely valójában nem másolja a memóriában az értékeket.

A más nyelvekbeli broadcastingot ismerő programozók vegyék észre, hogy a Julia megközelítése (nagyrészt) hasonló a NumPy-hoz, de sokkal kevésbé tűri a méretek eltérését, mint az R.

Broadcasting helyben

Ha a memóriahasználat aggaszt, akkor a helyben végzett műveletek gyakori módot jelentenek az allokációk csökkentésére. A fenti példákban szereplő broadcasting műveletek azonban új Vector-t hoznak létre ahelyett, hogy az eredetit módosítanák.

julia> v = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> v .+ 1
3-element Vector{Int64}:
 2
 3
 4

julia> v
3-element Vector{Int64}:
 1
 2
 3

A v helyben történő módosításához a módosítást broadcastolni kell.

julia> v .= v .+ 1
3-element Vector{Int64}:
 2
 3
 4

julia> v .+= 1  # equivalent operation to above
3-element Vector{Int64}:
 3
 4
 5

julia> v
3-element Vector{Int64}:
 3
 4
 5

De légy óvatos! A pont az értékadó operátor, a .= előtt fontos.

julia> v = [1, 2, 3];

julia> v = v .+ 1
3-element Vector{Int64}:
 2
 3
 4

julia> v
3-element Vector{Int64}:
 2
 3
 4

Ez úgy tűnik, ugyanúgy működött, de itt a v .+ 1 létrehozott egy új [2, 3, 4] vektort, majd hozzárendelte a v változóhoz, így a kezdeti [1, 2, 3] vektor a memóriában maradt, hogy a szemétgyűjtő eltakarítsa. Ez végül kétszer annyi memóriát használ, mint az előző példa, amely újrahasznosítja a kezdeti vektor számára lefoglalt memóriát.

Hasonlóképpen, vannak kapcsolódó finomságok, amikor ugyanolyan méretű vektorokkal broadcastolunk. Például adott két ugyanolyan méretű vektor, v és w:

  • v .= w a w egy copy példányát hozza létre a v memóriahelyén. A további változtatások a v-ben nem érintik a w-t, és viszont.
  • v = w egy másik mutatót hoz létre a w memóriahelyére v néven. A további változtatások a v-ben megjelennek a w-ben, és viszont.

Pont nélküli operátorok: tanulságos történet

Ez az alszakasz eléggé matematikaigényes, ezért a legtöbb tanulótól nem várjuk el, hogy igazán megértse. Mindazonáltal hasznos figyelmeztetés, amely segíthet a hibakeresésben, amikor váratlan hibaüzeneteket látsz.

julia> v = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> v * v
ERROR: MethodError: no method matching *(::Vector{Int64}, ::Vector{Int64})

# look, no commas
julia> u = [1 2 3]
1×3 Matrix{Int64}:
 1  2  3

julia> u * v
1-element Vector{Int64}:
 14

julia> v * u
3×3 Matrix{Int64}:
 1  2  3
 2  4  6
 3  6  9

Ha véletlenül lineáris algebrai háttérrel rendelkezel, akkor (1) nem vagy tipikus Exercism-felhasználó (de nagyon szívesen látunk itt!) és (2) felismerheted, hogy a v oszlopvektor, az u sorvektor, az u * v belső szorzat, a v * u pedig külső szorzat. A Julia követi a matematika szabályait, ebben is, mint mindenben.

Mindenki másnak: kérlek, értsd meg, miért javasoljuk, hogy elemenkénti számításokhoz mindig pontozott operátorokat használj: a v .* v pontosan úgy működik, ahogy várnád, és [1, 4, 9]-et ad.

Indexelés

A vektorok elemeinek indexszám szerinti kiválasztásáról korábbi fogalmakban volt szó.

a = collect('A':'Z')  # => 26-element Vector{Char}

# index with an integer
a[2]  # => 'B'

# index with a range
 a[12:2:18]  # => ['L', 'N', 'P, 'R']
 
 # index with another vector
 a[ [1, 3, 5] ]  # => ['A', 'C', 'E']

Logikai indexelés

Lehetőség van olyan elemek kiválasztására is, amelyek kielégítenek valamilyen logikai kifejezést (technikailag egy „predikátumot”). Ez általában broadcastingot igényel.

julia> a[a .< 'D']
3-element Vector{Char}:
 'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
 'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
 'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)

Bonyolultabb kifejezéseknél a pontok hajlamosak elszaporodni (de kicsik és könnyű őket beírni).

julia> a[a .< 'D' .|| a .> 'W']
6-element Vector{Char}:
 'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
 'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
 'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
 'X': ASCII/Unicode U+0058 (category Lu: Letter, uppercase)
 'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
 'Z': ASCII/Unicode U+005A (category Lu: Letter, uppercase)

Emlékeztetőül: a „vektor” valójában bármilyen megfelelő rendezett iterálható lehet, például egy tartomány:

julia> n = 3:10
3:10

julia> n[isodd.(n)]
4-element Vector{Int64}:
 3
 5
 7
 9

Belül a predikátum BitVector-té alakul, amelyet aztán indexként használunk.

julia> condition = a .< 'D'
26-element BitVector:
 1
 1
 1
 0
 # display truncated

julia> a[condition]
3-element Vector{Char}:
 'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
 'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
 'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg

Tanuld meg a(z) Vektorműveletek fogalmat