From 4c41dbf5a93377adbef598e14eef6bf4089273fb Mon Sep 17 00:00:00 2001 From: colinleach Date: Mon, 10 Jul 2023 16:51:11 -0700 Subject: [PATCH] captains-log concept exercise --- config.json | 12 +++++ exercises/concept/captains-log/.docs/hints.md | 1 + .../captains-log/.docs/instructions.md | 42 +++++++++++++++ .../captains-log/.docs/introduction.md | 38 +++++++++++++ .../concept/captains-log/.meta/config.json | 11 ++++ .../concept/captains-log/.meta/design.md | 29 ++++++++++ .../concept/captains-log/.meta/exemplar.R | 12 +++++ exercises/concept/captains-log/captains-log.R | 8 +++ .../concept/captains-log/test_captains-log.R | 53 +++++++++++++++++++ 9 files changed, 206 insertions(+) create mode 100644 exercises/concept/captains-log/.docs/hints.md create mode 100644 exercises/concept/captains-log/.docs/instructions.md create mode 100644 exercises/concept/captains-log/.docs/introduction.md create mode 100644 exercises/concept/captains-log/.meta/config.json create mode 100644 exercises/concept/captains-log/.meta/design.md create mode 100644 exercises/concept/captains-log/.meta/exemplar.R create mode 100644 exercises/concept/captains-log/captains-log.R create mode 100644 exercises/concept/captains-log/test_captains-log.R diff --git a/config.json b/config.json index 01ec79ac..a0c00812 100644 --- a/config.json +++ b/config.json @@ -65,6 +65,18 @@ "booleans" ], "status": "wip" + }, + { + "slug": "captains-log", + "name": "Captains Log", + "uuid": "bf0295ab-d4da-4cfa-8597-7689c780b5c9", + "concepts": [ + "randomness" + ], + "prerequisites": [ + "vectors" + ], + "status": "wip" } ], "practice": [ diff --git a/exercises/concept/captains-log/.docs/hints.md b/exercises/concept/captains-log/.docs/hints.md new file mode 100644 index 00000000..b5296c36 --- /dev/null +++ b/exercises/concept/captains-log/.docs/hints.md @@ -0,0 +1 @@ +# Hints diff --git a/exercises/concept/captains-log/.docs/instructions.md b/exercises/concept/captains-log/.docs/instructions.md new file mode 100644 index 00000000..ec225326 --- /dev/null +++ b/exercises/concept/captains-log/.docs/instructions.md @@ -0,0 +1,42 @@ +# Instructions + +Mary is a big fan of the TV series _Star Trek: The Next Generation_. She often plays pen-and-paper role playing games, where she and her friends pretend to be the crew of the _Starship Enterprise_. Mary's character is Captain Picard, which means she has to keep the captain's log. She loves the creative part of the game, but doesn't like to generate random data on the spot. + +Help Mary by creating random generators for data commonly appearing in the captain's log. + +## 1. Generate a random planet + +The _Starship Enterprise_ encounters many planets in its travels. Planets in the Star Trek universe are split into categories based on their properties. For example, Earth is a class M planet. All possible planetary classes are: D, H, J, K, L, M, N, R, T, and Y. + +Implement the `random_planet` function. It should return one of the planetary classes at random. + +```R +random_planet_class() +# => "K" +``` + +## 2. Generate a random starship registry number + +Enterprise (registry number NCC-1701) is not the only starship flying around! When it rendezvous with another starship, Mary needs to log the registry number of that starship. + +Registry numbers start with the prefix "NCC-" and then use a number from 1000 to 9999 (inclusive). + +Implement the `random_ship_registry_number` function that returns a random starship registry number. + +```R +random_ship_registry_number() +# => "NCC-1947" +``` + +## 3. Generate a random stardate + +What's the use of a log if it doesn't include dates? + +A stardate is a floating point number. The adventures of the _Starship Enterprise_ from the first season of _The Next Generation_ take place between the stardates 41000.0 and 42000.0. The "4" stands for the 24th century, the "1" for the first season. + +Implement the function `random_stardate` that returns a floating point number between 41000.0 (inclusive) and 42000.0 (exclusive). + +```R +random_stardate() +# => 41458.15721310934 +``` diff --git a/exercises/concept/captains-log/.docs/introduction.md b/exercises/concept/captains-log/.docs/introduction.md new file mode 100644 index 00000000..ad8af00e --- /dev/null +++ b/exercises/concept/captains-log/.docs/introduction.md @@ -0,0 +1,38 @@ +# Introduction + +There are many options for generating random values. +These are a few common ones. + +## Discrete values with `sample` + +To pick from a limited number of discrete values, `sample()` can be used with or without replacement (i.e. is it permitted to get repeated values?). +This is useful for integers, strings, etc. + +```R +> sample(1:10, 5) # no repeats +[1] 8 3 4 2 6 + +> sample(1:10, 5, replace = TRUE) # repeats are OK +[1] 10 10 7 10 9 + +> languages <- c("Fortran", "R", "Python", "Julia") +> sample(languages, 1) +[1] "Python" + +> sample(c(TRUE, FALSE), 5, replace = TRUE) # coin flips +[1] FALSE TRUE FALSE TRUE TRUE +``` + +## Floating-point numbers + +For a random-uniform distribution use `runif()`. +Both `min` and `max` can be specified but default to 0 and 1. + +```R +> runif(5) +[1] 0.3038506 0.3527959 0.3319309 0.4846354 0.4386279 + +> runif(5, max = 100) +[1] 79.70762 51.62232 52.85281 71.08571 63.94380 +``` + diff --git a/exercises/concept/captains-log/.meta/config.json b/exercises/concept/captains-log/.meta/config.json new file mode 100644 index 00000000..c86be019 --- /dev/null +++ b/exercises/concept/captains-log/.meta/config.json @@ -0,0 +1,11 @@ +{ + "authors": ["colinleach"], + "contributors": [], + "files": { + "solution": ["captains-log.R"], + "test": ["test_captains-log.R"], + "exemplar": [".meta/exemplar.R"] + }, + "forked_from": ["elixir/captains-log"], + "blurb": "Help Mary with her role playing game by generating suitable random data." +} diff --git a/exercises/concept/captains-log/.meta/design.md b/exercises/concept/captains-log/.meta/design.md new file mode 100644 index 00000000..027bb649 --- /dev/null +++ b/exercises/concept/captains-log/.meta/design.md @@ -0,0 +1,29 @@ +# Design + +## Goal + +The goal of this exercise is to teach the student about using random values in R. + +## Learning objectives + +- Understand `sample()`, with and without replacement. +- Understand `runif()` to get uniformly-distributed floating-point numbers. + +## Out of scope + +- Details of all the options used in statistical modeling. R has *many* of these. + +## Concepts + +This exercise unlocks no other concepts. + +## Prerequisites + +- `strings` + +## Test runner considerations + +In `test_captains-log.R`, `random_planet` needs enough runs to be pretty sure all possible values will be chosen. +- With 1000 (current setting), the chance of a false fail is 1.7e-46 +- With 500, this is 1.3e-23 +- We'll need to see timings for the test runner in real use diff --git a/exercises/concept/captains-log/.meta/exemplar.R b/exercises/concept/captains-log/.meta/exemplar.R new file mode 100644 index 00000000..257c78c8 --- /dev/null +++ b/exercises/concept/captains-log/.meta/exemplar.R @@ -0,0 +1,12 @@ +random_planet <- function() { + planetary_classes <- c("D", "H", "J", "K", "L", "M", "N", "R", "T", "Y") + sample(planetary_classes, 1) +} + +random_ship_registry_number <- function() { + sprintf("NCC-%d", sample(1000:9999, 1)) +} + +random_stardate <- function() { + runif(1, min = 41000, max = 42000) +} diff --git a/exercises/concept/captains-log/captains-log.R b/exercises/concept/captains-log/captains-log.R new file mode 100644 index 00000000..dbcea2c1 --- /dev/null +++ b/exercises/concept/captains-log/captains-log.R @@ -0,0 +1,8 @@ +random_planet <- function() { +} + +random_ship_registry_number <- function() { +} + +random_stardate <- function() { +} diff --git a/exercises/concept/captains-log/test_captains-log.R b/exercises/concept/captains-log/test_captains-log.R new file mode 100644 index 00000000..f12bf39c --- /dev/null +++ b/exercises/concept/captains-log/test_captains-log.R @@ -0,0 +1,53 @@ +source("./captains-log.R") +library(testthat) + +# 1) random_planet + +repeats <- 1000 +random_planets <- vector(length = repeats) +for (i in 1:repeats) random_planets[i] <- random_planet() +planetary_classes <- c("D", "H", "J", "K", "L", "M", "N", "R", "T", "Y") + +test_that("all generated planets are valid", { + expect_equal(all(random_planets %in% planetary_classes), TRUE) +}) + +test_that("all valid planets are generated", { + expect_equal(all(planetary_classes %in% random_planets), TRUE) +}) + +# 2) random_ship_registry_number + +reg_repeats <- 100 +random_registry <- vector(length = reg_repeats) +for (i in 1:reg_repeats) random_registry[i] <- random_ship_registry_number() + +test_that("all registry numbers are the correct length", { + expect_equal(all(nchar(random_registry) == 8), TRUE) +}) + +test_that("all registry numbers have the correct prefix", { + expect_equal(all(substr(random_registry, 1, 4) == "NCC-"), TRUE) +}) + +test_that("all registry numbers have the correct numeric range", { + nums <- strtoi(substr(random_registry, 5, 8)) + expect_equal(all(nums >= 1000 & nums <= 9999), TRUE) +}) + +# 3) random_stardate + +stardate_repeats <- 100 +random_stardates <- vector(length = stardate_repeats) +for (i in 1:stardate_repeats) random_stardates[i] <- random_stardate() + +test_that("all stardates have the correct numeric range", { + expect_equal(all(random_stardates >= 41000.0 & + random_stardates <= 42000.0), TRUE) +}) + +# the next one could surely be improved +test_that("registry numbers are not all similar", { + expect_equal(any(random_stardates < 41500.0) & + any(random_stardates > 41500.0), TRUE) +})