Hlavní navigace

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

3. 8. 2017
Doba čtení: 22 minut

Sdílet

 Autor: Rust project
Popis možností velmi užitečné knihovny ndarray vytvořené v Rustu dnes dokončíme. Budeme se zabývat především různými způsoby aplikací funkcí na prvky polí, což je jedna z technik umožňujících vyhnout se nutnosti explicitního psaní programových smyček.

Obsah

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

2. Funkce vyššího řádu map a jednorozměrná pole

3. Předávání prvků hodnotou a referencí, automatická dereference prvků

4. Funkce vyššího řádu map a dvourozměrná i trojrozměrná pole

5. Složitější příklad: vektory a pole obsahující komplexní čísla

6. Funkce vyššího řádu map a pole komplexních čísel

7. Přímá modifikace prvků pole pomocí funkce mapv_inplace

8. Funkce mapv_into: kombinace mapv a mapv_inplace

9. Použití funkce vyššího řádu fold (reduce)

10. Makro azip! pro manipulaci s prvky většího množství polí

11. Použití struktury Zip

12. Větší množství producentů při použití struktury Zip

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

14. Odkazy na Internetu

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

Již ve třetí části tohoto seriálu jsme se seznámili s funkcemi použitelnými při zpracování sekvencí vytvářených pomocí iterátorů. Tyto funkce mají jména i sémantiku převzatou z funkcionálních jazyků (poprvé se objevily v LISPu, který sice není čistě funkcionální, ale nalezneme v něm mnoho prvků, které se do FP jazyků posléze dostaly). Mezi zmíněné funkce – které by byly ve funkcionálních jazycích implementovány funkcemi vyššího řádu – patří zejména map, filter, take, take_while a fold (někde se nazývá reduce).

Rustovské funkce vyššího řádu ze standardní knihovny jsou určeny pro zpracování sekvencí, přičemž výsledkem jejich aplikace může být jiná sekvence s obecně odlišným počtem prvků. Typickým příkladem je zejména funkce filter vracející odlišnou sekvenci (v extrémním případě i sekvenci prázdnou). Některé funkce vyššího řádu nalezneme i v knihovně ndarray, ovšem v tomto případě se pochopitelně bude jednat o funkce, které na vstupu očekávají pole (jednorozměrný vektor, dvourozměrnou matici, vícerozměrné pole…) a výsledkem může být opět pole popř. jediná hodnota libovolného typu. Mezi funkce vyššího řádu, které nalezneme v knihovně ndarray, patří především:

Funkce/makro Význam Popsána v kapitole
map postupná aplikace funkce na každý prvek, výsledkem je nové pole 2
mapv postupná aplikace funkce na každý prvek předaný hodnotou 3
mapv_inplace dtto, ale původní pole se změní (nevytvoří se nové) 7
mapv_into kombinace předchozích dvou funkcí 8
fold postupná „redukce“ prvků v poli s akumulací výsledku 9
azip! makro umožňující například kombinaci prvků dvou polí 10

2. Funkce vyššího řádu map a jednorozměrná pole

Základní funkcí vyššího řádu, kterou v knihovně ndarray nalezneme, je funkce nazvaná map. Ta postupně aplikuje jí předanou funkci na jednotlivé prvky nějakého pole (nezávisle na počtu dimenzí) a vytváří tak pole nové. Tento čistě funkcionální přístup, kdy se původní pole nemění, je sice velmi užitečný a z hlediska teorie „čistý“, ovšem lze ho aplikovat jen na relativně malá pole, nikoli například na rozsáhlé matice s miliony prvků. V takovém případě by se totiž zvyšovaly paměťové nároky, snižovala by se efektivnost cache atd.

Podívejme se nyní na způsob aplikace (anonymních) funkcí na prvky vektoru obsahujícího hodnoty 0 až 12. Povšimněte si, že se ve funkci map pracuje s referencí na prvky, což je výhodné, protože se neprovádí jejich klonování:

let vector1 = Array::from_iter(0..12);
 
// vektor s prvky 0, 2, 4 ...
let vector2 = vector1.map(|x| 2* *x);
 
// vektor s prvky 0, 1, 0, 1, ... (1 pro sudé hodnoty)
let vector3 = vector1.map(|x| *x % 2);
 
// vektor s prvky true, false, true, false (test, zda je hodnota sudá)
let vector4 = vector1.map(|x| *x % 2 == 0);
 
