Obsah
1. Programovací jazyk Rust: knihovna ndarray pro práci s n-rozměrnými poli (2)
6. Specifikace kroku při tvorbě řezu polem
9. Explicitní krok v řezech dvourozměrných polí
10. Operátory aplikované na prvky polí
11. Zpracování prvků zdrojového pole
12. Změna pole vystupujícího v roli zdrojového i cílového operandu
14. Broadcasting při použití skalární hodnoty
15. Repositář s demonstračními příklady
1. Programovací jazyk Rust: knihovna ndarray pro práci s n-rozměrnými poli (2)
Většina funkcí a metod, které knihovna ndarray programátorům nabízí, je navržena takovým způsobem, aby se při zpracování vektorů, matic či vícerozměrných polí nemusely explicitně používat programové smyčky. V tomto kontextu se tedy ndarray do značné míry podobá známé knihovně Numpy určené pro Python či specializovaným jazykům navrženým pro zpracování polí (APL, J). Dokonce i ve chvíli, kdy se může zdát, že je použití programových smyček nezbytné (násobení matice konstantou, průchod všemi sudými prvky vektoru, změna tvaru matice, součet matic atd.), je většinou v knihovně ndarray dostupný mechanismus, který nás od nutnosti explicitního zápisu smyček „ochrání“. Důvodů je více, ovšem ten hlavní důvod spočívá v mnohem přehlednějším kódu a v neposlední řadě je taktéž možné předpokládat, že funkce pro práci s poli budou v ndarray napsány bezchybně a s ohledem na rychlost zpracování.
2. Pohledy na pole
Jedním z mechanismů, které nám umožňují zbavit se programových smyček, jsou takzvané pohledy (views), díky nimž je možné se na jedna zdrojová data dívat různými způsoby, a to většinou bez nutnosti přesunu či kopie těchto dat (což je samozřejmě u rozsáhlých polí velmi neefektivní operace).
Nejjednodušší pohled na pole se bude chovat stejně jako samotné pole, a to včetně přístupu k prvkům, informacím o dimenzích atd. Takový pohled se vytvoří zavoláním metody pole.view():
let array = Array::from_iter(0..12); let view = array.view(); println!("length: {}", array.len()); println!("dimensions: {}", array.ndim()); println!("dimension: {:?}", array.dim()); println!("shape: {:?}", array.shape()); println!("strides: {:?}\n", array.strides()); println!("length: {}", view.len()); println!("dimensions: {}", view.ndim()); println!("dimension: {:?}", view.dim()); println!("shape: {:?}", view.shape()); println!("strides: {:?}\n", view.strides());
Po překladu a spuštění skutečně uvidíme, že se pole a jeho pohled vlastně nijak neliší, samozřejmě s tím rozdílem, že pohled neobsahuje žádné prvky, ty jsou stále ve vlastnictví původního pole:
length: 12 dimensions: 1 dimension: 12 shape: [12] strides: [1] length: 12 dimensions: 1 dimension: 12 shape: [12] strides: [1]
Totéž samozřejmě platí i u vícerozměrných polí (zde čtyřrozměrných):
let array = Array::from_iter(0..16).into_shape((2,2,2,2)).unwrap(); let view = array.view(); println!("length: {}", array.len()); println!("dimensions: {}", array.ndim()); println!("dimension: {:?}", array.dim()); println!("shape: {:?}", array.shape()); println!("strides: {:?}\n", array.strides()); println!("length: {}", view.len()); println!("dimensions: {}", view.ndim()); println!("dimension: {:?}", view.dim()); println!("shape: {:?}", view.shape()); println!("strides: {:?}\n", view.strides());
Výstup získaný po spuštění příkladu:
length: 16 dimensions: 4 dimension: (2, 2, 2, 2) shape: [2, 2, 2, 2] strides: [8, 4, 2, 1] length: 16 dimensions: 4 dimension: (2, 2, 2, 2) shape: [2, 2, 2, 2] strides: [8, 4, 2, 1]
Ve skutečnosti se takto pohled na pole nebude prakticky nikdy vytvářet. Pohledy ovšem vznikají i jinak:
- Řezem pole (slice)
- Rozšířením velikosti a/nebo počtu dimenzí (broadcast)
- Odstraněním jedné dimenze (subview)
3. Měnitelný pohled na pole
Pohledy vytvořené v předchozích příkladech mohly sloužit pouze pro čtení prvků z pole, popř. pro další operace, u nichž nedochází k modifikaci (níže popsané aritmetické operace aplikované na prvky apod.). V případě, že je zapotřebí pole přes jeho pohled modifikovat, musí být samozřejmě původní pole měnitelné (mutable) a současně se musí pohled vytvořit metodou view_mut() a nikoli jen view():
let mut array = Array2::<i32>::zeros((3,3)); println!("original array:\n{}\n", array); { let mut view = array.view_mut(); view[[1,1]] = 42; } println!("array modified via view:\n{}\n", array); array[[1,1]] = 1000; println!("array modified directly:\n{}\n", array);
Po překladu a spuštění získáme tyto výsledky:
original array: [[0, 0, 0], [0, 0, 0], [0, 0, 0]] array modified via view: [[0, 0, 0], [0, 42, 0], [0, 0, 0]] array modified directly: [[0, 0, 0], [0, 1000, 0], [0, 0, 0]]
Ve chvíli, kdy je proměnná obsahující pohled ještě v oblasti viditelnosti, není možné k poli přistupovat a modifikovat ho přes původní proměnnou, o čemž se opět můžeme snadno přesvědčit:
let mut array = Array2::<i32>::zeros((3,3)); println!("original array:\n{}\n", array); { let mut view = array.view_mut(); view[[1,1]] = 42; array[[1,1]] = 1000; } println!("array modified via view:\n{}\n", array);
Tento souběžný přístup ohlídá překladač:
error[E0499]: cannot borrow `array` as mutable more than once at a time --> main.rs:62:10 | 59 | let mut view = array.view_mut(); | ----- first mutable borrow occurs here ... 62 | array[[1,1]] = 1000; | ^^^^^ second mutable borrow occurs here 63 | } | - first borrow ends here error: aborting due to previous error
4. Řezy jednorozměrných polí
V mnoha případech je nutné z polí, ať již se jedná o jednorozměrné vektory, dvourozměrné matice či vícerozměrná pole, získat hodnoty většího množství prvků tvořících souvislý blok. Může se například jednat o všechny prvky jednorozměrného pole kromě prvku prvního a posledního (typické pro některé filtry), prvky z první poloviny pole atd. I v tomto případě knihovna ndarray nabízí vývojářům velmi elegantní řešení, a to ve formě takzvaných řezů (slices). Pro tvorbu řezů je určena funkce slice() s makrem s zapisovaným samozřejmě s vykřičníkem na konci. Při použití tohoto makra pro vektory se namísto jediného indexu zadávají dva indexy oddělené dvěma tečkami, které potom reprezentují začátek a konec řezu. Jak horní, tak i dolní index je možné vynechat; v tomto případě se nahradí indexem prvního resp. posledního prvku zvýšeného o jedničku (to odpovídá chování Range). Opět se podívejme na demonstrační příklady, prozatím pro jednorozměrná pole:
let array = Array::from_iter(0..12); println!("original array:\n{}\n", array); let slice = array.slice(s![3..8]); println!("slice 3..8:\n{}\n", slice); let slice2 = array.slice(s![..8]); println!("slice ..8:\n{}\n", slice2); let slice3 = array.slice(s![3..]); println!("slice 3..:\n{}\n", slice3); let slice4 = array.slice(s![..]); println!("slice ..:\n{}\n", slice4); let slice5 = array.slice(s![3..4]); println!("slice 3..4:\n{}\n", slice5); let slice6 = array.slice(s![4..4]); println!("slice 4..4:\n{}\n", slice6);
Povšimněte si, že se zadává polootevřený interval, opět stejně jako v případě Range:
original array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] slice 3..8: [3, 4, 5, 6, 7] slice ..8: [0, 1, 2, 3, 4, 5, 6, 7] slice 3..: [3, 4, 5, 6, 7, 8, 9, 10, 11] slice ..: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] slice 3..4: [3] slice 4..4: []
5. Použití záporných indexů
Použít je možné i záporné indexy, popř. první či druhý index zcela vynechat:
let array = Array::from_iter(0..12); println!("original array:\n{}\n", array); let slice = array.slice(s![-10..-1]); println!("slice -10..-1:\n{}\n", slice); let slice2 = array.slice(s![..-1]); println!("slice2 ..-1:\n{}\n", slice2); let slice3 = array.slice(s![..-8]); println!("slice ..-8:\n{}\n", slice3); let slice4 = array.slice(s![-3..]); println!("slice -3..:\n{}\n", slice4); let slice5 = array.slice(s![-1..]); println!("slice -1..:\n{}\n", slice5);
Z výsledků vypsaných po překladu a spuštění je patrné, že v případě záporných indexů se prvky vybírají od konce pole, přičemž index –1 ve skutečnosti odpovídá indexu posledního prvku pole. Kvůli tomu, že se u řezů používá polootevřený interval, bude zápis:
s![..-1]
odpovídat původnímu vektoru, ovšem bez posledního prvku:
original array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] slice -10..-1: [2, 3, 4, 5, 6, 7, 8, 9, 10] slice2 ..-1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] slice ..-8: [0, 1, 2, 3] slice -3..: [9, 10, 11] slice -1..: [11]
Poznámka: díky této vlastnosti se prakticky nikdy nesetkáme s nutností zjišťovat skutečnou velikost dimenzí pole, tj. v případě vektoru s nutností spočtení počtu prvků.
6. Specifikace kroku při tvorbě řezu polem
Při provádění operace řezu polem jsme doposud používali zápis:
pole.slice(s![od..do])
popř. se jeden z indexů od či do mohl vynechat. Ovšem knihovna ndarray podporuje mj. i mírně rozšířený zápis:
pole.slice(s![od..do;krok])
přičemž poslední použitá hodnota udává vzdálenost (stride) mezi sousedními prvky řezu. Podívejme se na několik jednoduchých příkladů:
let array = Array::from_iter(0..12); println!("original array:\n{}\n", array); let slice = array.slice(s![3..8;2]); println!("slice 3..8;2:\n{}\n", slice); let slice2 = array.slice(s![..8;2]); println!("slice ..8;2:\n{}\n", slice2); let slice3 = array.slice(s![3..;2]); println!("slice 3..;2:\n{}\n", slice3); let slice4 = array.slice(s![..;2]); println!("slice ..;2:\n{}\n", slice4);
Ve všech případech byl krok nastavený na hodnotu 2, takže se z původního pole vybral vždy každý druhý prvek (zda se jednalo o prvek sudý nebo lichý závisí na hodnotě prvního vybíraného indexu):
original array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] slice 3..8;2: [3, 5, 7] slice ..8;2: [0, 2, 4, 6] slice 3..;2: [3, 5, 7, 9, 11] slice ..;2: [0, 2, 4, 6, 8, 10]
Zejména poslední „trik“ se zápisem s![..;krok] může nahradit některé typy programových smyček – představme si například výběr jedné barvové složky z vektoru/matice pixelů používajících barvový model RGB atd.
7. Záporný krok
I hodnota kroku, resp. přesněji řečeno stride, může být v případě potřeby záporná. V takovém případě se bude polem procházet v opačném směru:
let array = Array::from_iter(0..12); println!("original array:\n{}\n", array); let slice = array.slice(s![3..8;-2]); println!("slice 3..8;-2:\n{}\n", slice); let slice2 = array.slice(s![..8;-2]); println!("slice ..8;-2:\n{}\n", slice2); let slice3 = array.slice(s![3..;-2]); println!("slice 3..;-2:\n{}\n", slice3); let slice4 = array.slice(s![..;-2]); println!("slice ..;-2:\n{}\n", slice4);
S výsledky:
original array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] slice 3..8;-2: [7, 5, 3] slice ..8;-2: [7, 5, 3, 1] slice 3..;-2: [11, 9, 7, 5, 3] slice ..;-2: [11, 9, 7, 5, 3, 1]
Tohoto chování je možné využít například pro zrcadlení všech prvků ve vektoru:
let slice5 = array.slice(s![..;-1]); println!("slice ..;-1:\n{}\n", slice5); slice ..;-1: [11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Poznámka: krok (resp. stride) může být kladný či záporný, nikoli však nulový. Tato kontrola je provedena až v době běhu programu:
thread 'main' panicked at 'Slice stride must not be none(for SliceArg [..;0])', /home/tester/.cargo/registry/src/github.com-1ecc6299db9ec823/ndarray-0.9.1/src/dimension/dimension_trait.rs:280:12 note: Run with `RUST_BACKTRACE=1` for a backtrace.
8. Řezy dvourozměrných polí
Řezy je možné vytvářet i z dvourozměrných, popř. vícerozměrných polí (zde však již může být obtížné si představit, jak bude výsledek vypadat). Makro s totiž umožňuje specifikovat takový počet klauzulí s předpisem řezu, jaký odpovídá dimenzi původního pole. Samozřejmě se opět podíváme na několik příkladů, které budou pro ilustraci používat dvourozměrná pole, tedy matice. Původní matice bude mít čtyři řádky a pět sloupců, prvky budou v rozsahu 10 až 29 (to jen kvůli pěknému zarovnání sloupců):
let array = Array::from_iter(10..30).into_shape((4,5)).unwrap(); println!("original array:\n{}\n", array); let slice = array.slice(s![.., 1..3]); println!("slice: .., 1..3\n{}\n", slice); let slice2 = array.slice(s![1..2, 3..4]); println!("slice: 1..2, 3..4\n{}\n", slice2); let slice3 = array.slice(s![1.., 1..]); println!("slice: 1.., 1..\n{}\n", slice3); let slice4 = array.slice(s![.., ..]); println!("slice: .., ..\n{}\n", slice4); let slice5 = array.slice(s![..-1, ..-1]); println!("slice: ..-1, ..-1\n{}\n", slice5);
Původní matice a její řezy vypadají takto:
original array: [[10, 11, 12, 13, 14], [15, 16, 17, 18, 19], [20, 21, 22, 23, 24], [25, 26, 27, 28, 29]] všechny řádky, ovšem jen dva vybrané sloupce: slice: .., 1..3 [[11, 12], [16, 17], [21, 22], [26, 27]] nezapomeňte na polootevřený interval: slice: 1..2, 3..4 [[18]] podmatice bez prvního řádku a prvního sloupce: slice: 1.., 1.. [[16, 17, 18, 19], [21, 22, 23, 24], [26, 27, 28, 29]] kopie původní matice: slice: .., .. [[10, 11, 12, 13, 14], [15, 16, 17, 18, 19], [20, 21, 22, 23, 24], [25, 26, 27, 28, 29]] podmatice bez posledního řádku a posledního sloupce: slice: ..-1, ..-1 [[10, 11, 12, 13], [15, 16, 17, 18], [20, 21, 22, 23]]
9. Explicitní krok v řezech dvourozměrných polí
Samozřejmě je možné i u řezů dvourozměrných polí (matic) zvolit krok, který může být odlišný při výběru řádků i výběru sloupců. Povšimněte si, že se krok zapisuje vždy ihned za klauzuli se specifikací řezu v dané dimenzi:
let array = Array::from_iter(10..30).into_shape((4,5)).unwrap(); println!("original array:\n{}\n", array); let slice = array.slice(s![..;2, 1..3]); println!("slice: ..;2, 1..3\n{}\n", slice); let slice2 = array.slice(s![1..2;-1, 3..4;-1]); println!("slice: 1..2;-1, 3..4;-1\n{}\n", slice2); let slice3 = array.slice(s![1..;2, 1..;2]); println!("slice: 1..;2, 1..;2\n{}\n", slice3); let slice4 = array.slice(s![.., ..;-1]); println!("slice: .., ..;-1\n{}\n", slice4); let slice5 = array.slice(s![..;-1, ..;-1]); println!("slice: ..;-1, ..;-1\n{}\n", slice5); let slice6 = array.slice(s![..;-1, ..]); println!("slice: ..;-1, ..\n{}\n", slice6);
Výsledky budou následující:
original array: [[10, 11, 12, 13, 14], [15, 16, 17, 18, 19], [20, 21, 22, 23, 24], [25, 26, 27, 28, 29]] jen sudé řádky a současně druhý a třetí sloupec: slice: ..;2, 1..3 [[11, 12], [21, 22]] slice: 1..2;-1, 3..4;-1 [[18]] liché řádky a sloupce: slice: 1..;2, 1..;2 [[16, 18], [26, 28]] zrcadlení sloupců: slice: .., ..;-1 [[14, 13, 12, 11, 10], [19, 18, 17, 16, 15], [24, 23, 22, 21, 20], [29, 28, 27, 26, 25]] zrcadlení sloupců i řádků: slice: ..;-1, ..;-1 [[29, 28, 27, 26, 25], [24, 23, 22, 21, 20], [19, 18, 17, 16, 15], [14, 13, 12, 11, 10]] zrcadlení řádků: slice: ..;-1, .. [[25, 26, 27, 28, 29], [20, 21, 22, 23, 24], [15, 16, 17, 18, 19], [10, 11, 12, 13, 14]]
10. Operátory aplikované na prvky polí
Na prvky polí, například na dvojici vektorů nebo dvojici matic, je možné aplikovat vybraný operátor. V nejjednodušším případě se používají základní přetížené operátory aplikované na reference na pole. To tedy mj. znamená, že operátor * násobí jednotlivé prvky vektoru; není to tedy ani skalární ani vektorový součin (stejně tak u matic):
let array1 = Array::range(0.0, 10.0, 1.0); let array2 = Array::range(0.0, 5.0, 0.5); println!("array1: {}", array1); println!("array2: {}", array2); println!("add: {}", &array1 + &array2); println!("sub: {}", &array1 - &array2); println!("mul: {}", &array1 * &array2); println!("div: {}", &array1 / &array2);
Původní vektory a výsledky všech čtyř operací:
array1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] array2: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5] add: [0, 1.5, 3, 4.5, 6, 7.5, 9, 10.5, 12, 13.5] sub: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5] mul: [0, 0.5, 2, 4.5, 8, 12.5, 18, 24.5, 32, 40.5] div: [NaN, 2, 2, 2, 2, 2, 2, 2, 2, 2]
Výsledkem je v těchto případech vždy nové pole, zatímco hodnoty ani vlastnictví původních polí se nezmění. Vytvoření nového pole však nemusí být ve všech případech žádoucí; ostatně stačí si představit například aplikace několika filtrů na velký rastrový obrázek.
Poznámka: kupodivu nejsou (alespoň prozatím) implementovány relační operátory.
Operace nad maticemi:
let array1 = Array::from_iter(10..30).into_shape((4,5)).unwrap(); let array2 = Array::from_iter(100..120).into_shape((4,5)).unwrap(); println!("array1:\n{}\n", array1); println!("array2:\n{}\n", array2); println!("add:\n{}\n", array1 * array2);
S výsledkem:
array1: [[10, 11, 12, 13, 14], [15, 16, 17, 18, 19], [20, 21, 22, 23, 24], [25, 26, 27, 28, 29]] array2: [[100, 101, 102, 103, 104], [105, 106, 107, 108, 109], [110, 111, 112, 113, 114], [115, 116, 117, 118, 119]] add: [[1000, 1111, 1224, 1339, 1456], [1575, 1696, 1819, 1944, 2071], [2200, 2331, 2464, 2599, 2736], [2875, 3016, 3159, 3304, 3451]]
11. Zpracování prvků zdrojového pole
V případě, že nějaký operátor (například operátor +) aplikujeme na dvojici polí (nikoli na reference), bude celá operace probíhat odlišně. Například zápis array1 + array2 ve skutečnosti znamená, že se pole array1 modifikuje (uloží se do něj výsledné prvky) a současně je toto pole výsledkem (návratovou hodnotou) celé operace. V praxi to znamená, že se ušetří paměť a současně původní proměnná/parametr array1 vstupující do operace array1 + array2 ztratí vlastnictví původního pole. To například znamená, že následující program bude možné přeložit, ale po součtu už nebude možné použít proměnnou array1 a ani array2:
let array1 = Array::range(0.0, 10.0, 1.0); let array2 = Array::range(0.0, 5.0, 0.5); println!("array1: {}", array1); println!("array2: {}", array2); let result = array1 + array2; println!("result: {}", result);
Výsledek běhu tohoto úryvku kódu:
array1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] array2: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5] result: [0, 1.5, 3, 4.5, 6, 7.5, 9, 10.5, 12, 13.5]
Nyní se pokusme po součtu přistoupit k původním proměnným array1 a array2:
let array1 = Array::range(0.0, 10.0, 1.0); let array2 = Array::range(0.0, 5.0, 0.5); println!("array1: {}", array1); println!("array2: {}", array2); let result = array1 + array2; println!("array1: {}", array1); println!("array2: {}", array2); println!("result: {}", result);
Překladač nám v tomto případě „vynadá“, protože již vlastnictví objektů nepatří k původním proměnným:
error[E0382]: use of moved value: `array1` --> main.rs:34:28 | 32 | let result = array1 + array2; | ------ value moved here 33 | 34 | println!("array1: {}", array1); | ^^^^^^ value used here after move | = note: move occurs because `array1` has type `ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarray::Dim<[usize; 1]>>`, which does not implement the `Copy` trait error[E0382]: use of moved value: `array2` --> main.rs:35:28 | 32 | let result = array1 + array2; | ------ value moved here ... 35 | println!("array2: {}", array2); | ^^^^^^ value used here after move | = note: move occurs because `array2` has type `ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarray::Dim<[usize; 1]>>`, which does not implement the `Copy` trait error: aborting due to 2 previous errors
12. Změna pole vystupujícího v roli zdrojového i cílového operandu
Kromě operátorů +, -, * a / je možné u polí používat i operátory +=, -=, *= a /=. V těchto případech vždy dochází k výpočtu výsledného pole a uložení výsledku do proměnné, která se nachází na levé straně operátoru. Proto musí být tato proměnná modifikovatelná (mutable):
let array1 = Array::range(0.0, 10.0, 1.0); let mut array2 = Array::range(0.0, 5.0, 0.5); println!("array1: {}", array1); println!("array2: {}", array2); array2 += &array1; println!("result: {}", array2);
Výsledek běhu programu:
array1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] array2: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5] result: [0, 1.5, 3, 4.5, 6, 7.5, 9, 10.5, 12, 13.5]
To, zda je možné proměnné přiřadit jinou hodnotu, samozřejmě kontroluje překladač:
error[E0596]: cannot borrow immutable local variable `array2` as mutable --> main.rs:66:5 | 61 | let array2 = Array::range(0.0, 5.0, 0.5); | ------ consider changing this to `mut array2` ... 66 | array2 += &array1; | ^^^^^^ cannot borrow mutably error: aborting due to previous error
13. Broadcasting
Pole typu Array obsahují i užitečnou metodu nazvanou broadcast sloužící pro zvětšení pole (počtu prvků ve vektoru, počtu řádků či sloupců u matice atd.) nebo pro rozšíření jeho dimenzí, tj. z vektoru lze vytvořit matici, z×matice trojrozměrné pole atd. (ve skutečnosti se nevytváří nové pole, ale jen nový pohled na pole původní, což je většinou efektivnější). Tato metoda je tedy nepřesným opakem metody slice, s níž jsme se již seznámili. Ovšem největší užitečnost metody broadcast spočívá v tom, že se v případě potřeby volá automaticky přetíženými operátory +, -, *, / popsanými výše, a to ve chvíli, kdy se tvary (shape) obou polí odlišují a pokud je skutečně možné jedno pole vhodně rozšířit. Opět si ukažme příklad:
let array1 = Array::from_iter(0..12).into_shape((3,4)).unwrap(); let array2 = Array::from_iter(0..4); println!("array1:\n{}\n", array1); println!("array2:\n{}\n", array2); let result = array1 * array2; println!("result:\n{}", result);
Druhé pole, které bylo původně jednorozměrným vektorem, se převedlo na dvourozměrnou matici (interně se ovšem pouze nastavil atribut řídicí opakování prvků na jedné ose):
array1: [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]] array2: [0, 1, 2, 3] result: [[0, 1, 4, 9], [0, 5, 12, 21], [0, 9, 20, 33]]
14. Broadcasting při použití skalární hodnoty
Pokud například budeme chtít vynásobit všechny prvky trojrozměrného pole konstantou 2, můžeme použít broadcasting jednoprvkového vektoru na trojrozměrné pole o stejné velikosti, jako má pole vstupující do operace násobení:
let array3 = Array::from_iter(0..24).into_shape((2,3,4)).unwrap(); let array4 = Array::from_vec(vec![2]); println!("array3:\n{}\n", array3); println!("array4:\n{}\n", array4); let result = &array3 * &array4; println!("array3 * array4:\n{}", result);
Výsledek:
array3: [[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]] array4: [2] array3 * array4: [[[0, 2, 4, 6], [8, 10, 12, 14], [16, 18, 20, 22]], [[24, 26, 28, 30], [32, 34, 36, 38], [40, 42, 44, 46]]]
Ve skutečnosti to je ovšem ještě jednodušší, protože rozšířit je možné i skalární hodnotu, tedy běžnou číselnou konstantu. Opět se podívejme na příklad (dnes již poslední):
let array1 = Array::from_iter(100..124).into_shape((2,3,4)).unwrap(); println!("array1:\n{}\n", array1); let result = &array1 * 10; println!("array1 * 10:\n{}\n", result); let result = &array1 + 1000; println!("array1 + 1000:\n{}\n", result);
Výsledek:
array1: [[[100, 101, 102, 103], [104, 105, 106, 107], [108, 109, 110, 111]], [[112, 113, 114, 115], [116, 117, 118, 119], [120, 121, 122, 123]]] array1 * 10: [[[1000, 1010, 1020, 1030], [1040, 1050, 1060, 1070], [1080, 1090, 1100, 1110]], [[1120, 1130, 1140, 1150], [1160, 1170, 1180, 1190], [1200, 1210, 1220, 1230]]] array1 + 1000: [[[1100, 1101, 1102, 1103], [1104, 1105, 1106, 1107], [1108, 1109, 1110, 1111]], [[1112, 1113, 1114, 1115], [1116, 1117, 1118, 1119], [1120, 1121, 1122, 1123]]]
15. Repositář s demonstračními příklady
Všechny dnes popisované demonstrační příklady (Rustovské projekty) byly, ostatně podobně jako ve všech předchozích částech tohoto seriálu, uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/presentations. Demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý repositář (ovšem u projektů je lepší mít celý repositář, abyste nemuseli pracně stahovat všechny potřebné soubory):
Příklad | Adresa |
---|---|
První projekt | ndarray-views |
Cargo.toml | https://github.com/tisnik/presentations/blob/master/rust/projects/ndarray-views/Cargo.toml |
main.rs | https://github.com/tisnik/presentations/blob/master/rust/projects/ndarray-views/src/main.rs |
Druhý projekt | ndarray-slices |
Cargo.toml | https://github.com/tisnik/presentations/blob/master/rust/projects/ndarray-slices/Cargo.toml |
main.rs | https://github.com/tisnik/presentations/blob/master/rust/projects/ndarray-slices/src/main.rs |
Třetí projekt | ndarray-operators |
Cargo.toml | https://github.com/tisnik/presentations/blob/master/rust/projects/ndarray-operators/Cargo.toml |
main.rs | https://github.com/tisnik/presentations/blob/master/rust/projects/ndarray-operators/src/main.rs |
16. Odkazy na Internetu
- ndarray – dokumentace k modulu
https://bluss.github.io/rust-ndarray/master/ndarray/index.html - ndarray – Crate
https://crates.io/crates/ndarray - rustup
https://www.rustup.rs/ - rustup: the Rust toolchain installer (Git repositář + dokumentace)
https://github.com/rust-lang-nursery/rustup.rs - The Rust FFI Omnibus
http://jakegoulding.com/rust-ffi-omnibus/ - Build Script Support
http://doc.crates.io/build-script.html - Calling Rust From Python
https://bheisler.github.io/post/calling-rust-in-python/ - Calling Rust in Python (komentáře k předchozímu článku)
https://www.reddit.com/r/rust/comments/63iy5a/calling_rust_in_python/ - CFFI Documentation
https://cffi.readthedocs.io/en/latest/ - Build Script Support
http://doc.crates.io/build-script.html - Creating a shared and static library with the gnu compiler [gcc]
http://www.adp-gmbh.ch/cpp/gcc/create_lib.html - ctypes — A foreign function library for Python
https://docs.python.org/2/library/ctypes.html - FFI: Foreign Function Interface
https://doc.rust-lang.org/book/ffi.html - Primitive Type pointer
https://doc.rust-lang.org/std/primitive.pointer.html - Cargo: správce projektů a balíčků pro programovací jazyk Rust
https://mojefedora.cz/cargo-spravce-projektu-a-balicku-pro-programovaci-jazyk-rust/ - Network Communication and Serialization in Rust
https://www.safaribooksonline.com/blog/2014/01/28/network-communication-serialization-rust/ - Crate bincode
http://tyoverby.com/bincode/bincode/index.html - Struct std::fs::File
https://doc.rust-lang.org/std/fs/struct.File.html - Trait std::io::Seek
https://doc.rust-lang.org/std/io/trait.Seek.html - Trait std::io::Read
https://doc.rust-lang.org/std/io/trait.Read.html - Trait std::io::Write
https://doc.rust-lang.org/std/io/trait.Write.html - Trait std::io::BufRead
https://doc.rust-lang.org/std/io/trait.BufRead.html - Module std::io::prelude
https://doc.rust-lang.org/std/io/prelude/index.html - std::net::IpAddr
https://doc.rust-lang.org/std/net/enum.IpAddr.html - std::net::Ipv4Addr
https://doc.rust-lang.org/std/net/struct.Ipv4Addr.html - std::net::Ipv6Addr
https://doc.rust-lang.org/std/net/struct.Ipv6Addr.html - TcpListener
https://doc.rust-lang.org/std/net/struct.TcpListener.html - TcpStream
https://doc.rust-lang.org/std/net/struct.TcpStream.html - Binary heap (Wikipedia)
https://en.wikipedia.org/wiki/Binary_heap - Binární halda (Wikipedia)
https://cs.wikipedia.org/wiki/Bin%C3%A1rn%C3%AD_halda - Halda (datová struktura)
https://cs.wikipedia.org/wiki/Halda_%28datov%C3%A1_struktura%29 - Struct std::collections::HashSet
https://doc.rust-lang.org/std/collections/struct.HashSet.html - Struct std::collections::BTreeSet
https://doc.rust-lang.org/std/collections/struct.BTreeSet.html - Struct std::collections::BinaryHeap
https://doc.rust-lang.org/std/collections/struct.BinaryHeap.html - Set (abstract data type)
https://en.wikipedia.org/wiki/Set_%28abstract_data_type%29#Language_support - Associative array
https://en.wikipedia.org/wiki/Associative_array - Hash Table
https://en.wikipedia.org/wiki/Hash_table - B-tree
https://en.wikipedia.org/wiki/B-tree - Pedro Celis: Robin Hood Hashing (naskenované PDF!)
https://cs.uwaterloo.ca/research/tr/1986/CS-86–14.pdf - Robin Hood hashing
http://codecapsule.com/2013/11/11/robin-hood-hashing/ - Robin Hood hashing: backward shift deletion
http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/ - Module std::collections
https://doc.rust-lang.org/std/collections/ - Module std::vec
https://doc.rust-lang.org/nightly/std/vec/index.html - Struct std::collections::VecDeque
https://doc.rust-lang.org/std/collections/struct.VecDeque.html - Struct std::collections::LinkedList
https://doc.rust-lang.org/std/collections/struct.LinkedList.html - Module std::fmt
https://doc.rust-lang.org/std/fmt/ - Macro std::println
https://doc.rust-lang.org/std/macro.println.html - Enum std::result::Result
https://doc.rust-lang.org/std/result/enum.Result.html - Module std::result
https://doc.rust-lang.org/std/result/ - Result
http://rustbyexample.com/std/result.html - Rust stdlib: Option
https://doc.rust-lang.org/std/option/enum.Option.html - Module std::option
https://doc.rust-lang.org/std/option/index.html - Rust by example: option
http://rustbyexample.com/std/option.html - Rust by example: if-let
http://rustbyexample.com/flow_control/if_let.html - Rust by example: while let
http://rustbyexample.com/flow_control/while_let.html - Rust by example: Option<i32>
http://rustbyexample.com/std/option.html - An Overview of Macros in Rust
http://words.steveklabnik.com/an-overview-of-macros-in-rust - A Practical Intro to Macros in Rust 1.0
https://danielkeep.github.io/practical-intro-to-macros.html - The Rust Programming Language: macros
https://doc.rust-lang.org/beta/book/macros.html - Rust by example: 15 macro_rules!
http://rustbyexample.com/macros.html - Primitive Type isize
https://doc.rust-lang.org/nightly/std/primitive.isize.html - Primitive Type usize
https://doc.rust-lang.org/nightly/std/primitive.usize.html - Primitive Type array
https://doc.rust-lang.org/nightly/std/primitive.array.html - Module std::slice
https://doc.rust-lang.org/nightly/std/slice/ - Rust by Example: 2.3 Arrays and Slices
http://rustbyexample.com/primitives/array.html - What is the difference between Slice and Array (stackoverflow)
http://stackoverflow.com/questions/30794235/what-is-the-difference-between-slice-and-array - Learning Rust With Entirely Too Many Linked Lists
http://cglab.ca/~abeinges/blah/too-many-lists/book/ - Testcase: linked list
http://rustbyexample.com/custom_types/enum/testcase_linked_list.html - Operators and Overloading
https://doc.rust-lang.org/book/operators-and-overloading.html - Module std::ops
https://doc.rust-lang.org/std/ops/index.html - Module std::cmp
https://doc.rust-lang.org/std/cmp/index.html - Trait std::ops::Add
https://doc.rust-lang.org/stable/std/ops/trait.Add.html - Trait std::ops::AddAssign
https://doc.rust-lang.org/std/ops/trait.AddAssign.html - Trait std::ops::Drop
https://doc.rust-lang.org/std/ops/trait.Drop.html - Trait std::cmp::Eq
https://doc.rust-lang.org/std/cmp/trait.Eq.html - Struct std::boxed::Box
https://doc.rust-lang.org/std/boxed/struct.Box.html - Explore the ownership system in Rust
https://nercury.github.io/rust/guide/2015/01/19/ownership.html - Rust's ownership and move semantic
http://www.slideshare.net/saneyuki/rusts-ownership-and-move-semantics - Trait std::marker::Copy
https://doc.rust-lang.org/stable/std/marker/trait.Copy.html - Trait std::clone::Clone
https://doc.rust-lang.org/stable/std/clone/trait.Clone.html - The Stack and the Heap
https://doc.rust-lang.org/book/the-stack-and-the-heap.html - Rust Compare: Pointers & References
http://www.rust-compare.com/site/pointers.html - Rust Compare: Parameters
http://www.rust-compare.com/site/params.html - Why does this compile? Automatic dereferencing?
https://users.rust-lang.org/t/why-does-this-compile-automatic-dereferencing/2183 - Understanding Pointers, Ownership, and Lifetimes in Rust
http://koerbitz.me/posts/Understanding-Pointers-Ownership-and-Lifetimes-in-Rust.html - Rust lang series episode #25 — pointers (#rust-series)
https://steemit.com/rust-series/@jimmco/rust-lang-series-episode-25-pointers-rust-series - Rust – home page
https://www.rust-lang.org/en-US/ - Rust – Frequently Asked Questions
https://www.rust-lang.org/en-US/faq.html - Destructuring and Pattern Matching
https://pzol.github.io/getting_rusty/posts/20140417_destructuring_in_rust/ - The Rust Programming Language
https://doc.rust-lang.org/book/ - Rust (programming language)
https://en.wikipedia.org/wiki/Rust_%28programming_language%29 - Go – home page
https://golang.org/ - Stack Overflow – Most Loved, Dreaded, and Wanted language
https://stackoverflow.com/research/developer-survey-2016#technology-most-loved-dreaded-and-wanted - Rust vs Go (dva roky staré hodnocení, od té doby došlo k posunům v obou jazycích)
http://jaredforsyth.com/2014/03/22/rust-vs-go/ - Rust vs Go: My experience
https://www.reddit.com/r/golang/comments/21m6jq/rust_vs_go_my_experience/ - Friends of Rust (Organizations running Rust in production)
https://www.rust-lang.org/en-US/friends.html - Rust programs versus C++ g++
https://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=rust&lang2=gpp - Další benchmarky (nejedná se o reálné příklady „ze života“)
https://github.com/kostya/benchmarks - Go na Redditu
https://www.reddit.com/r/golang/ - Rust vs. Go
http://vschart.com/compare/rust/vs/go-language - Abstraction without overhead: traits in Rust
https://blog.rust-lang.org/2015/05/11/traits.html - Method Syntax
https://doc.rust-lang.org/book/method-syntax.html - Traits in Rust
https://doc.rust-lang.org/book/traits.html - Functional Programming in Rust – Part 1 : Function Abstraction
http://blog.madhukaraphatak.com/functional-programming-in-rust-part-1/ - Of the emerging systems languages Rust, D, Go and Nim, which is the strongest language and why?
https://www.quora.com/Of-the-emerging-systems-languages-Rust-D-Go-and-Nim-which-is-the-strongest-language-and-why - Chytré ukazatele (moderní verze jazyka C++) [MSDN]
https://msdn.microsoft.com/cs-cz/library/hh279674.aspx - UTF-8 Everywhere
http://utf8everywhere.org/ - Rust by Example
http://rustbyexample.com/ - Rust oficiálně ve Fedoře
https://mojefedora.cz/rust-oficialne-ve-fedore/ - Resource acquisition is initialization
https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization - TIOBE index (October 2016)
http://www.tiobe.com/tiobe-index/ - Porovnání Go, D a Rustu na OpenHubu:
https://www.openhub.net/languages/compare?language_name[]=-1&language_name[]=-1&language_name[]=dmd&language_name[]=golang&language_name[]=rust&language_name[]=-1&measure=commits - String Types in Rust
http://www.suspectsemantics.com/blog/2016/03/27/string-types-in-rust/ - Trait (computer programming)
https://en.wikipedia.org/wiki/Trait_%28computer_programming%29 - Type inference
https://en.wikipedia.org/wiki/Type_inference