Hlavní navigace

Programovací jazyk Rust: knihovna ndarray pro práci s n-rozměrnými poli (2)

27. 7. 2017
Doba čtení: 22 minut

Sdílet

 Autor: Rust project
Dnešní článek o programovacím jazyku Rust i o užitečných knihovnách připravených pro tento jazyk bude navazovat na předchozí část, protože se opět budeme zabývat knihovnou ndarray určenou pro práci s n-rozměrnými poli, samozřejmě včetně běžných vektorů a matic.

Obsah

1. Programovací jazyk Rust: knihovna ndarray pro práci s n-rozměrnými poli (2)

2. Pohledy na pole

3. Měnitelný pohled na pole

4. Řezy jednorozměrných polí

5. Použití záporných indexů

6. Specifikace kroku při tvorbě řezu polem

7. Záporný krok

8. Řezy dvourozměrných polí

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

13. Broadcasting

14. Broadcasting při použití skalární hodnoty

15. Repositář s demonstračními příklady

16. Odkazy na Internetu

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:

  1. Řezem pole (slice)
  2. Rozšířením velikosti a/nebo počtu dimenzí (broadcast)
  3. 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í):

CS24_early

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/pre­sentations. 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):

16. Odkazy na Internetu

  1. ndarray – dokumentace k modulu
    https://bluss.github.io/rust-ndarray/master/ndarray/index.html
  2. ndarray – Crate
    https://crates.io/crates/ndarray
  3. rustup
    https://www.rustup.rs/
  4. rustup: the Rust toolchain installer (Git repositář + dokumentace)
    https://github.com/rust-lang-nursery/rustup.rs
  5. The Rust FFI Omnibus
    http://jakegoulding.com/rust-ffi-omnibus/
  6. Build Script Support
    http://doc.crates.io/build-script.html
  7. Calling Rust From Python
    https://bheisler.github.i­o/post/calling-rust-in-python/
  8. Calling Rust in Python (komentáře k předchozímu článku)
    https://www.reddit.com/r/rus­t/comments/63iy5a/calling_rus­t_in_python/
  9. CFFI Documentation
    https://cffi.readthedocs.i­o/en/latest/
  10. Build Script Support
    http://doc.crates.io/build-script.html
  11. Creating a shared and static library with the gnu compiler [gcc]
    http://www.adp-gmbh.ch/cpp/gcc/create_lib.html
  12. ctypes — A foreign function library for Python
    https://docs.python.org/2/li­brary/ctypes.html
  13. FFI: Foreign Function Interface
    https://doc.rust-lang.org/book/ffi.html
  14. Primitive Type pointer
    https://doc.rust-lang.org/std/primitive.pointer.html
  15. Cargo: správce projektů a balíčků pro programovací jazyk Rust
    https://mojefedora.cz/cargo-spravce-projektu-a-balicku-pro-programovaci-jazyk-rust/
  16. Network Communication and Serialization in Rust
    https://www.safaribookson­line.com/blog/2014/01/28/net­work-communication-serialization-rust/
  17. Crate bincode
    http://tyoverby.com/binco­de/bincode/index.html
  18. Struct std::fs::File
    https://doc.rust-lang.org/std/fs/struct.File.html
  19. Trait std::io::Seek
    https://doc.rust-lang.org/std/io/trait.Seek.html
  20. Trait std::io::Read
    https://doc.rust-lang.org/std/io/trait.Read.html
  21. Trait std::io::Write
    https://doc.rust-lang.org/std/io/trait.Write.html
  22. Trait std::io::BufRead
    https://doc.rust-lang.org/std/io/trait.BufRead.html
  23. Module std::io::prelude
    https://doc.rust-lang.org/std/io/prelude/index.html
  24. std::net::IpAddr
    https://doc.rust-lang.org/std/net/enum.IpAddr.html
  25. std::net::Ipv4Addr
    https://doc.rust-lang.org/std/net/struct.Ipv4Addr.html
  26. std::net::Ipv6Addr
    https://doc.rust-lang.org/std/net/struct.Ipv6Addr.html
  27. TcpListener
    https://doc.rust-lang.org/std/net/struct.TcpLis­tener.html
  28. TcpStream
    https://doc.rust-lang.org/std/net/struct.TcpStream.html
  29. Binary heap (Wikipedia)
    https://en.wikipedia.org/wi­ki/Binary_heap
  30. Binární halda (Wikipedia)
    https://cs.wikipedia.org/wi­ki/Bin%C3%A1rn%C3%AD_halda
  31. Halda (datová struktura)
    https://cs.wikipedia.org/wi­ki/Halda_%28datov%C3%A1_struk­tura%29
  32. Struct std::collections::HashSet
    https://doc.rust-lang.org/std/collections/struc­t.HashSet.html
  33. Struct std::collections::BTreeSet
    https://doc.rust-lang.org/std/collections/struc­t.BTreeSet.html
  34. Struct std::collections::BinaryHeap
    https://doc.rust-lang.org/std/collections/struc­t.BinaryHeap.html
  35. Set (abstract data type)
    https://en.wikipedia.org/wi­ki/Set_%28abstract_data_ty­pe%29#Language_support
  36. Associative array
    https://en.wikipedia.org/wi­ki/Associative_array
  37. Hash Table
    https://en.wikipedia.org/wi­ki/Hash_table
  38. B-tree
    https://en.wikipedia.org/wiki/B-tree
  39. Pedro Celis: Robin Hood Hashing (naskenované PDF!)
    https://cs.uwaterloo.ca/re­search/tr/1986/CS-86–14.pdf
  40. Robin Hood hashing
    http://codecapsule.com/2013/11/11/ro­bin-hood-hashing/
  41. Robin Hood hashing: backward shift deletion
    http://codecapsule.com/2013/11/17/ro­bin-hood-hashing-backward-shift-deletion/
  42. Module std::collections
    https://doc.rust-lang.org/std/collections/
  43. Module std::vec
    https://doc.rust-lang.org/nightly/std/vec/index.html
  44. Struct std::collections::VecDeque
    https://doc.rust-lang.org/std/collections/struc­t.VecDeque.html
  45. Struct std::collections::LinkedList
    https://doc.rust-lang.org/std/collections/struc­t.LinkedList.html
  46. Module std::fmt
    https://doc.rust-lang.org/std/fmt/
  47. Macro std::println
    https://doc.rust-lang.org/std/macro.println.html
  48. Enum std::result::Result
    https://doc.rust-lang.org/std/result/enum.Result.html
  49. Module std::result
    https://doc.rust-lang.org/std/result/
  50. Result
    http://rustbyexample.com/std/re­sult.html
  51. Rust stdlib: Option
    https://doc.rust-lang.org/std/option/enum.Option.html
  52. Module std::option
    https://doc.rust-lang.org/std/option/index.html
  53. Rust by example: option
    http://rustbyexample.com/std/op­tion.html
  54. Rust by example: if-let
    http://rustbyexample.com/flow_con­trol/if_let.html
  55. Rust by example: while let
    http://rustbyexample.com/flow_con­trol/while_let.html
  56. Rust by example: Option<i32>
    http://rustbyexample.com/std/op­tion.html
  57. An Overview of Macros in Rust
    http://words.steveklabnik.com/an-overview-of-macros-in-rust
  58. A Practical Intro to Macros in Rust 1.0
    https://danielkeep.github.io/practical-intro-to-macros.html
  59. The Rust Programming Language: macros
    https://doc.rust-lang.org/beta/book/macros.html
  60. Rust by example: 15 macro_rules!
    http://rustbyexample.com/macros.html
  61. Primitive Type isize
    https://doc.rust-lang.org/nightly/std/primi­tive.isize.html
  62. Primitive Type usize
    https://doc.rust-lang.org/nightly/std/primi­tive.usize.html
  63. Primitive Type array
    https://doc.rust-lang.org/nightly/std/primi­tive.array.html
  64. Module std::slice
    https://doc.rust-lang.org/nightly/std/slice/
  65. Rust by Example: 2.3 Arrays and Slices
    http://rustbyexample.com/pri­mitives/array.html
  66. What is the difference between Slice and Array (stackoverflow)
    http://stackoverflow.com/qu­estions/30794235/what-is-the-difference-between-slice-and-array
  67. Learning Rust With Entirely Too Many Linked Lists
    http://cglab.ca/~abeinges/blah/too-many-lists/book/
  68. Testcase: linked list
    http://rustbyexample.com/cus­tom_types/enum/testcase_lin­ked_list.html
  69. Operators and Overloading
    https://doc.rust-lang.org/book/operators-and-overloading.html
  70. Module std::ops
    https://doc.rust-lang.org/std/ops/index.html
  71. Module std::cmp
    https://doc.rust-lang.org/std/cmp/index.html
  72. Trait std::ops::Add
    https://doc.rust-lang.org/stable/std/ops/trait.Add.html
  73. Trait std::ops::AddAssign
    https://doc.rust-lang.org/std/ops/trait.AddAssign.html
  74. Trait std::ops::Drop
    https://doc.rust-lang.org/std/ops/trait.Drop.html
  75. Trait std::cmp::Eq
    https://doc.rust-lang.org/std/cmp/trait.Eq.html
  76. Struct std::boxed::Box
    https://doc.rust-lang.org/std/boxed/struct.Box.html
  77. Explore the ownership system in Rust
    https://nercury.github.io/rus­t/guide/2015/01/19/ownership­.html
  78. Rust's ownership and move semantic
    http://www.slideshare.net/sa­neyuki/rusts-ownership-and-move-semantics
  79. Trait std::marker::Copy
    https://doc.rust-lang.org/stable/std/marker/tra­it.Copy.html
  80. Trait std::clone::Clone
    https://doc.rust-lang.org/stable/std/clone/tra­it.Clone.html
  81. The Stack and the Heap
    https://doc.rust-lang.org/book/the-stack-and-the-heap.html
  82. Rust Compare: Pointers & References
    http://www.rust-compare.com/site/pointers.html
  83. Rust Compare: Parameters
    http://www.rust-compare.com/site/params.html
  84. Why does this compile? Automatic dereferencing?
    https://users.rust-lang.org/t/why-does-this-compile-automatic-dereferencing/2183
  85. Understanding Pointers, Ownership, and Lifetimes in Rust
    http://koerbitz.me/posts/Understanding-Pointers-Ownership-and-Lifetimes-in-Rust.html
  86. Rust lang series episode #25 — pointers (#rust-series)
    https://steemit.com/rust-series/@jimmco/rust-lang-series-episode-25-pointers-rust-series
  87. Rust – home page
    https://www.rust-lang.org/en-US/
  88. Rust – Frequently Asked Questions
    https://www.rust-lang.org/en-US/faq.html
  89. Destructuring and Pattern Matching
    https://pzol.github.io/get­ting_rusty/posts/20140417_des­tructuring_in_rust/
  90. The Rust Programming Language
    https://doc.rust-lang.org/book/
  91. Rust (programming language)
    https://en.wikipedia.org/wi­ki/Rust_%28programming_lan­guage%29
  92. Go – home page
    https://golang.org/
  93. Stack Overflow – Most Loved, Dreaded, and Wanted language
    https://stackoverflow.com/re­search/developer-survey-2016#technology-most-loved-dreaded-and-wanted
  94. 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/
  95. Rust vs Go: My experience
    https://www.reddit.com/r/go­lang/comments/21m6jq/rust_vs_go_my_ex­perience/
  96. Friends of Rust (Organizations running Rust in production)
    https://www.rust-lang.org/en-US/friends.html
  97. Rust programs versus C++ g++
    https://benchmarksgame.ali­oth.debian.org/u64q/compa­re.php?lang=rust&lang2=gpp
  98. Další benchmarky (nejedná se o reálné příklady „ze života“)
    https://github.com/kostya/benchmarks
  99. Go na Redditu
    https://www.reddit.com/r/golang/
  100. Rust vs. Go
    http://vschart.com/compare/rust/vs/go-language
  101. Abstraction without overhead: traits in Rust
    https://blog.rust-lang.org/2015/05/11/traits.html
  102. Method Syntax
    https://doc.rust-lang.org/book/method-syntax.html
  103. Traits in Rust
    https://doc.rust-lang.org/book/traits.html
  104. Functional Programming in Rust – Part 1 : Function Abstraction
    http://blog.madhukaraphatak­.com/functional-programming-in-rust-part-1/
  105. 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
  106. Chytré ukazatele (moderní verze jazyka C++) [MSDN]
    https://msdn.microsoft.com/cs-cz/library/hh279674.aspx
  107. UTF-8 Everywhere
    http://utf8everywhere.org/
  108. Rust by Example
    http://rustbyexample.com/
  109. Rust oficiálně ve Fedoře
    https://mojefedora.cz/rust-oficialne-ve-fedore/
  110. Resource acquisition is initialization
    https://en.wikipedia.org/wi­ki/Resource_acquisition_is_i­nitialization
  111. TIOBE index (October 2016)
    http://www.tiobe.com/tiobe-index/
  112. Porovnání Go, D a Rustu na OpenHubu:
    https://www.openhub.net/lan­guages/compare?language_na­me[]=-1&language_name[]=-1&language_name[]=dmd&lan­guage_name[]=golang&langu­age_name[]=rust&language_na­me[]=-1&measure=commits
  113. String Types in Rust
    http://www.suspectsemantic­s.com/blog/2016/03/27/str­ing-types-in-rust/
  114. Trait (computer programming)
    https://en.wikipedia.org/wi­ki/Trait_%28computer_program­ming%29
  115. Type inference
    https://en.wikipedia.org/wi­ki/Type_inference

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.