println!("vector1: {}", vector1);
println!("vector2: {}", vector2);
println!("vector3: {}", vector3);
println!("vector4: {}", vector4);

Výsledek běhu výše uvedeného úryvku kódu:

vector1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
vector2: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]
vector3: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
vector4: [true, false, true, false, true, false, true, false, true, false, true, false]

Poznámka: povšimněte si, že nově vytvořené pole může obsahovat prvky jiného typu než pole původní.

3. Předávání prvků hodnotou a referencí, automatická dereference prvků

Pokud chcete prvky předávat hodnotou a nikoli odkazem (referencí), použijte namísto funkce map funkci nazvanou mapv. V (typicky) anonymní funkci se bude pracovat přímo s hodnotami prvků, což mj. znamená, že může dojít k jejich klonování:

let vector1 = Array::from_iter(0..12);
 
// vektor s prvky 0, 2, 4 ...
let vector2 = vector1.mapv(|x| 2*x);
 
// vektor s prvky 0, 1, 0, 1, ... (1 pro sudé hodnoty)
let vector3 = vector1.mapv(|x| x % 2);
 
// vektor s prvky true, false, true, false (test, zda je hodnota sudá)
let vector4 = vector1.mapv(|x| x % 2 == 0);
 
println!("vector1: {}", vector1);
println!("vector2: {}", vector2);
println!("vector3: {}", vector3);
println!("vector4: {}", vector4);

Výsledek běhu výše uvedeného úryvku kódu:

vector1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
vector2: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]
vector3: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
vector4: [true, false, true, false, true, false, true, false, true, false, true, false]

Ve skutečnosti se díky automatickému použití traitu Deref může i v anonymní funkci předané do map pracovat přímo s hodnotami prvků:

let vector1 = Array::from_iter(0..12);
 
// vektor s prvky 0, 2, 4 ...
let vector2 = vector1.map(|x| 2*x);
 
// vektor s prvky 0, 1, 0, 1, ... (1 pro sudé hodnoty)
let vector3 = vector1.map(|x| x % 2);
 
// vektor s prvky true, false, true, false (test, zda je hodnota sudá)
let vector4 = vector1.map(|x| x % 2 == 0);
 
println!("vector1: {}", vector1);
println!("vector2: {}", vector2);
println!("vector3: {}", vector3);
println!("vector4: {}", vector4);

Výsledek běhu výše uvedeného úryvku kódu je shodný s předchozím výsledkem:

vector1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
vector2: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]
vector3: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
vector4: [true, false, true, false, true, false, true, false, true, false, true, false]

4. Funkce vyššího řádu map a dvourozměrná i trojrozměrná pole

Funkci vyššího řádu map samozřejmě nemusíme používat pouze pro jednorozměrné vektory, ale lze ji zavolat i pro dvourozměrné matice či pro vícerozměrná pole. Podívejme se na aplikaci různých anonymních funkcí na prvky matice 3×4 prvky:

let array1 = Array::from_iter(0..12).into_shape((3,4)).unwrap();
 
// vektor s prvky 0, 2, 4 ...
let array2 = array1.map(|x| 2* *x);
 
// vektor s prvky 0, 1, 0, 1, ... (1 pro sudé hodnoty)
let array3 = array1.map(|x| *x % 2);
 
// vektor s prvky true, false, true, false (test, zda je hodnota sudá)
let array4 = array1.map(|x| *x % 2 == 0);
 
println!("array1:\n{}\n", array1);
println!("array2:\n{}\n", array2);
println!("array3:\n{}\n", array3);
println!("array4:\n{}\n", array4);

Výsledek běhu výše uvedeného úryvku kódu:

array1:
[[0, 1, 2, 3],
 [4, 5, 6, 7],
 [8, 9, 10, 11]]
 
array2:
[[0, 2, 4, 6],
 [8, 10, 12, 14],
 [16, 18, 20, 22]]
 
array3:
[[0, 1, 0, 1],
 [0, 1, 0, 1],
 [0, 1, 0, 1]]
 
array4:
[[true, false, true, false],
 [true, false, true, false],
 [true, false, true, false]]

Aplikace na trojrozměrné pole o rozměrech 3×3×3 prvky:

let array1 = Array::from_iter(0..27).into_shape((3,3,3)).unwrap();
 
// vektor s prvky 0, 2, 4 ...
let array2 = array1.map(|x| 2* *x);
 
