diff --git a/concepts/lapply-sapply/.meta/config.json b/concepts/lapply-sapply/.meta/config.json new file mode 100644 index 00000000..957f3caa --- /dev/null +++ b/concepts/lapply-sapply/.meta/config.json @@ -0,0 +1,5 @@ +{ + "authors": ["colinleach"], + "contributors": [], + "blurb": "lapply() and sapply() will apply an unvectorized function to a vector or list" +} diff --git a/concepts/lapply-sapply/about.md b/concepts/lapply-sapply/about.md new file mode 100644 index 00000000..673e45ec --- /dev/null +++ b/concepts/lapply-sapply/about.md @@ -0,0 +1,78 @@ +# About + +We saw previously that many functions in R are "vectorized": they will operate across entire vectors, with no need to write explicit loops. + +Functions using only vectorized functions are themselves likely to be automatically vectorized. +In a trivial example: + +```R +> triple <- function(x) return(3 * x) +> triple(1:10) + [1] 3 6 9 12 15 18 21 24 27 30 + ``` + +However, more complex function bodies may not be fully vectorized, for example if they contain loops or if-else blocks. +Then we need a way to apply them to vectors (similar to `map()` in some other languages). + +Because R has a rich variety of data structures in the core language, it also has a whole family of `*apply()` functions to operate on them. +For now, consider `lapply()` and `sapply()`. + +## `lapply()` + +"List-apply" is designed to work on list inputs, but vectors will be silently coerced to lists as necessary. + +The output is a list, so this may need unpacking to get a vector: + +```R +h <- function(size) { + switch(size, + "small" = 6, + "medium" = 8, + "large" = 10 + ) +} + +> v <- c("small", "large", "medium") + +> h(v) # bad use of an unvectorized function +Error in switch(size, small = 6, medium = 8, large = 10) : + EXPR must be a length 1 vector + +> lapply(v, h) # output is a list +[[1]] +[1] 6 + +[[2]] +[1] 10 + +[[3]] +[1] 8 + +> unlist(lapply(v, h)) # output is a vector +[1] 6 10 8 +``` + +## `sapply()` + +"Simplified-apply" will do something similar, but in this case returns a named vector. +Depending on context, use of `unname()` may be necessary: + +```R +> sapply(v, h) + small large medium + 6 10 8 + +> unname(sapply(v, h)) +[1] 6 10 8 +``` + +## Advanced Topics: Alternative Approaches + +Base R also has a `vapply()` function. +This is similar to `sapply()` but also has a required parameter to specify the output format. + +Some developers prefer this extra level of predictability when writing library code. +Using it interactively or in simple functions is probably less useful. + +Outside Exercism, there is also the `purrr` package, which provides several additional options such as `map()`. +Less constrained by backwards compatibility than base R, this package is currently closer to modern functional programming in other languages. diff --git a/concepts/lapply-sapply/introduction.md b/concepts/lapply-sapply/introduction.md new file mode 100644 index 00000000..94f26e5b --- /dev/null +++ b/concepts/lapply-sapply/introduction.md @@ -0,0 +1,67 @@ +# Introduction + +We saw previously that many functions in R are "vectorized": they will operate across entire vectors, with no need to write explicit loops. + +Functions using only vectorized functions are themselves likely to be automatically vectorized. +In a trivial example: + +```R +> triple <- function(x) return(3 * x) +> triple(1:10) + [1] 3 6 9 12 15 18 21 24 27 30 + ``` + +However, more complex function bodies may not be fully vectorized, for example if they contain loops or if-else blocks. +Then we need a way to apply them to vectors (similar to `map()` in some other languages). + +Because R has a rich variety of data structures in the core language, it also has a whole family of `*apply()` functions to operate on them. +For now, consider `lapply()` and `sapply()`. + +## `lapply()` + +"List-apply" is designed to work on list inputs, but vectors will be silently coerced to lists as necessary. + +The output is a list, so this may need unpacking to get a vector: + +```R +h <- function(size) { + switch(size, + "small" = 6, + "medium" = 8, + "large" = 10 + ) +} + +> v <- c("small", "large", "medium") + +> h(v) # bad use of an unvectorized function +Error in switch(size, small = 6, medium = 8, large = 10) : + EXPR must be a length 1 vector + +> lapply(v, h) # output is a list +[[1]] +[1] 6 + +[[2]] +[1] 10 + +[[3]] +[1] 8 + +> unlist(lapply(v, h)) # output is a vector +[1] 6 10 8 +``` + +## `sapply()` + +"Simplified-apply" will do something similar, but returns a named vector. +Depending on context, use of `unname()` may be necessary: + +```R +> sapply(v, h) + small large medium + 6 10 8 + +> unname(sapply(v, h)) +[1] 6 10 8 +``` diff --git a/concepts/lapply-sapply/links.json b/concepts/lapply-sapply/links.json new file mode 100644 index 00000000..55039426 --- /dev/null +++ b/concepts/lapply-sapply/links.json @@ -0,0 +1,18 @@ +[ + { + "url": "https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/lapply", + "description": "lapply: Apply a Function over a List or Vector" + }, + { + "url": "https://www.dataquest.io/blog/apply-functions-in-r-sapply-lapply-tapply/", + "description": "Apply Functions in R with Examples" + }, + { + "url": "https://www.learnbyexample.org/r-apply-functions/", + "description": "R Apply Family" + }, + { + "url": "https://adv-r.hadley.nz/functionals.html", + "description": "Advanced R: Functionals" + } +] diff --git a/config.json b/config.json index b51aafcc..271e0e8c 100644 --- a/config.json +++ b/config.json @@ -586,6 +586,11 @@ "uuid": "2751b6f2-7d71-4397-b063-9bf927a57756", "slug": "booleans", "name": "Booleans" + }, + { + "uuid": "137411b7-5af8-4a78-9225-5b85353ba81e", + "slug": "lapply-sapply", + "name": "Lapply Sapply" } ], "key_features": [