10.6 Rekurzivní kombinace prvků vektorů

Nekdy mame seznam objektu, na ktere potrebujeme aplikovat funkci, ktera vsak bere jen dva vstupy. Patrne nejdulezitejsi priklad takoveho uziti je spojeni mnoha tabulek ulozenych v seznamu pomoci funkce left_join(), se kterym se seznamite v kapitole 16. V takovém případě chceme aplikovat funkci postupně: nejprve spojit první dvě tabulky, pak k výsledku připojit třetí tabulku, k výsledku tohoto spojení čtvrtou atd. Balík purrr k tomuto účelu nabízí čtyři funkce: reduce(.x, .f, ..., .init, .dir = c("forward", "backward")) a reduce2(.x, .y, .f, ..., .init) a accumulate(.x, .f, ..., .init, .dir = c("forward", "backward")) a accumulate2(.x, .y, .f, ..., .init). My se zde podíváme jen na funkce reduce() a accumulate(), které pracují nad jedním seznamem; druhé dvě funkce pracují paralelně nad dvěma.

Funkce accumulate() postupně aplikuje na vektor .x funkci .f a vrací seznam stejné délky jako .x (nebo o 1 delší, viz dále). Prvním prvkem výsledného vektoru je .x[[1]], druhým .f(.x[[1]], .x[[2]]), třetím .f(.f(.x[[1]], .x[[2]]), .x[[3]]) atd. Pokud tedy “akumulujeme” atomický vektor čísel \(1, 2, \ldots, N\) pomocí funkce součtu +, pak dostaneme atomický vektor čísel \(1, 1 + 2, 1 + 2 + 3, \dots\), tedy totéž, co by nám vrátila funkce cumsum():

accumulate(1:5, `+`)
## [1]  1  3  6 10 15

Funkce reduce() funguje podobně, ale vrací jen finální výsledek akumulace, zde tedy součet všech prvků vektoru:

reduce(1:5, `+`)
## [1] 15

Podívejme se nyní na realističtější příklad. Řekněme, že máme seznam atomických vektorů, které obsahují id jedinců, kteří se zúčastnili nějakých akcí. Zajímá nás, kteří jedinci, se zúčastnili všech těchto akcí, tj. hledáme průnik všech těchto množin. K tomu slouží funkce intersect(x, y), která však umí vrátit pouze průnik dvou množin. Musíme ji tedy použít na všechny prvky seznamu rekurzivně. Protože nás zajímá jen finální průnik, použijeme funkci reduce():

riots <- list(  # seznam id účastníků různých akcí
  c(1, 2, 3, 7, 9),
  c(1, 4, 8, 9),
  c(1, 3, 5, 9)
)
reduce(riots, intersect)  # celkový průsečík množin
## [1] 1 9

Funkce reduce() a accumulate() umožňují zadat i počáteční hodnotu pomocí parametru .init. To se hodí v případě, kdy by akumulovaný vektor mohl být prázdný a my nechceme, aby výpočet zhavaroval.

v <- numeric(0)
reduce(v, `+`)
## Error: `.x` is empty, and no `.init` supplied
reduce(v, `+`, .init = 0)
## [1] 0

Pokud zadáme parametr .init, bude výsledek funkce accumulate() o 1 delší než vstupní vektor.

Parametr .dir umožňuje nastavit směr akumulace (implicitně se akumuluje od prvního prvku vektoru po poslední). Detaily najdete v dokumentaci.