// vektor s prvky 0, 1, 0, 1, ... (1 pro sudé hodnoty)
let array3 = array1.map(|x| *x % 2);
 
// vektor s prvky true, false, true, false (test, zda je hodnota sudá)
let array4 = array1.map(|x| *x % 2 == 0);
 
println!("array1:\n{}\n", array1);
println!("array2:\n{}\n", array2);
println!("array3:\n{}\n", array3);
println!("array4:\n{}\n", array4);

Výsledek běhu výše uvedeného úryvku kódu bude nyní následující:

array1:
[[[0, 1, 2],
  [3, 4, 5],
  [6, 7, 8]],
 [[9, 10, 11],
  [12, 13, 14],
  [15, 16, 17]],
 [[18, 19, 20],
  [21, 22, 23],
  [24, 25, 26]]]
 
array2:
[[[0, 2, 4],
  [6, 8, 10],
  [12, 14, 16]],
 [[18, 20, 22],
  [24, 26, 28],
  [30, 32, 34]],
 [[36, 38, 40],
  [42, 44, 46],
  [48, 50, 52]]]
 
array3:
[[[0, 1, 0],
  [1, 0, 1],
  [0, 1, 0]],
 [[1, 0, 1],
  [0, 1, 0],
  [1, 0, 1]],
 [[0, 1, 0],
  [1, 0, 1],
  [0, 1, 0]]]
 
array4:
[[[true, false, true],
  [false, true, false],
  [true, false, true]],
 [[false, true, false],
  [true, false, true],
  [false, true, false]],
 [[true, false, true],
  [false, true, false],
  [true, false, true]]]

5. Složitější příklad: vektory a pole obsahující komplexní čísla

Ukažme si nyní nepatrně složitější příklad, v němž budeme používat vektory a pole obsahující komplexní čísla. Celý příklad je rozdělen do několika částí. První část specifikuje, které moduly (a jejich jmenné prostory) budeme využívat:

extern crate ndarray;
use ndarray::Array;
use std::fmt;

Dále deklarujeme strukturu reprezentující komplexní čísla. Její nejjednodušší varianta (bez použití generických typů) bude následující:

struct Complex {
    real: f32,
    imag: f32,
}

Pro komplexní čísla dále implementujeme konstruktor new a funkci pro výpočet absolutní hodnoty abs. Právě tato funkce je v kontextu tohoto příkladu důležitá:

impl Complex {
    fn new(real: f32, imag: f32) -> Complex {
        Complex{real:real, imag:imag}
    }
 
    fn abs(&self) -> f32 {
        (self.real * self.real + self.imag * self.imag).sqrt()
    }
}

Pro snazší výpis vektorů a polí komplexních čísel navíc implementujeme i funkci fmt, která bude automaticky volána v makru println! (funkce fmt má podobný význam jako metoda toString v Javě, jen je ji možné použít i pro zápis do souborů atd.):

impl fmt::Debug for Complex {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}+{}i", self.real, self.imag)
    }
}

6. Funkce vyššího řádu map a pole komplexních čísel

Další funkce definovaná v demonstračním příkladu nejdříve vytvoří jednorozměrný vektor obsahující pětici komplexních čísel a následně aplikuje funkci/metodu Complex::abs na každý prvek tohoto vektoru. Výsledkem bude opět vektor, tentokrát však jeho prvky budou mít typ f32 a nikoli Complex!. Ostatně se o tom snadno přesvědčíme na výstupu:

fn map_complex() {
    println!("map_complex");
 
    let vector1 = Array::from_vec(vec![Complex::new(0.0, 0.0),
                                       Complex::new(1.0, 0.0),
                                       Complex::new(0.0, 1.0),
                                       Complex::new(1.0, 1.0),
                                       Complex::new(100.0, 100.0)]);
 
    let vector2 = vector1.map(|x| Complex::abs(x));
 
    println!("vector1: {:?}", vector1);
    println!("vector2: {:?}", vector2);
 
    println!()
}

Prakticky stejným způsobem můžeme vytvořit matici komplexních čísel o rozměrech 3×3 prvky. Po aplikaci funkce/metody Complex::abs na jednotlivé prvky této matice získáme matici novou, tentokrát však budou prvky této matice 3×3 prvky typu f32:

