Hlavní navigace

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

Pavel Tišnovský

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.

Doba čtení: 22 minut

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í):

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
Našli jste v článku chybu?