Skip to content

Commit

Permalink
Add solutions 20240827
Browse files Browse the repository at this point in the history
  • Loading branch information
damianooldoni committed Aug 29, 2024
1 parent fd05510 commit 8751c0e
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 1 deletion.
2 changes: 1 addition & 1 deletion docs/sessions/20240827_the_art_of_debugging.html
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@

# Resources

- Commented challenges solutions will be available soon.
- [Challenges solutions](https://github.com/inbo/coding-club/blob/main/src/20240827/20240827_challenges_solutions.R) are available. You can opt to download them by using `inborutils::setup_codingclub_session("20240827")`.
- The edited [video recording](https://vimeo.com/1004025584) is available on our [vimeo channel]([vimeo channel](https://vimeo.com/channels/1659345).
- [webinar](https://youtu.be/-yy_3htRHdU) and related [tutorial](https://htmlpreview.github.io/?https://github.com/berkeley-scf/tutorial-R-debugging/blob/master/R-debugging.html) from Chris Paciorek, Department of Statistics, UC Berkley
- [Debugging in RStudio tutorial](https://support.rstudio.com/hc/en-us/articles/205612627-Debugging-with-RStudio) written by posit (company behind RStudio) itself
Expand Down
1 change: 1 addition & 0 deletions file_paths/file_path_df.csv
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,4 @@ src/20240528/20240528_challenges_solutions.R,20240528_challenges_solutions.R,src
src/20240627/20240627_challenges.R,20240627_challenges.R,src,20240627
src/20240627/20240627_challenges_solutions.R,20240627_challenges_solutions.R,src,20240627
src/20240827/20240827_challenges.R,20240827_challenges.R,src,20240827
src/20240827/20240827_challenges_solutions.R,20240827_challenges_solutions.R,src,20240827
305 changes: 305 additions & 0 deletions src/20240827/20240827_challenges_solutions.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
library(tidyverse)

## CHALLENGE 1

# Use debug techniques to find the bug in the function evenOdd()

#' The error is in the "for" definition. The right for declaration is:
#'
#' for (i in seq_len(nchar(char_n)))
#'
#' Then, extract the digit with:
#' digit <- as.integer(substr(char_n, i, i))
#'
#' Here below the entire function without bug.

#' Function evenOdd() takes an integer n and returns a list containing two integers
#' that respectively indicate how many even and odd digits occur in n
#'
#' @examples
#' evenOdd(398473234) # should return (4, 5)
#' evenOdd(459) # should return (1, 2)
evenOdd <- function(n) {
char_n <- as.character(n)
counter_even <- 0
counter_odd <- 0
for (i in seq_len(nchar(char_n))) {
digit <- as.integer(substr(char_n, i, i))
if (digit %% 2 == 0) {
counter_even <- counter_even + 1
} else {
counter_odd <- counter_odd + 1
}
}
return(list(n_even = counter_even,
n_odd = counter_odd))
}


## CHALLENGE 2

# Using debug techniques find what is going wrong in functions step() and steps()

# In this case, `step()` works, but the result is wrong. A very dangerous
# situation. We need to add the conversion from list to character and then from
# character to integer before `return()`:
#
# n_odd_even_total <- paste0(n_odd_even_total, collapse = "")
# n_odd_even_total <- as.integer(n_odd_even_total)
#
#
# The bug in `steps()` is probably the easiest: `n_steps` is used before being
# created. `n_steps` must be initialised before the loop:
#
# n_steps <- 0
#
# Here below the functions without bugs.

#' Function step() takes a positive integer, `n`, count up the number of even
#' and odd digits, and the total number of digits and return it as a number.
#'
#' @examples
#' step(398473234)
#' 459
#' step(459)
#' 123
step <- function(n) {
n_odd_even <- evenOdd(n)
total_digits <- nchar(n)
n_odd_even_total <- n_odd_even
n_odd_even_total$n_total <- total_digits
n_odd_even_total <- paste0(n_odd_even_total, collapse = "")
n_odd_even_total <- as.integer(n_odd_even_total)
return(n_odd_even_total)
}


#' Function `steps` takes an integer and return how
#' many steps you need before the number 123 is reached.
#'
#' @examples
#' steps(398473234)
#' 2
#' steps(1)
#' 5
#' steps(2)
#' 2
steps <- function(n) {
n_steps <- 0
while (n != 123) {
n <- step(n)
n_steps <- n_steps + 1
}
return(n_steps)
}


## INTERMEZZO

#' Customized `min()` function for numeric vectors to avoid warning while
#' calculating minimum of empty vectors
#'
#' `custom_min()` should fail if a not numeric vector is passed
#'
#' @examples
#' custom_min(c(8, 4, 2, 5)) # this works
#' custom_min(c("e", "r", "a")) # this should return an error
custom_min <- function(x) {
if (length(x)>0) min(x) else NA
}


# Improved version with assertion

#' Customized `min()` function for numeric vectors to avoid warning while
#' calculating minimum of empty vectors
#'
#' `custom_min()` should fail if a not numeric vector is passed
#'
#' @examples
#' custom_min(c(8, 4, 2, 5)) # this works
#' custom_min(c("e", "r", "a")) # this should return an error
custom_min <- function(x) {
# check x argument (assertion)
assertthat::assert_that(
is.numeric(x),
msg = "Invalid type of input: only numeric vectors allowed."
)
# function core follows
if (length(x)>0) min(x) else NA
}

## CHALLENGE 3

# Based on defensive programming principle, add some checks to `get_migrations()`
# function defined here below. You can test the function using the examples
# defined under the function. Notice also how documenting your own functions is
# more than a nice-to-have.

# The error while running the 2n example is due to define `dist_thresh` as a
# character: dist_thresh <- "5000"
#
# Set it to integer:
# dist_thresh <- 5000
#
# The error in 3rd example is due to two missing columns in input data.frame:
# `totaldistance_m` and `arrival`. There are two columns called
# `total_distance_m` and `arrive` instead. Rename them before applying the
# function:
#
# eel_amber <- eel_amber %>%
# dplyr::rename(totaldistance_m = total_distance_m,
# arrival = arrive)
#
# Here below the entire code with the corrections and the assertions to
# immediately detect the anomalies in the input arguments.
# Try to run the improved function here below using the original (wrong) inputs
# to see the assertions in action.


#' Core function to find migration periods of a tagged eel.
#'
#' @examples
#' library(readr)
#' library(dplyr)
#' library(lubridate)
#' library(ggplot2)
#'
#' ## Example 1
#'
#' # read eel tagging data
#' eel_emma <- read_csv("./data/20240827/20240827_eel_Emma.txt", na = "")
#' # define thresholds
#' distance_threshold <- 5000 # threshold in meter
#' migration_speed_threshold <- 0.1 # speed threshold in m/s
#' # run migration detection
#' eel_emma_migration_info <- get_migrations(
#' eel_emma,
#' dist_threshold = distance_threshold,
#' speed_threshold = migration_speed_threshold
#' )
#' # plot result (this chunk is not included in following examples)
#' ggplot(eel_emma_migration_info, aes(x = arrival, y = totaldistance_m)) +
#' geom_point(aes(color = downstream_migration)) +
#' geom_line() +
#' scale_y_reverse()
#'
#' ## Example 2
#' eel_rhea <- read_csv("./data/20240827/20240827_eel_Rhea.txt", na = "")
#' # define thresholds
#' dist_thresh <- 5000 # threshold in meter
#' speed_thresh <- 0.1 # speed threshold in m/s
#' # run migration detection
#' get_migrations(eel_rhea,
#' dist_threshold = dist_thresh,
#' speed_threshold = speed_thresh)
#'
#' ## Example 3
#' eel_amber <- read_csv("./data/20240827/20240827_eel_Amber.txt", na = "")
#' eel_amber <- eel_amber %>%
#' dplyr::rename(totaldistance_m = total_distance_m,
#' arrival = arrive)
#' # Define thresholds
#' dist_thresh <- 5000 # threshold in meter
#' speed_thresh <- 0.1 # speed threshold in m/s
#' # Run migration detection
#' get_migrations(eel_amber,
#' dist_threshold = dist_thresh,
#' speed_threshold = speed_thresh)
get_migrations <- function(df,
dist_threshold,
speed_threshold) {

## check inputs
# df is a data.frame. Notice that msg argument with your own error message is
# optional
assertthat::assert_that(is.data.frame(df))
# dist_threshold is a number
assertthat::assert_that(is.numeric(dist_threshold))
# speed_threshold is a number
assertthat::assert_that(is.numeric(speed_threshold))
# we make use of some columns in df. So, they need to be present in df
assertthat::assert_that("totaldistance_m" %in% names(df),
msg = "Column `totaldistance_m` not found in df."
)
assertthat::assert_that("arrival" %in% names(df),
msg = "Column `arrival` not found in df."
)
assertthat::assert_that("departure" %in% names(df),
msg = "Column `departure` not found in df."
)

## Core function follows
df %>%
mutate(dist_threshold = totaldistance_m + dist_threshold) %>%
rowwise() %>%
mutate(first_dist_to_use = custom_min(
df$totaldistance_m[df$totaldistance_m >= dist_threshold])) %>%
mutate(row_first_dist_to_use = if_else(
!is.na(first_dist_to_use),
which(df$totaldistance_m == first_dist_to_use)[1],
NA)) %>%
ungroup() %>%
mutate(time_first_dist_to_use = if_else(!is.na(row_first_dist_to_use),
df$arrival[row_first_dist_to_use],
NA)) %>%
mutate(delta_totdist = first_dist_to_use - totaldistance_m) %>%
mutate(delta_t = as.numeric(as.duration(time_first_dist_to_use - departure))) %>%
mutate(migration_speed = (delta_totdist / delta_t)) %>%
mutate(downstream_migration = migration_speed >= speed_threshold)
}

## BONUS CHALLENGE

# Notice the order of the assertions to fail as fastest as possible and
# precisely detect the bug


#' Function evenOdd_bonus() improves `evenOdd()` by adding assertions to fail as
#' fast as possible.
#'
#' @examples
#' evenOdd_bonus(398473234) # Returns (4, 5)
#' evenOdd_bonus(459) # Returns (1, 2)
#' # Assertion catches a too large input
#' evenOdd_bonus(3984732344209249404409505.4)
#' # Assertion catches a decimal input
#' evenOdd_bonus(1.5)
#' # Assertion catches negative number
#' evenOdd_bonus(-2349)
#' # Assertion catches a vector with more than one number
#' evenOdd_bonus(c(34867, 239))
evenOdd_bonus <- function(n) {
assertthat::assert_that(length(n) == 1,
msg = "n must be of length 1."
)
assertthat::assert_that(n >= 0,
msg = "n must be a non-negative number."
)
assertthat::assert_that(
n <= .Machine$integer.max,
msg = paste0(
"n exceeds the greatest integer your machine can deal with, which is ",
.Machine$integer.max,
"."
)
)
assertthat::assert_that(
as.integer(n) == n,
msg = "n must be an integer"
)

char_n <- as.character(n)
counter_even <- 0
counter_odd <- 0
for (i in seq_len(nchar(char_n))) {
digit <- as.integer(substr(char_n, i, i))
if (digit %% 2 == 0) {
counter_even <- counter_even + 1
} else {
counter_odd <- counter_odd + 1
}
}
return(list(n_even = counter_even,
n_odd = counter_odd))
}

0 comments on commit 8751c0e

Please sign in to comment.