fn map_complex_2d() {
    println!("map_complex_2d");
 
    let array1 = Array::from_shape_vec((3,3), vec![Complex::new(0.0, 0.0),
                                                   Complex::new(1.0, 0.0),
                                                   Complex::new(2.0, 0.0),
                                                   Complex::new(0.0, 1.0),
                                                   Complex::new(1.0, 1.0),
                                                   Complex::new(2.0, 1.0),
                                                   Complex::new(0.0, 2.0),
                                                   Complex::new(1.0, 2.0),
                                                   Complex::new(2.0, 2.0)]).unwrap();
 
    let array2 = array1.map(|x| Complex::abs(x));
 
    println!("vector1:\n{:?}\n", array1);
    println!("vector2:\n{:?}\n", array2);
 
    println!()
}

Nyní nám jen zbývá obě výše uvedené funkce zavolat:

fn main() {
    map_complex();
    map_complex_2d();
}

Po překladu a spuštění tohoto příkladu by se měly na standardní výstup vypsat následující řádky:

map_complex
vector1: [0+0i, 1+0i, 0+1i, 1+1i, 100+100i] shape=[5], strides=[1], layout=C | F (0x3)
vector2: [0, 1, 1, 1.4142135, 141.42136] shape=[5], strides=[1], layout=C | F (0x3)
 
map_complex_2d
vector1:
[[0+0i, 1+0i, 2+0i],
 [0+1i, 1+1i, 2+1i],
 [0+2i, 1+2i, 2+2i]] shape=[3, 3], strides=[3, 1], layout=C (0x1)
 
vector2:
[[0, 1, 2],
 [1, 1.4142135, 2.236068],
 [2, 2.236068, 2.828427]] shape=[3, 3], strides=[3, 1], layout=C (0x1)

7. Přímá modifikace prvků pole pomocí funkce mapv_inplace

Jak jsme si již vysvětlili u popisu funkce map, může být čistě funkcionální přístup při zpracování rozsáhlých polí problematický, protože vede k neustálému vytváření polí nových. Jedno z řešení spočívá v aplikaci nějaké zvolené funkce na prvky pole, ovšem s tím rozdílem, že se původní prvek přepíše výslednou hodnotou dané funkce. Celé zpracování je tedy skutečně provedeno „in situ“ se všemi výhodami (žádná další spotřeba paměti) i nevýhodami (měníme stav objektu, což je obecně postup, kterému se snažíme vyhnout).

Vzhledem k tomu, že se prvky pole modifikují, musí být celé pole měnitelné (mutable):

let mut vector1 = Array::from_iter(0..12);
 
println!("vector1: {}", vector1);

Na takové pole (zde konkrétně jednorozměrný vektor) můžeme volat funkci mapv_inplace. Povšimněte si, jak lze použít rozhodovací konstrukci if-then-else:

vector1.mapv_inplace(|x| x*2);
 
println!("vector1: {}", vector1);
 
vector1.mapv_inplace(|x| if x<10 {0} else {1});
 
println!("vector1: {}", vector1);

Výsledek běhu výše uvedeného úryvku kódu:

vector1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
vector1: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]
vector1: [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]

Podobně budeme postupovat u dvourozměrných polí:

let mut array1 = Array::from_iter(0..12).into_shape((3,4)).unwrap();
 
println!("array1:\n{}\n", array1);
 
array1.mapv_inplace(|x| if x%2 == 0 {0} else {1});
 
println!("array1:\n{}\n", array1);

Výsledek běhu výše uvedeného úryvku kódu:

array1:
[[0, 1, 2, 3],
 [4, 5, 6, 7],
 [8, 9, 10, 11]]
 
array1:
[[0, 1, 0, 1],
 [0, 1, 0, 1],
 [0, 1, 0, 1]]

Aplikace na trojrozměrné pole o rozměrech 3×3×3 prvky:

let mut array1 = Array::from_iter(0..27).into_shape((3,3,3)).unwrap();
 
println!("array1:\n{}\n", array1);
 
array1.mapv_inplace(|x| if x%2 == 0 {0} else {1});
 
println!("array1:\n{}\n", array1);

Výsledek běhu výše uvedeného úryvku kódu:

array1:
[[[0, 1, 2],
  [3, 4, 5],
  [6, 7, 8]],
 [[9, 10, 11],
  [12, 13, 14],
  [15, 16, 17]],
 [[18, 19, 20],
  [21, 22, 23],
  [24, 25, 26]]]
 
