Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

captains-log concept exercise #273

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
1 change: 1 addition & 0 deletions exercises/concept/captains-log/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Hints
42 changes: 42 additions & 0 deletions exercises/concept/captains-log/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -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
```
38 changes: 38 additions & 0 deletions exercises/concept/captains-log/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -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
```

11 changes: 11 additions & 0 deletions exercises/concept/captains-log/.meta/config.json
Original file line number Diff line number Diff line change
@@ -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."
}
29 changes: 29 additions & 0 deletions exercises/concept/captains-log/.meta/design.md
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions exercises/concept/captains-log/.meta/exemplar.R
Original file line number Diff line number Diff line change
@@ -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)
}
8 changes: 8 additions & 0 deletions exercises/concept/captains-log/captains-log.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
random_planet <- function() {
}

random_ship_registry_number <- function() {
}

random_stardate <- function() {
}
53 changes: 53 additions & 0 deletions exercises/concept/captains-log/test_captains-log.R
Original file line number Diff line number Diff line change
@@ -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)
})