array1:
[[[0, 1, 0],
  [1, 0, 1],
  [0, 1, 0]],
 [[1, 0, 1],
  [0, 1, 0],
  [1, 0, 1]],
 [[0, 1, 0],
  [1, 0, 1],
  [0, 1, 0]]]

8. Funkce mapv_into: kombinace mapv a mapv_inplace

Kromě již popsaných funkcí vyššího řádu map, mapv a mapv_inplace existuje ještě funkce nazvaná mapv_into, která přebírá vlastnosti obou předchozích variant mapv*. Funkce mapv_into totiž „konzumuje“ původní pole a vrací pole nové. Ve skutečnosti ovšem opět pracuje „in situ“, takže je její použití paměťově efektivní, ovšem navíc se tato funkce chová více funkcionálně, protože po konzumaci původního pole ho již není možné použít a musíme namísto toho použít návratovou hodnotu mapv_into (toto chování si zkontroluje překladač):

let vector1 = Array::from_iter(0..12);
let vector2 = vector1.mapv_into(|x| x*2);
let vector3 = vector2.mapv_into(|x| if x<10 {0} else {1});
 
println!("vector3: {}", vector3);

Výsledek běhu výše uvedeného úryvku kódu:

vector3: [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]

9. Použití funkce vyššího řádu fold (reduce)

Další velmi užitečnou funkcí vyššího řádu, která nesmí chybět v repertoáru žádné funkcionálně zaměřené knihovny (a tím pádem ani v repertoáru „funkcionální“ části knihovny ndarray), je funkce pojmenovaná reduce popř. fold. Názvy těchto funkcí naznačují jejich účel – dochází k postupné „redukci“ prvků uložených v poli, a to (postupnou) aplikací zvolené uživatelské funkce na jednotlivé prvky a po krocích počítaný mezivýsledek (někdy se podle svého chování nazývá akumulátor).

V knihovně ndarray se příslušná metoda, která toto chování pro libovolné pole zajišťuje, jmenuje fold a akceptuje dva parametry – počáteční hodnotu akumulátoru (mezivýsledku) a uzávěr, což je zjednodušeně řečeno funkce (typicky anonymní), která na sebe má navázánu alespoň jednu volnou proměnnou. Tento uzávěr je postupně volán se dvěma argumenty, tedy s aktuální hodnotou akumulátoru (mezivýsledku) a prvkem získaným ze vstupní sekvence. Akumulátor je po každém volání modifikován tak, že obsahuje hodnotou vrácenou uzávěrem.

Podívejme se na příklad, v němž sečteme všechny prvky jednorozměrného pole a posléze vypočteme součin všech prvků. Při výpočtu součtu je počáteční hodnota akumulátoru nulová, u součinu musí být samozřejmě nastavena na jedničku:

let vector1 = Array::from_iter(1..11);
 
let sum = vector1.fold(0, |acc, value| acc+value);
 
let product = vector1.fold(1, |acc, value| acc*value);
 
println!("vector1: {}", vector1);
println!("sum: {}", sum);
println!("product: {}", product);

Výsledek běhu výše uvedeného úryvku kódu:

vector1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
sum: 55
product: 3628800

Ve složitějším příkladu provedeme stejnou operaci, ovšem s maticí 3×3 prvky. Navíc ještě vypočteme součet prvků s počáteční hodnotou akumulátoru nastavenou na 1000:

let array = Array::from_shape_vec((3,3), vec![1,2,3,4,5,6,7,8,9]).unwrap();
 
let sum = array.fold(0, |acc, value| acc+value);
let sum_offset = array.fold(1000, |acc, value| acc+value);
 
let product = array.fold(1, |acc, value| acc*value);
 
println!("array:\n{}\n", array);
println!("sum: {}", sum);
println!("sum_offset: {}", sum_offset);
println!("product: {}", product);

Výsledek běhu výše uvedeného úryvku kódu:

array:
[[1, 2, 3],
 [4, 5, 6],
 [7, 8, 9]]
 
sum: 45
sum_offset: 1045
product: 362880

10. Makro azip! pro manipulaci s prvky většího množství polí

Minule jsme si mj. popsali i operátory, které byly přetížené takovým způsobem, aby je bylo možné využít pro součet, rozdíl, součin či podíl korespondujících prvků dvou polí. Ve skutečnosti je ovšem použití přetížených operátorů poměrně omezené, což ale nemusí představovat větší problém, a to díky existenci makra nazvaného azip!. Toto makro dokáže zpracovat větší množství vstupních polí, přičemž aplikuje zvolený výraz na každou kombinaci korespondujících prvků vstupních polí. Nejlepší bude, když si ukážeme jednoduchý příklad pro součet dvou vektorů.

Mějme dva vstupní vektory:

let vector1 = Array::from_iter(1..11);
let vector2 = Array::from_iter(1..11);

A vektor pro uložení výsledku. Tento vektor musí mít shodnou velikost a musí být samozřejmě měnitelný (mutable):

let mut result = Array::zeros((10));

Nyní korespondující prvky vektoru sečteme použitím makra azip!. Před slovem in se specifikuje výsledné pole i pole vstupní. Výraz ve složených závorkách se aplikuje na korespondující prvky vektorů:

azip!(mut result, vector1, vector2 in { *result = vector1 + vector2 });
 
println!("vector1: {}", vector1);
println!("vector2: {}", vector2);
println!("result sum: {}", result);

Původní vstupní vektory nejsou „zkonzumovány“, takže je lze použít v dalším výpočtu:

let mut result_mul = Array::zeros((10));
 
azip!(mut result_mul, vector1, vector2 in { *result_mul = vector1 * vector2 });
println!("result_mul: {}", result_mul);

Výsledek běhu výše uvedeného úryvku kódu:

vector1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
vector2: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result sum: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
result_mul: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

11. Použití struktury Zip

Ve skutečnosti se makro azip! expanduje na složitější výraz se strukturou Zip. Tato struktura poskytuje (přesněji řečeno implementuje) několik funkcí umožňujících specifikovat výstupní pole, vstupní (zdrojová) pole i prováděnou operaci. Tato operace se typicky zadává formou anonymní funkce předané do funkce vyššího řádu apply. Opět se podívejme na příklad podobný příkladu z předchozí kapitoly.

Mějme dva vstupní vektory:

let vector1 = Array::from_iter(1..11);
let vector2 = Array::from_iter(1..11);

A vektor pro uložení výsledku. Tento vektor musí mít shodnou velikost a musí být samozřejmě měnitelný (mutable):

let mut result = Array::zeros((10));

Nyní s využitím metod from a and specifikujeme vstupní a výstupní pole a následně přes funkci vyššího řádu apply uvedeme, jaká operace se má provádět s korespondujícími prvky vstupních polí. Ty se předávají přes referenci:

Zip::from(&mut result)
     .and(&vector1)
     .and(&vector2)
     .apply(|result, &vector1, &vector2| {
          *result = vector1 + vector2
     });
 
println!("vector1: {}", vector1);
println!("vector2: {}", vector2);
println!("result sum: {}", result);

Výsledek běhu výše uvedeného úryvku kódu:

vector1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
vector2: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result sum: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

12. Větší množství producentů při použití struktury Zip

Ve skutečnosti je možné specifikovat větší (prakticky libovolné) množství vstupních polí, což je patrné z následujícího příkladu se čtyřmi vstupními poli:

let vector1 = Array::from_iter(1..11);
let vector2 = Array::from_iter(1..11);
let vector3 = Array::from_vec(vec![1,2,3,1,2,3,1,2,3,1000]);
let vector4 = Array::from_vec(vec![1,1,1,2,2,2,3,3,3,4]);
 
let mut result = Array::zeros((10));
 
Zip::from(&mut result)
     .and(&vector1)
     .and(&vector2)
     .and(&vector3)
     .and(&vector4)
     .apply(|result, &vector1, &vector2, &vector3, &vector4| {
          *result = vector1 + vector2 * vector3 / vector4
     });
 
println!("vector1: {}", vector1);
println!("vector2: {}", vector2);
println!("vector3: {}", vector3);
println!("vector4: {}", vector4);
println!("result sum: {}", result);

Výsledek běhu výše uvedeného úryvku kódu:

vector1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
vector2: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
vector3: [1, 2, 3, 1, 2, 3, 1, 2, 3, 1000]
vector4: [1, 1, 1, 2, 2, 2, 3, 3, 3, 4]
result sum: [2, 6, 12, 6, 10, 15, 9, 13, 18, 2510]

Poznámka: zápis je poměrně složitý, což je i jeden z důvodů, proč je mnohem jednodušší používat makro azip!.

13. 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):

CS24_early

Každý projekt se přeloží příkazem:

cargo build

Jeho následné spuštění zajistí příkaz:

cargo clean

14. Odkazy na Internetu

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