diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba35816..e24e81c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,6 +15,8 @@ This is enforced by the "Learning checks" workflow. bin/add-practice-exercise ``` +- An exercise should include a `.meta/tests.toml` file if, and only if, the exercise has a `canonical-data.json` in [problem-specifications](https://github.com/exercism/problem-specifications/tree/main/exercises). + #### **Do you want to report a bug?** - **Ensure the bug was not already reported** by searching the [forum](https://forum.exercism.org/c/programming/factor). diff --git a/bin/extract-known-words b/bin/extract-known-words index bee86ec..c5a799e 100755 --- a/bin/extract-known-words +++ b/bin/extract-known-words @@ -25,7 +25,7 @@ x y z w m k i j p q r s u v n seq seq1 seq2 quot quot1 quot2 quot3 quot: quot:: assoc key value old elt elt1 elt2 bool string str old-x new-x 3x 2x n+1 n+10% n+1% length+kerf length+10% -days new-days new-days-or-f symbol direction direction' +new-days new-days-or-f symbol direction direction' amount rate years seconds planet period arr sortedstr count/f p' dx dy sx sy points transformed price prices item items base-speed @@ -42,7 +42,7 @@ recipe-step depth >>slot slot>> change-slot test n*x n*y n+x n+y newseq newkey newvalue sortedseq headseq tailseq padded inputs outputs difference label -hour minute hours minutes word words +word words phrase\' """.split()) diff --git a/concepts/calendar/.meta/config.json b/concepts/calendar/.meta/config.json new file mode 100644 index 0000000..a4236fe --- /dev/null +++ b/concepts/calendar/.meta/config.json @@ -0,0 +1,6 @@ +{ + "authors": [ + "keiravillekode" + ], + "blurb": "The calendar vocabulary: and >date< build and split timestamps, day-of-week, leap-year?, and days-in-month query them, and durations like days shift them with time+." +} diff --git a/concepts/calendar/about.md b/concepts/calendar/about.md new file mode 100644 index 0000000..1a478f2 --- /dev/null +++ b/concepts/calendar/about.md @@ -0,0 +1,60 @@ +# About + +Factor's [`calendar`][calendar] vocabulary is built around two tuples: + +- `timestamp` — a point in time (`year`, `month`, `day`, `hour`, + `minute`, `second`, and a GMT offset). +- `duration` — a span of time (the same fields, used as amounts). + +## Timestamps + +`` builds a midnight timestamp; `>date<` splits one apart: + +``` + ( year month day -- timestamp ) +>date< ( timestamp -- year month day ) +``` + +```factor +USING: calendar ; + +2024 7 14 >date< . ! => 14 (2024 and 7 below it) +``` + +Common queries: + +```factor +USING: calendar calendar.english sequences ; + +2024 1 8 day-of-week . ! => 1 (0 = Sunday .. 6 = Saturday) +2024 1 8 day-of-week day-names nth . ! => "Monday" +2024 leap-year? . ! => t +2024 2 1 days-in-month . ! => 29 +``` + +`day-names` (and `month-names`) live in +[`calendar.english`][english]. + +## Durations and arithmetic + +Words like `days`, `weeks`, `hours`, and `minutes` build a `duration`, +and `time+` / `time-` shift a timestamp by one: + +``` +days ( x -- duration ) +time+ ( timestamp duration -- timestamp ) +``` + +```factor +2024 2 28 1 days time+ >date< . ! => 29 (2024-02-29) +``` + +Arithmetic carries across month and year boundaries, and two +timestamps naming the same instant compare equal with `=`. + +Beyond these, `calendar` offers times of day (`set-time`, `+hour`), +time-zone conversions (`>gmt`, `>local-time`), the current time +(`now`), and — via `calendar.format` — parsing and formatting. + +[calendar]: https://docs.factorcode.org/content/vocab-calendar.html +[english]: https://docs.factorcode.org/content/vocab-calendar.english.html diff --git a/concepts/calendar/introduction.md b/concepts/calendar/introduction.md new file mode 100644 index 0000000..d6a4d55 --- /dev/null +++ b/concepts/calendar/introduction.md @@ -0,0 +1,29 @@ +# Introduction + +Factor's [`calendar`][calendar] vocabulary represents a point in time as +a `timestamp` and a span of time as a `duration`. + +```factor +USING: calendar ; + +2024 7 14 . ! a timestamp at midnight on 2024-07-14 +2024 7 14 >date< . ! => 14 (2024 and 7 below it) +``` + +`day-of-week` returns `0` for Sunday through `6` for Saturday; +`leap-year?` and `days-in-month` answer the usual calendar questions: + +```factor +2024 1 8 day-of-week . ! => 1 +2024 leap-year? . ! => t +2024 2 1 days-in-month . ! => 29 +``` + +Durations such as `days` and `weeks` shift a timestamp with `time+`: + +```factor +2024 2 28 1 days time+ >date< . +! => 29 (2024-02-29) +``` + +[calendar]: https://docs.factorcode.org/content/vocab-calendar.html diff --git a/concepts/calendar/links.json b/concepts/calendar/links.json new file mode 100644 index 0000000..26ae122 --- /dev/null +++ b/concepts/calendar/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://docs.factorcode.org/content/vocab-calendar.html", + "description": "calendar vocabulary reference" + }, + { + "url": "https://docs.factorcode.org/content/vocab-calendar.english.html", + "description": "calendar.english — day and month names" + } +] diff --git a/concepts/randomness/.meta/config.json b/concepts/randomness/.meta/config.json new file mode 100644 index 0000000..1645acd --- /dev/null +++ b/concepts/randomness/.meta/config.json @@ -0,0 +1,6 @@ +{ + "authors": [ + "keiravillekode" + ], + "blurb": "The random vocabulary: random picks values and elements, randomize and sample reorder sequences, and with-random plus make results reproducible." +} diff --git a/concepts/randomness/about.md b/concepts/randomness/about.md new file mode 100644 index 0000000..496fddb --- /dev/null +++ b/concepts/randomness/about.md @@ -0,0 +1,58 @@ +# About + +Factor concentrates randomness in the [`random`][random] vocabulary +around a single generic word: + +``` +random ( obj -- elt ) +``` + +It dispatches on its argument: + +- an **integer** `n` yields a random integer in `[0, n)`; +- a **sequence** yields a random element; +- assocs, hash-sets, and `uniform-distribution` tuples are also + supported. + +Two sequence helpers build on it: + +```factor +USING: random ; + +{ 1 2 3 4 5 } randomize . ! shuffle in place (Fisher–Yates) +{ 1 2 3 4 5 } 3 sample . ! 3 distinct elements; error if too many +``` + +`randomize` mutates and returns the sequence, so `clone` first if you +need to keep the original order. + +## Where the randomness comes from + +`random` reads the generator held in the `random-generator` dynamic +variable. By default that's a Mersenne Twister seeded from the system +clock, so every process differs. You can install a different generator +for the duration of a quotation: + +``` + ( seed -- rnd ) +with-random ( rnd quot -- ) +``` + +```factor +USING: random random.mersenne-twister ; + +42 [ { 1 2 3 } randomize ] with-random . +! => the same ordering on every run +``` + +Because `with-random` simply binds `random-generator` for the dynamic +extent of the quotation, a word that wraps it should be marked +`inline` so the quotation can leave its result on the stack for the +caller. + +Beyond these, `random` offers `random-bits`, `random-unit`, +`uniform-random`, and `normal-random` for other distributions, plus +`system-random-generator` and `secure-random-generator` for +non-reproducible and cryptographic needs. + +[random]: https://docs.factorcode.org/content/vocab-random.html diff --git a/concepts/randomness/introduction.md b/concepts/randomness/introduction.md new file mode 100644 index 0000000..09d6043 --- /dev/null +++ b/concepts/randomness/introduction.md @@ -0,0 +1,32 @@ +# Introduction + +Factor's [`random`][random] vocabulary provides one generic word, +`random`, that adapts to its argument, plus helpers for shuffling and +sampling sequences. + +```factor +USING: random ; + +6 random . ! a value from 0..5 +{ "rock" "paper" "scissors" } random . ! a random element +{ 1 2 3 4 5 } randomize . ! the same cards, reordered +{ 1 2 3 4 5 } 2 sample . ! two distinct elements +``` + +`random` of an integer `n` returns a value in `[0, n)`; of a sequence +it returns a random element. `randomize` shuffles in place, and +`sample` draws distinct elements. + +By default these draw from a global generator seeded from the system +clock. To make outcomes reproducible, install a seeded generator with +`` (from [`random.mersenne-twister`][mt]) and +`with-random`: + +```factor +USING: random random.mersenne-twister ; + +42 [ 6 random ] with-random . ! same value every run +``` + +[random]: https://docs.factorcode.org/content/vocab-random.html +[mt]: https://docs.factorcode.org/content/vocab-random.mersenne-twister.html diff --git a/concepts/randomness/links.json b/concepts/randomness/links.json new file mode 100644 index 0000000..559cdae --- /dev/null +++ b/concepts/randomness/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://docs.factorcode.org/content/vocab-random.html", + "description": "random vocabulary reference" + }, + { + "url": "https://docs.factorcode.org/content/vocab-random.mersenne-twister.html", + "description": "random.mersenne-twister — seeded, reproducible generators" + } +] diff --git a/config.json b/config.json index 00f8385..feba064 100644 --- a/config.json +++ b/config.json @@ -424,6 +424,34 @@ ], "status": "beta" }, + { + "slug": "boardwalk-games", + "name": "Boardwalk Games", + "uuid": "2ab5123d-7837-4dad-a7a3-3bca1bfc15f2", + "concepts": [ + "randomness" + ], + "prerequisites": [ + "numbers", + "sequences", + "quotations", + "combinators" + ], + "status": "beta" + }, + { + "slug": "ferry-schedule", + "name": "Ferry Schedule", + "uuid": "4e03dc97-8707-41d5-8e68-ca269d5f17be", + "concepts": [ + "calendar" + ], + "prerequisites": [ + "numbers", + "sequences" + ], + "status": "beta" + }, { "slug": "bering-bearings", "name": "Bering Bearings", @@ -983,6 +1011,18 @@ ], "difficulty": 3 }, + { + "slug": "robot-name", + "name": "Robot Name", + "uuid": "1f3cded8-c357-448d-9640-63e8e6ea8efe", + "practices": [], + "prerequisites": [ + "dynamic-variables", + "randomness", + "hash-sets" + ], + "difficulty": 3 + }, { "slug": "rotational-cipher", "name": "Rotational Cipher", @@ -1094,6 +1134,19 @@ ], "difficulty": 4 }, + { + "slug": "dnd-character", + "name": "D&D Character", + "uuid": "7229dc0f-df0c-4c3d-9aec-810ab2474d6f", + "practices": [], + "prerequisites": [ + "randomness", + "tuples", + "locals", + "higher-order-sequences" + ], + "difficulty": 4 + }, { "slug": "flatten-array", "name": "Flatten Array", @@ -1158,6 +1211,19 @@ ], "difficulty": 4 }, + { + "slug": "meetup", + "name": "Meetup", + "uuid": "faf43296-da2b-4953-ab84-1381e52c600b", + "practices": [], + "prerequisites": [ + "calendar", + "case", + "locals", + "arrays" + ], + "difficulty": 4 + }, { "slug": "pascals-triangle", "name": "Pascal's Triangle", @@ -1563,6 +1629,20 @@ ], "difficulty": 6 }, + { + "slug": "food-chain", + "name": "Food Chain", + "uuid": "7cd07547-7e56-416f-ad19-b2086ff835c1", + "practices": [], + "prerequisites": [ + "macros", + "locals", + "higher-order-sequences", + "arrays", + "tabulation" + ], + "difficulty": 6 + }, { "slug": "ocr-numbers", "name": "OCR Numbers", @@ -1947,6 +2027,16 @@ "slug": "dynamic-variables", "name": "Dynamic Variables" }, + { + "uuid": "38b7be75-62ec-4a24-8bd0-d2714c4c8a3c", + "slug": "randomness", + "name": "Randomness" + }, + { + "uuid": "cd393574-269e-494e-b515-bb4f0fb677ea", + "slug": "calendar", + "name": "Calendar" + }, { "uuid": "e9c2a584-0c03-467f-9781-38754f06dd1a", "slug": "generics", diff --git a/exercises/concept/boardwalk-games/.docs/hints.md b/exercises/concept/boardwalk-games/.docs/hints.md new file mode 100644 index 0000000..426c632 --- /dev/null +++ b/exercises/concept/boardwalk-games/.docs/hints.md @@ -0,0 +1,38 @@ +# Hints + +## General + +- Everything you need is in the [`random`][random] vocabulary, except + ``, which lives in + [`random.mersenne-twister`][mt]. + +## 1. Roll a die + +- `random` of an integer `n` gives `0..n-1`. Add `1` to shift the range + to `1..n`. + +## 2. Grab a prize + +- `random` applied to a sequence already returns a random element — no + extra work needed. + +## 3. Shuffle the deck + +- `randomize` shuffles a sequence and returns it. + +## 4. Deal a hand + +- `sample` takes the sequence and the count and returns that many + distinct elements. + +## 5. Replay from a seed + +- Turn the seed into a generator with ``, then run the + quotation under `with-random`. +- `[ ] dip` converts the seed while leaving the + quotation on top of the stack for `with-random`. +- Mark the word `inline` so the quotation can return its result to the + caller. + +[random]: https://docs.factorcode.org/content/vocab-random.html +[mt]: https://docs.factorcode.org/content/vocab-random.mersenne-twister.html diff --git a/exercises/concept/boardwalk-games/.docs/instructions.md b/exercises/concept/boardwalk-games/.docs/instructions.md new file mode 100644 index 0000000..72770fc --- /dev/null +++ b/exercises/concept/boardwalk-games/.docs/instructions.md @@ -0,0 +1,63 @@ +# Instructions + +You run the games of chance at the seaside boardwalk arcade. Punters +roll dice, the claw machine grabs a prize, and the card table needs its +deck shuffled and hands dealt — all powered by randomness. At the end of +the night you also want a "replay" feature so a disputed game can be run +again from the same seed. + +## 1. Roll a die + +Define `roll-die` to take the number of sides and return a roll in the +range `1` to `sides` (inclusive). + +```factor +6 roll-die . +! => 4 (some value from 1..6) +``` + +## 2. Grab a prize + +Define `pick-prize` to take a sequence of prizes and return one of them +at random. + +```factor +{ "teddy" "goldfish" "keyring" } pick-prize . +! => "goldfish" +``` + +## 3. Shuffle the deck + +Define `shuffle-deck` to take a deck (a sequence) and return it with the cards +in a random order. + +```factor +{ 1 2 3 4 5 } shuffle-deck . +! => { 3 1 5 2 4 } +``` + +## 4. Deal a hand + +Define `deal-hand` to take a deck and a count `n`, and return `n` *distinct* +cards from the deck. + +```factor +{ 10 20 30 40 50 } 3 deal-hand . +! => { 40 10 30 } +``` + +## 5. Replay from a seed + +Define `play-seeded` to take a seed and a quotation, and run the +quotation with a generator seeded from that integer, so the same seed +always reproduces the same outcome. The quotation's result should be +returned to the caller. + +```factor +42 [ 6 roll-die ] play-seeded . +! => the same value every time for seed 42 + +! Same seed, same game: +99 [ 5 deal-hand ] play-seeded 99 [ 5 deal-hand ] play-seeded = . +! => t +``` diff --git a/exercises/concept/boardwalk-games/.docs/introduction.md b/exercises/concept/boardwalk-games/.docs/introduction.md new file mode 100644 index 0000000..b227143 --- /dev/null +++ b/exercises/concept/boardwalk-games/.docs/introduction.md @@ -0,0 +1,108 @@ +# Introduction + +Games of chance need a source of randomness. Factor's [`random`][random] +vocabulary provides one generic word, `random`, that adapts to whatever +you hand it, plus helpers for shuffling and sampling, and a way to make +randomness *reproducible* for tests. + +## `random` — one element + +`random` (in [`random`][random]) is generic over its argument: + +``` +random ( obj -- elt ) +``` + +- Given an **integer** `n`, it returns a random integer in `[0, n)`: + +```factor +USING: random ; + +6 random . ! 4 (a value from 0 to 5) +``` + +- Given a **sequence**, it returns a random element: + +```factor +{ "rock" "paper" "scissors" } random . ! "paper" +``` + +To roll a normal die you shift the range up by one: `random` of `6` +gives 0 to 5, so `6 random 1 +` gives 1 to 6. + +## Drawing several values + +To collect a fixed number of random draws, repeat a quotation with +`replicate` (in [`sequences`][sequences]): + +``` +replicate ( n quot -- seq ) +``` + +```factor +USING: random sequences ; + +3 [ 6 random 1 + ] replicate . ! { 4 1 6 } (three dice) +``` + +## `randomize` — shuffle in place + +`randomize` (in [`random`][random]) returns the sequence with its +elements in a random order: + +``` +randomize ( seq -- randomized ) +``` + +```factor +{ 1 2 3 4 5 } randomize . ! { 3 1 5 2 4 } +``` + +It shuffles **in place** and returns the same sequence, so `clone` +first if you need to keep the original order. + +## `sample` — several distinct elements + +`sample` (in [`random`][random]) draws `n` *distinct* elements from a +sequence — like dealing a hand of cards, never the same card twice: + +``` +sample ( seq n -- seq' ) +``` + +```factor +{ 10 20 30 40 50 } 3 sample . ! { 40 10 30 } +``` + +Asking for more elements than the sequence holds is an error. + +## Reproducible randomness + +By default `random` draws from a global generator seeded from the +system clock, so every run differs. For tests — or any time you want a +game you can *replay* — bind a generator with a fixed **seed**. + +`` (in [`random.mersenne-twister`][mt]) builds a +generator from an integer seed, and `with-random` (in [`random`][random]) +runs a quotation with that generator installed: + +``` + ( seed -- rnd ) +with-random ( rnd quot -- ) +``` + +```factor +USING: random random.mersenne-twister ; + +42 [ 6 random 1 + ] with-random . +! always the same value for seed 42 +``` + +Everything inside the quotation — `random`, `randomize`, `sample` — +draws from the seeded generator, so the same seed always reproduces the +same outcome. Marking a word that wraps `with-random` as `inline` lets +the quotation return values to the caller. + +[random]: https://docs.factorcode.org/content/vocab-random.html +[mt]: https://docs.factorcode.org/content/vocab-random.mersenne-twister.html +[sequences]: https://docs.factorcode.org/content/vocab-sequences.html diff --git a/exercises/concept/boardwalk-games/.docs/source.md b/exercises/concept/boardwalk-games/.docs/source.md new file mode 100644 index 0000000..761b7ba --- /dev/null +++ b/exercises/concept/boardwalk-games/.docs/source.md @@ -0,0 +1,3 @@ +# Source + +Created for the Factor track to introduce the `random` vocabulary. diff --git a/exercises/concept/boardwalk-games/.meta/config.json b/exercises/concept/boardwalk-games/.meta/config.json new file mode 100644 index 0000000..9b2b16e --- /dev/null +++ b/exercises/concept/boardwalk-games/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "boardwalk-games/boardwalk-games.factor" + ], + "test": [ + "boardwalk-games/boardwalk-games-tests.factor" + ], + "exemplar": [ + ".meta/exemplar.factor" + ] + }, + "icon": "amusement-park", + "blurb": "Learn about randomness in Factor by running the games of chance at a seaside boardwalk arcade." +} diff --git a/exercises/concept/boardwalk-games/.meta/design.md b/exercises/concept/boardwalk-games/.meta/design.md new file mode 100644 index 0000000..c834c1e --- /dev/null +++ b/exercises/concept/boardwalk-games/.meta/design.md @@ -0,0 +1,43 @@ +# Design + +## Goal + +Introduce the `random` vocabulary: drawing random values, shuffling and +sampling sequences, and making randomness reproducible with a seeded +generator. + +## Learning objectives + +- Use `random` on an integer (`[0, n)`) and on a sequence (a random + element). +- Shuffle a sequence with `randomize`. +- Draw distinct elements with `sample`. +- Install a seeded generator with `` and `with-random` + to make outcomes reproducible. +- Recognise that a word wrapping `with-random` must be `inline` for the + quotation to return a value. + +## Out of scope + +- The random-generator protocol (`random-32*`, `random-bytes*`) and + writing custom generators. +- `random-bits`, `random-unit(s)`, `uniform-random`, `normal-random`, + and other distribution words. +- `system-random-generator` / `secure-random-generator` and the + cryptographic generators. + +## Concepts + +- `randomness`: the `random` vocabulary — `random`, `randomize`, + `sample`, and reproducible randomness via `with-random` plus + ``. + +## Prerequisites + +- `numbers` — taught in `currency-conversion` (range arithmetic in + `roll-die`). +- `sequences` — taught in `backyard-birdwatcher` (`random` on a + sequence, `randomize`, `sample`). +- `quotations` — taught in `high-school-sweetheart` (the quotation + argument to `play-seeded`). +- `combinators` — taught in `joiners-journey` (`dip` in `play-seeded`). diff --git a/exercises/concept/boardwalk-games/.meta/exemplar.factor b/exercises/concept/boardwalk-games/.meta/exemplar.factor new file mode 100644 index 0000000..e2540ac --- /dev/null +++ b/exercises/concept/boardwalk-games/.meta/exemplar.factor @@ -0,0 +1,17 @@ +USING: kernel math random random.mersenne-twister ; +IN: boardwalk-games + +: roll-die ( sides -- n ) + random 1 + ; + +: pick-prize ( prizes -- prize ) + random ; + +: shuffle-deck ( deck -- deck' ) + randomize ; + +: deal-hand ( deck n -- hand ) + sample ; + +: play-seeded ( seed quot -- ) + [ ] dip with-random ; inline diff --git a/exercises/concept/boardwalk-games/boardwalk-games/boardwalk-games-tests.factor b/exercises/concept/boardwalk-games/boardwalk-games/boardwalk-games-tests.factor new file mode 100644 index 0000000..a6724b6 --- /dev/null +++ b/exercises/concept/boardwalk-games/boardwalk-games/boardwalk-games-tests.factor @@ -0,0 +1,69 @@ +USING: boardwalk-games exercism-tools kernel math math.order +random random.mersenne-twister sequences sets sorting tools.test ; +IN: boardwalk-games.tests + +TASK: 1 roll-die +{ 6 } [ 4 [ 6 roll-die ] with-random ] unit-test + +STOP-HERE + +{ 2 } [ 5 [ 6 roll-die ] with-random ] unit-test + +! Every roll of a six-sided die lands in 1..6. +{ t } [ + 1 [ 200 [ 6 roll-die ] replicate ] with-random + [ 1 6 between? ] all? +] unit-test + +TASK: 2 pick-prize +{ "e" } [ + 4 + [ { "a" "b" "c" "d" "e" } pick-prize ] with-random +] unit-test + +{ "b" } [ + 5 + [ { "a" "b" "c" "d" "e" } pick-prize ] with-random +] unit-test + +TASK: 3 shuffle-deck +{ { 5 1 3 4 2 } } [ + 42 [ { 1 2 3 4 5 } shuffle-deck ] with-random +] unit-test + +{ { 2 4 3 5 1 } } [ + 7 [ { 1 2 3 4 5 } shuffle-deck ] with-random +] unit-test + +! Shuffling keeps exactly the same cards, only reordered. +{ t } [ + 13 [ { 3 1 4 1 5 9 } shuffle-deck ] with-random + natural-sort { 1 1 3 4 5 9 } = +] unit-test + +TASK: 4 deal-hand +{ { 30 40 20 } } [ + 42 [ { 10 20 30 40 50 } 3 deal-hand ] with-random +] unit-test + +{ { 30 50 10 } } [ + 7 [ { 10 20 30 40 50 } 3 deal-hand ] with-random +] unit-test + +! A dealt hand is distinct cards drawn from the deck. +{ t } [ + 7 [ { 10 20 30 40 50 } 3 deal-hand ] with-random + [ [ { 10 20 30 40 50 } member? ] all? ] + [ [ length ] [ members length ] bi = ] bi and +] unit-test + +TASK: 5 play-seeded +! play-seeded installs the seeded generator and returns the result. +{ 6 } [ 4 [ 6 roll-die ] play-seeded ] unit-test + +! The same seed reproduces the same game. +{ t } [ + 99 [ { 10 20 30 40 50 } 3 deal-hand ] play-seeded + 99 [ { 10 20 30 40 50 } 3 deal-hand ] play-seeded + = +] unit-test diff --git a/exercises/concept/boardwalk-games/boardwalk-games/boardwalk-games.factor b/exercises/concept/boardwalk-games/boardwalk-games/boardwalk-games.factor new file mode 100644 index 0000000..b01021a --- /dev/null +++ b/exercises/concept/boardwalk-games/boardwalk-games/boardwalk-games.factor @@ -0,0 +1,17 @@ +USING: kernel ; +IN: boardwalk-games + +: roll-die ( sides -- n ) + "unimplemented" throw ; + +: pick-prize ( prizes -- prize ) + "unimplemented" throw ; + +: shuffle-deck ( deck -- deck' ) + "unimplemented" throw ; + +: deal-hand ( deck n -- hand ) + "unimplemented" throw ; + +: play-seeded ( seed quot -- ) + "unimplemented" throw ; inline diff --git a/exercises/concept/boardwalk-games/exercism-tools/exercism-tools.factor b/exercises/concept/boardwalk-games/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/concept/boardwalk-games/exercism-tools/exercism-tools.factor @@ -0,0 +1,37 @@ +USING: accessors command-line continuations debugger io kernel + lexer namespaces sequences source-files.errors.debugger + system tools.test vocabs vocabs.loader ; +IN: exercism-tools + +SYNTAX: STOP-HERE + lexer get [ text>> length ] keep line<< ; + +SYNTAX: TASK: + lexer get next-line ; + +! Label the test that follows with its description. The marker lets the +! wrapper strip this line from captured output and attach it to the next +! test as a name, rather than leaving it in the previous test's output. +: description ( str -- ) + "###DESC### " write print ; + +! Print one failure block in a stable, parser-friendly form. Bracketed by +! markers so a wrapper can split the stream reliably and avoid Factor's +! noisy callstack output (which is interleaved with subsequent failures). +:: print-failure ( failure -- ) + "###FAIL_BEGIN###" print + failure error-location print + failure error>> [ error. ] [ 2drop ] recover + "###FAIL_END###" print + flush ; + +: print-failures ( -- ) + test-failures get [ print-failure ] each ; + +: run-exercism-tests ( -- ) + command-line get first + [ require ] [ test ] bi + test-failures get empty? + [ 0 exit ] [ print-failures 1 exit ] if ; + +MAIN: run-exercism-tests diff --git a/exercises/concept/ferry-schedule/.docs/hints.md b/exercises/concept/ferry-schedule/.docs/hints.md new file mode 100644 index 0000000..e587aea --- /dev/null +++ b/exercises/concept/ferry-schedule/.docs/hints.md @@ -0,0 +1,32 @@ +# Hints + +## General + +- The date words are in the [`calendar`][calendar] vocabulary; + `day-names` is in [`calendar.english`][english]. + +## 1. Record a sailing date + +- `` takes the year, month, and day directly. + +## 2. Name the weekday + +- `day-of-week` gives a number from `0` (Sunday) to `6` (Saturday). +- Use it as an index into `day-names` with `nth`. + +## 3. Leap years + +- `leap-year?` already implements the rule for you. + +## 4. Days in a month + +- Build a timestamp for the first of the month with ``, then ask + `days-in-month`. It handles February in leap years for you. + +## 5. Days later + +- `days` turns a number into a `duration`; `time+` adds a duration to a + timestamp. + +[calendar]: https://docs.factorcode.org/content/vocab-calendar.html +[english]: https://docs.factorcode.org/content/vocab-calendar.english.html diff --git a/exercises/concept/ferry-schedule/.docs/instructions.md b/exercises/concept/ferry-schedule/.docs/instructions.md new file mode 100644 index 0000000..468503f --- /dev/null +++ b/exercises/concept/ferry-schedule/.docs/instructions.md @@ -0,0 +1,53 @@ +# Instructions + +You keep the sailing timetable for a small island ferry. Departures are +recorded as dates, and you need a few helpers to work with them. + +## 1. Record a sailing date + +Define `make-date` to take a year, month, and day and return the +corresponding timestamp. + +```factor +2024 7 14 make-date . +! => T{ timestamp { year 2024 } { month 7 } { day 14 } ... } +``` + +## 2. Name the weekday + +Define `weekday-name` to take a sailing's timestamp and return the name +of the weekday it falls on. + +```factor +2024 1 8 make-date weekday-name . +! => "Monday" +``` + +## 3. Leap years + +Define `leap?` to take a year and return whether it is a leap year. + +```factor +2024 leap? . ! => t +2023 leap? . ! => f +``` + +## 4. Days in a month + +Define `month-length` to take a year and a month and return the number +of days in that month (remember February). + +```factor +2024 2 month-length . ! => 29 +2023 2 month-length . ! => 28 +``` + +## 5. Days later + +Define `add-days` to take a timestamp and a number of days, and return +the timestamp that many days later. + +```factor +2024 2 28 make-date 1 add-days >date< . +! => 29 (2024-02-29) +``` diff --git a/exercises/concept/ferry-schedule/.docs/introduction.md b/exercises/concept/ferry-schedule/.docs/introduction.md new file mode 100644 index 0000000..6b050c4 --- /dev/null +++ b/exercises/concept/ferry-schedule/.docs/introduction.md @@ -0,0 +1,80 @@ +# Introduction + +Factor models dates and times with the [`calendar`][calendar] +vocabulary. A point in time is a `timestamp`; a span of time is a +`duration`. + +## Building a date + +`` (in [`calendar`][calendar]) builds a `timestamp` at midnight +on the given day: + +``` + ( year month day -- timestamp ) +``` + +```factor +USING: calendar ; + +2024 7 14 . +! a timestamp at midnight on 2024-07-14 +``` + +`>date<` splits a timestamp back into its parts: + +``` +>date< ( timestamp -- year month day ) +``` + +```factor +2024 7 14 >date< . ! => 14 (with 2024 and 7 below it) +``` + +## Inspecting a date + +`day-of-week` returns the weekday as an integer, `0` for Sunday +through `6` for Saturday. `day-names` (in [`calendar.english`][english]) +is the matching list of names, so `day-names nth` turns the number +into a word: + +```factor +USING: calendar calendar.english sequences ; + +2024 1 8 day-of-week . ! => 1 +day-names . ! { "Sunday" "Monday" ... "Saturday" } — Sunday first +``` + +`leap-year?` accepts a year (or a timestamp) and `days-in-month` +returns the length of a timestamp's month — already accounting for +leap years: + +``` +leap-year? ( obj -- ? ) +days-in-month ( timestamp -- n ) +``` + +```factor +2024 leap-year? . ! => t +2024 2 1 days-in-month . ! => 29 +``` + +## Date arithmetic + +Words like `days`, `weeks`, and `hours` build a `duration`, and +`time+` shifts a timestamp by one: + +``` +days ( x -- duration ) +time+ ( timestamp duration -- timestamp ) +``` + +```factor +2024 2 28 1 days time+ >date< . +! => 29 (2024-02-29, with 2024 and 2 below it) +``` + +Arithmetic carries correctly across month and year boundaries, and +two timestamps that name the same instant compare equal with `=`. + +[calendar]: https://docs.factorcode.org/content/vocab-calendar.html +[english]: https://docs.factorcode.org/content/vocab-calendar.english.html diff --git a/exercises/concept/ferry-schedule/.docs/source.md b/exercises/concept/ferry-schedule/.docs/source.md new file mode 100644 index 0000000..94a93a2 --- /dev/null +++ b/exercises/concept/ferry-schedule/.docs/source.md @@ -0,0 +1,3 @@ +# Source + +Created for the Factor track to introduce the `calendar` vocabulary. diff --git a/exercises/concept/ferry-schedule/.meta/config.json b/exercises/concept/ferry-schedule/.meta/config.json new file mode 100644 index 0000000..b3bd2df --- /dev/null +++ b/exercises/concept/ferry-schedule/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "ferry-schedule/ferry-schedule.factor" + ], + "test": [ + "ferry-schedule/ferry-schedule-tests.factor" + ], + "exemplar": [ + ".meta/exemplar.factor" + ] + }, + "icon": "calendar", + "blurb": "Learn about the calendar vocabulary in Factor by keeping the timetable for an island ferry." +} diff --git a/exercises/concept/ferry-schedule/.meta/design.md b/exercises/concept/ferry-schedule/.meta/design.md new file mode 100644 index 0000000..65d90a9 --- /dev/null +++ b/exercises/concept/ferry-schedule/.meta/design.md @@ -0,0 +1,36 @@ +# Design + +## Goal + +Introduce the `calendar` vocabulary: building and inspecting +timestamps, and shifting them with durations. + +## Learning objectives + +- Build a `timestamp` with `` and split it with `>date<`. +- Read the weekday with `day-of-week` (0 = Sunday) and name it via + `day-names`. +- Use `leap-year?` and `days-in-month`. +- Build a `duration` with `days` and shift a timestamp using `time+`. +- Recognise that timestamps naming the same instant compare equal + with `=`. + +## Out of scope + +- Times of day and time-of-day arithmetic (`set-time`, `+hour`, + ``), time zones, and `now`. +- `duration` introspection (`duration>days`, `time-`) and the full set + of duration constructors. +- Parsing and formatting timestamps (`calendar.format`). + +## Concepts + +- `calendar`: timestamps, `` / `>date<`, `day-of-week`, + `leap-year?`, `days-in-month`, and duration arithmetic via `time+`. + +## Prerequisites + +- `numbers` — taught in `currency-conversion` (the year/month/day + values). +- `sequences` — taught in `backyard-birdwatcher` (`nth` into + `day-names`). diff --git a/exercises/concept/ferry-schedule/.meta/exemplar.factor b/exercises/concept/ferry-schedule/.meta/exemplar.factor new file mode 100644 index 0000000..0bb5bf2 --- /dev/null +++ b/exercises/concept/ferry-schedule/.meta/exemplar.factor @@ -0,0 +1,17 @@ +USING: calendar calendar.english kernel sequences ; +IN: ferry-schedule + +: make-date ( year month day -- timestamp ) + ; + +: weekday-name ( timestamp -- name ) + day-of-week day-names nth ; + +: leap? ( year -- ? ) + leap-year? ; + +: month-length ( year month -- n ) + 1 days-in-month ; + +: add-days ( timestamp n -- timestamp ) + days time+ ; diff --git a/exercises/concept/ferry-schedule/exercism-tools/exercism-tools.factor b/exercises/concept/ferry-schedule/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/concept/ferry-schedule/exercism-tools/exercism-tools.factor @@ -0,0 +1,37 @@ +USING: accessors command-line continuations debugger io kernel + lexer namespaces sequences source-files.errors.debugger + system tools.test vocabs vocabs.loader ; +IN: exercism-tools + +SYNTAX: STOP-HERE + lexer get [ text>> length ] keep line<< ; + +SYNTAX: TASK: + lexer get next-line ; + +! Label the test that follows with its description. The marker lets the +! wrapper strip this line from captured output and attach it to the next +! test as a name, rather than leaving it in the previous test's output. +: description ( str -- ) + "###DESC### " write print ; + +! Print one failure block in a stable, parser-friendly form. Bracketed by +! markers so a wrapper can split the stream reliably and avoid Factor's +! noisy callstack output (which is interleaved with subsequent failures). +:: print-failure ( failure -- ) + "###FAIL_BEGIN###" print + failure error-location print + failure error>> [ error. ] [ 2drop ] recover + "###FAIL_END###" print + flush ; + +: print-failures ( -- ) + test-failures get [ print-failure ] each ; + +: run-exercism-tests ( -- ) + command-line get first + [ require ] [ test ] bi + test-failures get empty? + [ 0 exit ] [ print-failures 1 exit ] if ; + +MAIN: run-exercism-tests diff --git a/exercises/concept/ferry-schedule/ferry-schedule/ferry-schedule-tests.factor b/exercises/concept/ferry-schedule/ferry-schedule/ferry-schedule-tests.factor new file mode 100644 index 0000000..58d32ee --- /dev/null +++ b/exercises/concept/ferry-schedule/ferry-schedule/ferry-schedule-tests.factor @@ -0,0 +1,49 @@ +USING: calendar exercism-tools ferry-schedule kernel tools.test ; +IN: ferry-schedule.tests + +TASK: 1 make-date +{ 2024 7 14 } [ 2024 7 14 make-date >date< ] unit-test + +STOP-HERE + +TASK: 2 weekday-name +{ "Sunday" } [ 2024 1 7 make-date weekday-name ] unit-test +{ "Monday" } [ 2024 1 8 make-date weekday-name ] unit-test +{ "Thursday" } [ 2024 2 29 make-date weekday-name ] unit-test + +TASK: 3 leap? +{ t } [ 2024 leap? ] unit-test +{ f } [ 2023 leap? ] unit-test +! Century years are leap years only when divisible by 400. +{ f } [ 1900 leap? ] unit-test +{ t } [ 2000 leap? ] unit-test + +TASK: 4 month-length +! February has 29 days in a leap year, 28 otherwise. +{ 29 } [ 2024 2 month-length ] unit-test +{ 28 } [ 2023 2 month-length ] unit-test +{ 31 } [ 2024 1 month-length ] unit-test +{ 30 } [ 2024 4 month-length ] unit-test + +TASK: 5 add-days +{ t } [ + 2024 1 31 make-date 1 add-days + 2024 2 1 make-date = +] unit-test + +! Adding days rolls over into the next year. +{ t } [ + 2023 12 31 make-date 1 add-days + 2024 1 1 make-date = +] unit-test + +! February gains a 29th day in a leap year. +{ t } [ + 2024 2 28 make-date 1 add-days + 2024 2 29 make-date = +] unit-test + +{ t } [ + 2024 3 1 make-date 10 add-days + 2024 3 11 make-date = +] unit-test diff --git a/exercises/concept/ferry-schedule/ferry-schedule/ferry-schedule.factor b/exercises/concept/ferry-schedule/ferry-schedule/ferry-schedule.factor new file mode 100644 index 0000000..df81016 --- /dev/null +++ b/exercises/concept/ferry-schedule/ferry-schedule/ferry-schedule.factor @@ -0,0 +1,17 @@ +USING: kernel ; +IN: ferry-schedule + +: make-date ( year month day -- timestamp ) + "unimplemented" throw ; + +: weekday-name ( timestamp -- name ) + "unimplemented" throw ; + +: leap? ( year -- ? ) + "unimplemented" throw ; + +: month-length ( year month -- n ) + "unimplemented" throw ; + +: add-days ( timestamp n -- timestamp ) + "unimplemented" throw ; diff --git a/exercises/practice/dnd-character/.docs/instructions.md b/exercises/practice/dnd-character/.docs/instructions.md new file mode 100644 index 0000000..e14e794 --- /dev/null +++ b/exercises/practice/dnd-character/.docs/instructions.md @@ -0,0 +1,32 @@ +# Instructions + +For a game of [Dungeons & Dragons][dnd], each player starts by generating a character they can play with. +This character has, among other things, six abilities; strength, dexterity, constitution, intelligence, wisdom and charisma. +These six abilities have scores that are determined randomly. +You do this by rolling four 6-sided dice and recording the sum of the largest three dice. +You do this six times, once for each ability. + +Your character's initial hitpoints are 10 + your character's constitution modifier. +You find your character's constitution modifier by subtracting 10 from your character's constitution, divide by 2 and round down. + +Write a random character generator that follows the above rules. + +For example, the six throws of four dice may look like: + +- 5, 3, 1, 6: You discard the 1 and sum 5 + 3 + 6 = 14, which you assign to strength. +- 3, 2, 5, 3: You discard the 2 and sum 3 + 5 + 3 = 11, which you assign to dexterity. +- 1, 1, 1, 1: You discard the 1 and sum 1 + 1 + 1 = 3, which you assign to constitution. +- 2, 1, 6, 6: You discard the 1 and sum 2 + 6 + 6 = 14, which you assign to intelligence. +- 3, 5, 3, 4: You discard the 3 and sum 5 + 3 + 4 = 12, which you assign to wisdom. +- 6, 6, 6, 6: You discard the 6 and sum 6 + 6 + 6 = 18, which you assign to charisma. + +Because constitution is 3, the constitution modifier is -4 and the hitpoints are 6. + +~~~~exercism/note +Most programming languages feature (pseudo-)random generators, but few programming languages are designed to roll dice. +One such language is [Troll][troll]. + +[troll]: https://di.ku.dk/Ansatte/?pure=da%2Fpublications%2Ftroll-a-language-for-specifying-dicerolls(84a45ff0-068b-11df-825d-000ea68e967b)%2Fexport.html +~~~~ + +[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons diff --git a/exercises/practice/dnd-character/.docs/introduction.md b/exercises/practice/dnd-character/.docs/introduction.md new file mode 100644 index 0000000..5301f61 --- /dev/null +++ b/exercises/practice/dnd-character/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +After weeks of anticipation, you and your friends get together for your very first game of [Dungeons & Dragons][dnd] (D&D). +Since this is the first session of the game, each player has to generate a character to play with. +The character's abilities are determined by rolling 6-sided dice, but where _are_ the dice? +With a shock, you realize that your friends are waiting for _you_ to produce the dice; after all it was your idea to play D&D! +Panicking, you realize you forgot to bring the dice, which would mean no D&D game. +As you have some basic coding skills, you quickly come up with a solution: you'll write a program to simulate dice rolls. + +[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons diff --git a/exercises/practice/dnd-character/.meta/config.json b/exercises/practice/dnd-character/.meta/config.json new file mode 100644 index 0000000..b40f8ed --- /dev/null +++ b/exercises/practice/dnd-character/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "dnd-character/dnd-character.factor" + ], + "test": [ + "dnd-character/dnd-character-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Randomly generate Dungeons & Dragons characters.", + "source": "Simon Shine, Erik Schierboom", + "source_url": "https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945" +} diff --git a/exercises/practice/dnd-character/.meta/example.factor b/exercises/practice/dnd-character/.meta/example.factor new file mode 100644 index 0000000..9ecee38 --- /dev/null +++ b/exercises/practice/dnd-character/.meta/example.factor @@ -0,0 +1,22 @@ +USING: accessors kernel locals math random sequences sorting ; +IN: dnd-character + +TUPLE: character + strength dexterity constitution intelligence wisdom charisma + hitpoints ; + +: modifier ( score -- n ) + 2 /i 5 - ; + +: roll-d6 ( -- n ) + 6 random 1 + ; + +! Roll four six-sided dice, drop the lowest, and sum the rest. +: ability ( -- score ) + 4 [ roll-d6 ] replicate sort rest sum ; + +:: ( -- character ) + ability ability ability ability ability ability + :> ( strength dexterity constitution intelligence wisdom charisma ) + strength dexterity constitution intelligence wisdom charisma + constitution modifier 10 + character boa ; diff --git a/exercises/practice/dnd-character/.meta/generator.jl b/exercises/practice/dnd-character/.meta/generator.jl new file mode 100644 index 0000000..20582b7 --- /dev/null +++ b/exercises/practice/dnd-character/.meta/generator.jl @@ -0,0 +1,58 @@ +module DndCharacter + +const EXTRA_VOCABS = [ + "accessors", "assocs", "combinators.short-circuit", + "math", "math.order", "math.statistics", "sequences", +] + +# Test-only helpers. The randomness cases from problem-specifications are +# replaced (via tests.toml + supplements.json) with stronger statistical +# tests: a validity check over many characters and a chi-squared +# goodness-of-fit against the exact 4d6-drop-lowest distribution. +const EXTRA_HEADER = """ +CONSTANT: ability-frequencies + { 1 4 10 21 38 62 91 122 148 167 172 160 131 94 54 21 } + +:: ability-counts ( samples -- counts ) + samples histogram :> h + 16 [ 3 + h at 0 or ] map ; + +: cell-chi2 ( observed expected -- x ) [ - sq ] [ nip ] 2bi /f ; + +:: ability-chi-squared ( n -- chi2 ) + n [ ability ] replicate ability-counts + ability-frequencies [ n 1296 / * ] map + [ cell-chi2 ] 2map sum ; + +: valid-ability? ( score -- ? ) 3 18 between? ; + +: valid-character? ( character -- ? ) + { + [ strength>> valid-ability? ] + [ dexterity>> valid-ability? ] + [ constitution>> valid-ability? ] + [ intelligence>> valid-ability? ] + [ wisdom>> valid-ability? ] + [ charisma>> valid-ability? ] + [ [ hitpoints>> ] [ constitution>> modifier 10 + ] bi = ] + } 1&& ; +""" + +function gen_test_case(case) + p = case["property"] + if p == "modifier" + return "{ $(case["expected"]) } [ $(case["input"]["score"]) modifier ] unit-test" + elseif p == "range" + return "{ t } [ 1000 [ ability ] replicate [ valid-ability? ] all? ] unit-test" + elseif p == "valid" + return "{ t } [ 1000 [ ] replicate [ valid-character? ] all? ] unit-test" + elseif p == "stable" + return "{ t } [ [ strength>> ] [ strength>> ] bi = ] unit-test" + elseif p == "distribution" + return "{ t } [ 12960 ability-chi-squared 80 < ] unit-test" + else + error("unknown property: " * p) + end +end + +end diff --git a/exercises/practice/dnd-character/.meta/supplements.json b/exercises/practice/dnd-character/.meta/supplements.json new file mode 100644 index 0000000..e7656af --- /dev/null +++ b/exercises/practice/dnd-character/.meta/supplements.json @@ -0,0 +1,18 @@ +[ + { + "description": "every rolled ability is within range", + "property": "range" + }, + { + "description": "a freshly rolled character is valid", + "property": "valid" + }, + { + "description": "each ability is only calculated once", + "property": "stable" + }, + { + "description": "abilities follow the 4d6-drop-lowest distribution", + "property": "distribution" + } +] diff --git a/exercises/practice/dnd-character/.meta/tests.toml b/exercises/practice/dnd-character/.meta/tests.toml new file mode 100644 index 0000000..acc8dfe --- /dev/null +++ b/exercises/practice/dnd-character/.meta/tests.toml @@ -0,0 +1,78 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1e9ae1dc-35bd-43ba-aa08-e4b94c20fa37] +description = "ability modifier -> ability modifier for score 3 is -4" + +[cc9bb24e-56b8-4e9e-989d-a0d1a29ebb9c] +description = "ability modifier -> ability modifier for score 4 is -3" + +[5b519fcd-6946-41ee-91fe-34b4f9808326] +description = "ability modifier -> ability modifier for score 5 is -3" + +[dc2913bd-6d7a-402e-b1e2-6d568b1cbe21] +description = "ability modifier -> ability modifier for score 6 is -2" + +[099440f5-0d66-4b1a-8a10-8f3a03cc499f] +description = "ability modifier -> ability modifier for score 7 is -2" + +[cfda6e5c-3489-42f0-b22b-4acb47084df0] +description = "ability modifier -> ability modifier for score 8 is -1" + +[c70f0507-fa7e-4228-8463-858bfbba1754] +description = "ability modifier -> ability modifier for score 9 is -1" + +[6f4e6c88-1cd9-46a0-92b8-db4a99b372f7] +description = "ability modifier -> ability modifier for score 10 is 0" + +[e00d9e5c-63c8-413f-879d-cd9be9697097] +description = "ability modifier -> ability modifier for score 11 is 0" + +[eea06f3c-8de0-45e7-9d9d-b8cab4179715] +description = "ability modifier -> ability modifier for score 12 is +1" + +[9c51f6be-db72-4af7-92ac-b293a02c0dcd] +description = "ability modifier -> ability modifier for score 13 is +1" + +[94053a5d-53b6-4efc-b669-a8b5098f7762] +description = "ability modifier -> ability modifier for score 14 is +2" + +[8c33e7ca-3f9f-4820-8ab3-65f2c9e2f0e2] +description = "ability modifier -> ability modifier for score 15 is +2" + +[c3ec871e-1791-44d0-b3cc-77e5fb4cd33d] +description = "ability modifier -> ability modifier for score 16 is +3" + +[3d053cee-2888-4616-b9fd-602a3b1efff4] +description = "ability modifier -> ability modifier for score 17 is +3" + +[bafd997a-e852-4e56-9f65-14b60261faee] +description = "ability modifier -> ability modifier for score 18 is +4" + +[4f28f19c-2e47-4453-a46a-c0d365259c14] +description = "random ability is within range" +include = false +comment = "Replaced by stronger statistical tests in .meta/supplements.json" + +[385d7e72-864f-4e88-8279-81a7d75b04ad] +description = "random character is valid" +include = false +comment = "Replaced by stronger statistical tests in .meta/supplements.json" + +[2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe] +description = "each ability is only calculated once" +include = false + +[dca2b2ec-f729-4551-84b9-078876bb4808] +description = "each ability is only calculated once" +reimplements = "2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe" +include = false +comment = "Replaced by stronger statistical tests in .meta/supplements.json" diff --git a/exercises/practice/dnd-character/dnd-character/dnd-character-tests.factor b/exercises/practice/dnd-character/dnd-character/dnd-character-tests.factor new file mode 100644 index 0000000..00dfc56 --- /dev/null +++ b/exercises/practice/dnd-character/dnd-character/dnd-character-tests.factor @@ -0,0 +1,91 @@ +USING: accessors assocs combinators.short-circuit dnd-character exercism-tools io kernel math math.order math.statistics sequences tools.test unicode ; +IN: dnd-character.tests +CONSTANT: ability-frequencies + { 1 4 10 21 38 62 91 122 148 167 172 160 131 94 54 21 } + +:: ability-counts ( samples -- counts ) + samples histogram :> h + 16 [ 3 + h at 0 or ] map ; + +: cell-chi2 ( observed expected -- x ) [ - sq ] [ nip ] 2bi /f ; + +:: ability-chi-squared ( n -- chi2 ) + n [ ability ] replicate ability-counts + ability-frequencies [ n 1296 / * ] map + [ cell-chi2 ] 2map sum ; + +: valid-ability? ( score -- ? ) 3 18 between? ; + +: valid-character? ( character -- ? ) + { + [ strength>> valid-ability? ] + [ dexterity>> valid-ability? ] + [ constitution>> valid-ability? ] + [ intelligence>> valid-ability? ] + [ wisdom>> valid-ability? ] + [ charisma>> valid-ability? ] + [ [ hitpoints>> ] [ constitution>> modifier 10 + ] bi = ] + } 1&& ; + + +"ability modifier for score 3 is -4" description +{ -4 } [ 3 modifier ] unit-test + +STOP-HERE + +"ability modifier for score 4 is -3" description +{ -3 } [ 4 modifier ] unit-test + +"ability modifier for score 5 is -3" description +{ -3 } [ 5 modifier ] unit-test + +"ability modifier for score 6 is -2" description +{ -2 } [ 6 modifier ] unit-test + +"ability modifier for score 7 is -2" description +{ -2 } [ 7 modifier ] unit-test + +"ability modifier for score 8 is -1" description +{ -1 } [ 8 modifier ] unit-test + +"ability modifier for score 9 is -1" description +{ -1 } [ 9 modifier ] unit-test + +"ability modifier for score 10 is 0" description +{ 0 } [ 10 modifier ] unit-test + +"ability modifier for score 11 is 0" description +{ 0 } [ 11 modifier ] unit-test + +"ability modifier for score 12 is +1" description +{ 1 } [ 12 modifier ] unit-test + +"ability modifier for score 13 is +1" description +{ 1 } [ 13 modifier ] unit-test + +"ability modifier for score 14 is +2" description +{ 2 } [ 14 modifier ] unit-test + +"ability modifier for score 15 is +2" description +{ 2 } [ 15 modifier ] unit-test + +"ability modifier for score 16 is +3" description +{ 3 } [ 16 modifier ] unit-test + +"ability modifier for score 17 is +3" description +{ 3 } [ 17 modifier ] unit-test + +"ability modifier for score 18 is +4" description +{ 4 } [ 18 modifier ] unit-test + +"every rolled ability is within range" description +{ t } [ 1000 [ ability ] replicate [ valid-ability? ] all? ] unit-test + +"a freshly rolled character is valid" description +{ t } [ 1000 [ ] replicate [ valid-character? ] all? ] unit-test + +"each ability is only calculated once" description +{ t } [ [ strength>> ] [ strength>> ] bi = ] unit-test + +"abilities follow the 4d6-drop-lowest distribution" description +{ t } [ 12960 ability-chi-squared 80 < ] unit-test diff --git a/exercises/practice/dnd-character/dnd-character/dnd-character.factor b/exercises/practice/dnd-character/dnd-character/dnd-character.factor new file mode 100644 index 0000000..0147e25 --- /dev/null +++ b/exercises/practice/dnd-character/dnd-character/dnd-character.factor @@ -0,0 +1,15 @@ +USING: kernel ; +IN: dnd-character + +TUPLE: character + strength dexterity constitution intelligence wisdom charisma + hitpoints ; + +: modifier ( score -- n ) + "unimplemented" throw ; + +: ability ( -- score ) + "unimplemented" throw ; + +: ( -- character ) + "unimplemented" throw ; diff --git a/exercises/practice/dnd-character/exercism-tools/exercism-tools.factor b/exercises/practice/dnd-character/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/practice/dnd-character/exercism-tools/exercism-tools.factor @@ -0,0 +1,37 @@ +USING: accessors command-line continuations debugger io kernel + lexer namespaces sequences source-files.errors.debugger + system tools.test vocabs vocabs.loader ; +IN: exercism-tools + +SYNTAX: STOP-HERE + lexer get [ text>> length ] keep line<< ; + +SYNTAX: TASK: + lexer get next-line ; + +! Label the test that follows with its description. The marker lets the +! wrapper strip this line from captured output and attach it to the next +! test as a name, rather than leaving it in the previous test's output. +: description ( str -- ) + "###DESC### " write print ; + +! Print one failure block in a stable, parser-friendly form. Bracketed by +! markers so a wrapper can split the stream reliably and avoid Factor's +! noisy callstack output (which is interleaved with subsequent failures). +:: print-failure ( failure -- ) + "###FAIL_BEGIN###" print + failure error-location print + failure error>> [ error. ] [ 2drop ] recover + "###FAIL_END###" print + flush ; + +: print-failures ( -- ) + test-failures get [ print-failure ] each ; + +: run-exercism-tests ( -- ) + command-line get first + [ require ] [ test ] bi + test-failures get empty? + [ 0 exit ] [ print-failures 1 exit ] if ; + +MAIN: run-exercism-tests diff --git a/exercises/practice/food-chain/.docs/instructions.append.md b/exercises/practice/food-chain/.docs/instructions.append.md new file mode 100644 index 0000000..f575605 --- /dev/null +++ b/exercises/practice/food-chain/.docs/instructions.append.md @@ -0,0 +1,31 @@ +# Instructions append + +## Factor specifics + +Implement the word: + +```factor +food-chain ( start end -- lines ) +``` + +`start` and `end` are verse numbers (1 for the fly, up to 8 for the +horse), inclusive. Return the requested verses as a sequence of +strings — one string per line of the song. + +When more than one verse is requested, separate consecutive verses +with a single empty string (`""`) so that the verses are rendered with +a blank line between them. There is no trailing blank line after the +last verse. + +```factor +1 2 food-chain . +! => { +! "I know an old lady who swallowed a fly." +! "I don't know why she swallowed the fly. Perhaps she'll die." +! "" +! "I know an old lady who swallowed a spider." +! "It wriggled and jiggled and tickled inside her." +! "She swallowed the spider to catch the fly." +! "I don't know why she swallowed the fly. Perhaps she'll die." +! } +``` diff --git a/exercises/practice/food-chain/.docs/instructions.md b/exercises/practice/food-chain/.docs/instructions.md new file mode 100644 index 0000000..125820e --- /dev/null +++ b/exercises/practice/food-chain/.docs/instructions.md @@ -0,0 +1,64 @@ +# Instructions + +Generate the lyrics of the song 'I Know an Old Lady Who Swallowed a Fly'. + +While you could copy/paste the lyrics, or read them from a file, this problem is much more interesting if you approach it algorithmically. + +This is a [cumulative song][cumulative-song] of unknown origin. + +This is one of many common variants. + +```text +I know an old lady who swallowed a fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a spider. +It wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a bird. +How absurd to swallow a bird! +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a cat. +Imagine that, to swallow a cat! +She swallowed the cat to catch the bird. +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a dog. +What a hog, to swallow a dog! +She swallowed the dog to catch the cat. +She swallowed the cat to catch the bird. +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a goat. +Just opened her throat and swallowed a goat! +She swallowed the goat to catch the dog. +She swallowed the dog to catch the cat. +She swallowed the cat to catch the bird. +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a cow. +I don't know how she swallowed a cow! +She swallowed the cow to catch the goat. +She swallowed the goat to catch the dog. +She swallowed the dog to catch the cat. +She swallowed the cat to catch the bird. +She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. +She swallowed the spider to catch the fly. +I don't know why she swallowed the fly. Perhaps she'll die. + +I know an old lady who swallowed a horse. +She's dead, of course! +``` + +[cumulative-song]: https://en.wikipedia.org/wiki/Cumulative_song diff --git a/exercises/practice/food-chain/.meta/config.json b/exercises/practice/food-chain/.meta/config.json new file mode 100644 index 0000000..8a4efa0 --- /dev/null +++ b/exercises/practice/food-chain/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "food-chain/food-chain.factor" + ], + "test": [ + "food-chain/food-chain-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Generate the lyrics of the song 'I Know an Old Lady Who Swallowed a Fly'.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/There_Was_an_Old_Lady_Who_Swallowed_a_Fly" +} diff --git a/exercises/practice/food-chain/.meta/example.factor b/exercises/practice/food-chain/.meta/example.factor new file mode 100644 index 0000000..e7d35a8 --- /dev/null +++ b/exercises/practice/food-chain/.meta/example.factor @@ -0,0 +1,55 @@ +USING: arrays kernel locals macros math quotations ranges sequences ; +IN: food-chain + +! Unroll a quotation over a literal sequence at compile time: the +! runtime program is straight-line code, with one inlined copy of the +! quotation per element and no iteration left to do. +MACRO: each-literal ( seq quot -- compound ) + [ curry ] curry map concat >quotation ; + +CONSTANT: animals { "fly" "spider" "bird" "cat" "dog" "goat" "cow" "horse" } + +CONSTANT: comments { + "" + "It wriggled and jiggled and tickled inside her." + "How absurd to swallow a bird!" + "Imagine that, to swallow a cat!" + "What a hog, to swallow a dog!" + "Just opened her throat and swallowed a goat!" + "I don't know how she swallowed a cow!" + "" +} + +:: catch-line ( predator prey -- str ) + prey "spider" = + " that wriggled and jiggled and tickled inside her" "" ? :> suffix + "She swallowed the " predator append " to catch the " append + prey append suffix append "." append ; + +! The cumulative predator-to-prey lines are fixed, so the macro unrolls +! catch-line over the literal pair table, building the array of links +! with one inlined step per pair. +: links ( -- seq ) + { } + { + { "spider" "fly" } + { "bird" "spider" } + { "cat" "bird" } + { "dog" "cat" } + { "goat" "dog" } + { "cow" "goat" } + } [ first2 catch-line suffix ] each-literal ; + +:: verse ( idx -- lines ) + "I know an old lady who swallowed a " idx animals nth append "." append :> intro + idx 7 = [ + intro "She's dead, of course!" 2array + ] [ + intro 1array + idx comments nth dup empty? [ drop ] [ suffix ] if + links idx head reverse append + "I don't know why she swallowed the fly. Perhaps she'll die." suffix + ] if ; + +:: food-chain ( start end -- lines ) + start end [a..b] [ 1 - verse ] map { "" } join ; diff --git a/exercises/practice/food-chain/.meta/generator.jl b/exercises/practice/food-chain/.meta/generator.jl new file mode 100644 index 0000000..a6a8318 --- /dev/null +++ b/exercises/practice/food-chain/.meta/generator.jl @@ -0,0 +1,15 @@ +module FoodChain + +function factor_string(s) + escaped = replace(replace(s, "\\" => "\\\\"), "\"" => "\\\"") + return "\"$(escaped)\"" +end + +function gen_test_case(case) + i = case["input"] + start, finish = i["startVerse"], i["endVerse"] + lines = join((factor_string(l) for l in case["expected"]), " ") + return """{ { $(lines) } }\n[ $(start) $(finish) food-chain ] unit-test""" +end + +end diff --git a/exercises/practice/food-chain/.meta/tests.toml b/exercises/practice/food-chain/.meta/tests.toml new file mode 100644 index 0000000..30c5b98 --- /dev/null +++ b/exercises/practice/food-chain/.meta/tests.toml @@ -0,0 +1,40 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[751dce68-9412-496e-b6e8-855998c56166] +description = "fly" + +[6c56f861-0c5e-4907-9a9d-b2efae389379] +description = "spider" + +[3edf5f33-bef1-4e39-ae67-ca5eb79203fa] +description = "bird" + +[e866a758-e1ff-400e-9f35-f27f28cc288f] +description = "cat" + +[3f02c30e-496b-4b2a-8491-bc7e2953cafb] +description = "dog" + +[4b3fd221-01ea-46e0-825b-5734634fbc59] +description = "goat" + +[1b707da9-7001-4fac-941f-22ad9c7a65d4] +description = "cow" + +[3cb10d46-ae4e-4d2c-9296-83c9ffc04cdc] +description = "horse" + +[22b863d5-17e4-4d1e-93e4-617329a5c050] +description = "multiple verses" + +[e626b32b-745c-4101-bcbd-3b13456893db] +description = "full song" diff --git a/exercises/practice/food-chain/exercism-tools/exercism-tools.factor b/exercises/practice/food-chain/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/practice/food-chain/exercism-tools/exercism-tools.factor @@ -0,0 +1,37 @@ +USING: accessors command-line continuations debugger io kernel + lexer namespaces sequences source-files.errors.debugger + system tools.test vocabs vocabs.loader ; +IN: exercism-tools + +SYNTAX: STOP-HERE + lexer get [ text>> length ] keep line<< ; + +SYNTAX: TASK: + lexer get next-line ; + +! Label the test that follows with its description. The marker lets the +! wrapper strip this line from captured output and attach it to the next +! test as a name, rather than leaving it in the previous test's output. +: description ( str -- ) + "###DESC### " write print ; + +! Print one failure block in a stable, parser-friendly form. Bracketed by +! markers so a wrapper can split the stream reliably and avoid Factor's +! noisy callstack output (which is interleaved with subsequent failures). +:: print-failure ( failure -- ) + "###FAIL_BEGIN###" print + failure error-location print + failure error>> [ error. ] [ 2drop ] recover + "###FAIL_END###" print + flush ; + +: print-failures ( -- ) + test-failures get [ print-failure ] each ; + +: run-exercism-tests ( -- ) + command-line get first + [ require ] [ test ] bi + test-failures get empty? + [ 0 exit ] [ print-failures 1 exit ] if ; + +MAIN: run-exercism-tests diff --git a/exercises/practice/food-chain/food-chain/food-chain-tests.factor b/exercises/practice/food-chain/food-chain/food-chain-tests.factor new file mode 100644 index 0000000..8c25a59 --- /dev/null +++ b/exercises/practice/food-chain/food-chain/food-chain-tests.factor @@ -0,0 +1,44 @@ +USING: exercism-tools food-chain io kernel tools.test unicode ; +IN: food-chain.tests + +"fly" description +{ { "I know an old lady who swallowed a fly." "I don't know why she swallowed the fly. Perhaps she'll die." } } +[ 1 1 food-chain ] unit-test + +STOP-HERE + +"spider" description +{ { "I know an old lady who swallowed a spider." "It wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." } } +[ 2 2 food-chain ] unit-test + +"bird" description +{ { "I know an old lady who swallowed a bird." "How absurd to swallow a bird!" "She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." } } +[ 3 3 food-chain ] unit-test + +"cat" description +{ { "I know an old lady who swallowed a cat." "Imagine that, to swallow a cat!" "She swallowed the cat to catch the bird." "She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." } } +[ 4 4 food-chain ] unit-test + +"dog" description +{ { "I know an old lady who swallowed a dog." "What a hog, to swallow a dog!" "She swallowed the dog to catch the cat." "She swallowed the cat to catch the bird." "She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." } } +[ 5 5 food-chain ] unit-test + +"goat" description +{ { "I know an old lady who swallowed a goat." "Just opened her throat and swallowed a goat!" "She swallowed the goat to catch the dog." "She swallowed the dog to catch the cat." "She swallowed the cat to catch the bird." "She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." } } +[ 6 6 food-chain ] unit-test + +"cow" description +{ { "I know an old lady who swallowed a cow." "I don't know how she swallowed a cow!" "She swallowed the cow to catch the goat." "She swallowed the goat to catch the dog." "She swallowed the dog to catch the cat." "She swallowed the cat to catch the bird." "She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." } } +[ 7 7 food-chain ] unit-test + +"horse" description +{ { "I know an old lady who swallowed a horse." "She's dead, of course!" } } +[ 8 8 food-chain ] unit-test + +"multiple verses" description +{ { "I know an old lady who swallowed a fly." "I don't know why she swallowed the fly. Perhaps she'll die." "" "I know an old lady who swallowed a spider." "It wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." "" "I know an old lady who swallowed a bird." "How absurd to swallow a bird!" "She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." } } +[ 1 3 food-chain ] unit-test + +"full song" description +{ { "I know an old lady who swallowed a fly." "I don't know why she swallowed the fly. Perhaps she'll die." "" "I know an old lady who swallowed a spider." "It wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." "" "I know an old lady who swallowed a bird." "How absurd to swallow a bird!" "She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." "" "I know an old lady who swallowed a cat." "Imagine that, to swallow a cat!" "She swallowed the cat to catch the bird." "She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." "" "I know an old lady who swallowed a dog." "What a hog, to swallow a dog!" "She swallowed the dog to catch the cat." "She swallowed the cat to catch the bird." "She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." "" "I know an old lady who swallowed a goat." "Just opened her throat and swallowed a goat!" "She swallowed the goat to catch the dog." "She swallowed the dog to catch the cat." "She swallowed the cat to catch the bird." "She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." "" "I know an old lady who swallowed a cow." "I don't know how she swallowed a cow!" "She swallowed the cow to catch the goat." "She swallowed the goat to catch the dog." "She swallowed the dog to catch the cat." "She swallowed the cat to catch the bird." "She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her." "She swallowed the spider to catch the fly." "I don't know why she swallowed the fly. Perhaps she'll die." "" "I know an old lady who swallowed a horse." "She's dead, of course!" } } +[ 1 8 food-chain ] unit-test diff --git a/exercises/practice/food-chain/food-chain/food-chain.factor b/exercises/practice/food-chain/food-chain/food-chain.factor new file mode 100644 index 0000000..f3e2c31 --- /dev/null +++ b/exercises/practice/food-chain/food-chain/food-chain.factor @@ -0,0 +1,5 @@ +USING: kernel ; +IN: food-chain + +: food-chain ( start end -- lines ) + "unimplemented" throw ; diff --git a/exercises/practice/isogram/.meta/config.json b/exercises/practice/isogram/.meta/config.json index 8486409..a1db784 100644 --- a/exercises/practice/isogram/.meta/config.json +++ b/exercises/practice/isogram/.meta/config.json @@ -1,5 +1,7 @@ { - "authors": [], + "authors": [ + "davespanton" + ], "contributors": [ "sjwarner-bp" ], diff --git a/exercises/practice/meetup/.docs/instructions.md b/exercises/practice/meetup/.docs/instructions.md new file mode 100644 index 0000000..8b1bda5 --- /dev/null +++ b/exercises/practice/meetup/.docs/instructions.md @@ -0,0 +1,34 @@ +# Instructions + +Your task is to find the exact date of a meetup, given a month, year, weekday and week. + +There are six week values to consider: `first`, `second`, `third`, `fourth`, `last`, `teenth`. + +For example, you might be asked to find the date for the meetup on the first Monday in January 2018 (January 1, 2018). + +Similarly, you might be asked to find: + +- the third Tuesday of August 2019 (August 20, 2019) +- the teenth Wednesday of May 2020 (May 13, 2020) +- the fourth Sunday of July 2021 (July 25, 2021) +- the last Thursday of November 2022 (November 24, 2022) +- the teenth Saturday of August 1953 (August 15, 1953) + +## Teenth + +The teenth week refers to the seven days in a month that end in '-teenth' (13th, 14th, 15th, 16th, 17th, 18th and 19th). + +If asked to find the teenth Saturday of August, 1953, we check its calendar: + +```plaintext + August 1953 +Su Mo Tu We Th Fr Sa + 1 + 2 3 4 5 6 7 8 + 9 10 11 12 13 14 15 +16 17 18 19 20 21 22 +23 24 25 26 27 28 29 +30 31 +``` + +From this we find that the teenth Saturday is August 15, 1953. diff --git a/exercises/practice/meetup/.docs/introduction.md b/exercises/practice/meetup/.docs/introduction.md new file mode 100644 index 0000000..29170ef --- /dev/null +++ b/exercises/practice/meetup/.docs/introduction.md @@ -0,0 +1,29 @@ +# Introduction + +Every month, your partner meets up with their best friend. +Both of them have very busy schedules, making it challenging to find a suitable date! +Given your own busy schedule, your partner always double-checks potential meetup dates with you: + +- "Can I meet up on the first Friday of next month?" +- "What about the third Wednesday?" +- "Maybe the last Sunday?" + +In this month's call, your partner asked you this question: + +- "I'd like to meet up on the teenth Thursday; is that okay?" + +Confused, you ask what a "teenth" day is. +Your partner explains that a teenth day, a concept they made up, refers to the days in a month that end in '-teenth': + +- 13th (thirteenth) +- 14th (fourteenth) +- 15th (fifteenth) +- 16th (sixteenth) +- 17th (seventeenth) +- 18th (eighteenth) +- 19th (nineteenth) + +As there are also seven weekdays, it is guaranteed that each day of the week has _exactly one_ teenth day each month. + +Now that you understand the concept of a teenth day, you check your calendar. +You don't have anything planned on the teenth Thursday, so you happily confirm the date with your partner. diff --git a/exercises/practice/meetup/.meta/config.json b/exercises/practice/meetup/.meta/config.json new file mode 100644 index 0000000..8c60f50 --- /dev/null +++ b/exercises/practice/meetup/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "meetup/meetup.factor" + ], + "test": [ + "meetup/meetup-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Calculate the date of meetups.", + "source": "Jeremy Hinegardner mentioned a Boulder meetup that happens on the Wednesteenth of every month" +} diff --git a/exercises/practice/meetup/.meta/example.factor b/exercises/practice/meetup/.meta/example.factor new file mode 100644 index 0000000..dba47a1 --- /dev/null +++ b/exercises/practice/meetup/.meta/example.factor @@ -0,0 +1,23 @@ +USING: calendar calendar.english combinators kernel locals math +sequences ; +IN: meetup + +! Each schedule names a 7-day window of the month; the meetup is the +! first occurrence of the target weekday on or after the window's +! start day. +:: first-on-or-after ( year month start dow -- day ) + year month start day-of-week + dow swap - 7 + 7 mod start + ; + +:: meetup ( year month week dayofweek -- timestamp ) + dayofweek day-names index :> dow + week { + { "first" [ 1 ] } + { "second" [ 8 ] } + { "third" [ 15 ] } + { "fourth" [ 22 ] } + { "teenth" [ 13 ] } + { "last" [ year month 1 days-in-month 6 - ] } + } case :> start + year month start dow first-on-or-after :> day + year month day ; diff --git a/exercises/practice/meetup/.meta/generator.jl b/exercises/practice/meetup/.meta/generator.jl new file mode 100644 index 0000000..2a0bee1 --- /dev/null +++ b/exercises/practice/meetup/.meta/generator.jl @@ -0,0 +1,13 @@ +module Meetup + +const EXTRA_VOCABS = ["calendar"] + +function gen_test_case(case) + i = case["input"] + year, month = i["year"], i["month"] + week, dayofweek = i["week"], i["dayofweek"] + ey, em, ed = parse.(Int, split(case["expected"], "-")) + return """{ t } [ $(year) $(month) "$(week)" "$(dayofweek)" meetup $(ey) $(em) $(ed) = ] unit-test""" +end + +end diff --git a/exercises/practice/meetup/.meta/tests.toml b/exercises/practice/meetup/.meta/tests.toml new file mode 100644 index 0000000..1e5b84d --- /dev/null +++ b/exercises/practice/meetup/.meta/tests.toml @@ -0,0 +1,295 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[d7f8eadd-d4fc-46ee-8a20-e97bd3fd01c8] +description = "when teenth Monday is the 13th, the first day of the teenth week" + +[f78373d1-cd53-4a7f-9d37-e15bf8a456b4] +description = "when teenth Monday is the 19th, the last day of the teenth week" + +[8c78bea7-a116-425b-9c6b-c9898266d92a] +description = "when teenth Monday is some day in the middle of the teenth week" + +[cfef881b-9dc9-4d0b-8de4-82d0f39fc271] +description = "when teenth Tuesday is the 19th, the last day of the teenth week" + +[69048961-3b00-41f9-97ee-eb6d83a8e92b] +description = "when teenth Tuesday is some day in the middle of the teenth week" + +[d30bade8-3622-466a-b7be-587414e0caa6] +description = "when teenth Tuesday is the 13th, the first day of the teenth week" + +[8db4b58b-92f3-4687-867b-82ee1a04f851] +description = "when teenth Wednesday is some day in the middle of the teenth week" + +[6c27a2a2-28f8-487f-ae81-35d08c4664f7] +description = "when teenth Wednesday is the 13th, the first day of the teenth week" + +[008a8674-1958-45b5-b8e6-c2c9960d973a] +description = "when teenth Wednesday is the 19th, the last day of the teenth week" + +[e4abd5e3-57cb-4091-8420-d97e955c0dbd] +description = "when teenth Thursday is some day in the middle of the teenth week" + +[85da0b0f-eace-4297-a6dd-63588d5055b4] +description = "when teenth Thursday is the 13th, the first day of the teenth week" + +[ecf64f9b-8413-489b-bf6e-128045f70bcc] +description = "when teenth Thursday is the 19th, the last day of the teenth week" + +[ac4e180c-7d0a-4d3d-b05f-f564ebb584ca] +description = "when teenth Friday is the 19th, the last day of the teenth week" + +[b79101c7-83ad-4f8f-8ec8-591683296315] +description = "when teenth Friday is some day in the middle of the teenth week" + +[6ed38b9f-0072-4901-bd97-7c8b8b0ef1b8] +description = "when teenth Friday is the 13th, the first day of the teenth week" + +[dfae03ed-9610-47de-a632-655ab01e1e7c] +description = "when teenth Saturday is some day in the middle of the teenth week" + +[ec02e3e1-fc72-4a3c-872f-a53fa8ab358e] +description = "when teenth Saturday is the 13th, the first day of the teenth week" + +[d983094b-7259-4195-b84e-5d09578c89d9] +description = "when teenth Saturday is the 19th, the last day of the teenth week" + +[d84a2a2e-f745-443a-9368-30051be60c2e] +description = "when teenth Sunday is the 19th, the last day of the teenth week" + +[0e64bc53-92a3-4f61-85b2-0b7168c7ce5a] +description = "when teenth Sunday is some day in the middle of the teenth week" + +[de87652c-185e-4854-b3ae-04cf6150eead] +description = "when teenth Sunday is the 13th, the first day of the teenth week" + +[2cbfd0f5-ba3a-46da-a8cc-0fe4966d3411] +description = "when first Monday is some day in the middle of the first week" + +[a6168c7c-ed95-4bb3-8f92-c72575fc64b0] +description = "when first Monday is the 1st, the first day of the first week" + +[1bfc620f-1c54-4bbd-931f-4a1cd1036c20] +description = "when first Tuesday is the 7th, the last day of the first week" + +[12959c10-7362-4ca0-a048-50cf1c06e3e2] +description = "when first Tuesday is some day in the middle of the first week" + +[1033dc66-8d0b-48a1-90cb-270703d59d1d] +description = "when first Wednesday is some day in the middle of the first week" + +[b89185b9-2f32-46f4-a602-de20b09058f6] +description = "when first Wednesday is the 7th, the last day of the first week" + +[53aedc4d-b2c8-4dfb-abf7-a8dc9cdceed5] +description = "when first Thursday is some day in the middle of the first week" + +[b420a7e3-a94c-4226-870a-9eb3a92647f0] +description = "when first Thursday is another day in the middle of the first week" + +[61df3270-28b4-4713-bee2-566fa27302ca] +description = "when first Friday is the 1st, the first day of the first week" + +[cad33d4d-595c-412f-85cf-3874c6e07abf] +description = "when first Friday is some day in the middle of the first week" + +[a2869b52-5bba-44f0-a863-07bd1f67eadb] +description = "when first Saturday is some day in the middle of the first week" + +[3585315a-d0db-4ea1-822e-0f22e2a645f5] +description = "when first Saturday is another day in the middle of the first week" + +[c49e9bd9-8ccf-4cf2-947a-0ccd4e4f10b1] +description = "when first Sunday is some day in the middle of the first week" + +[1513328b-df53-4714-8677-df68c4f9366c] +description = "when first Sunday is the 7th, the last day of the first week" + +[49e083af-47ec-4018-b807-62ef411efed7] +description = "when second Monday is some day in the middle of the second week" + +[6cb79a73-38fe-4475-9101-9eec36cf79e5] +description = "when second Monday is the 8th, the first day of the second week" + +[4c39b594-af7e-4445-aa03-bf4f8effd9a1] +description = "when second Tuesday is the 14th, the last day of the second week" + +[41b32c34-2e39-40e3-b790-93539aaeb6dd] +description = "when second Tuesday is some day in the middle of the second week" + +[90a160c5-b5d9-4831-927f-63a78b17843d] +description = "when second Wednesday is some day in the middle of the second week" + +[23b98ce7-8dd5-41a1-9310-ef27209741cb] +description = "when second Wednesday is the 14th, the last day of the second week" + +[447f1960-27ca-4729-bc3f-f36043f43ed0] +description = "when second Thursday is some day in the middle of the second week" + +[c9aa2687-300c-4e79-86ca-077849a81bde] +description = "when second Thursday is another day in the middle of the second week" + +[a7e11ef3-6625-4134-acda-3e7195421c09] +description = "when second Friday is the 8th, the first day of the second week" + +[8b420e5f-9290-4106-b5ae-022f3e2a3e41] +description = "when second Friday is some day in the middle of the second week" + +[80631afc-fc11-4546-8b5f-c12aaeb72b4f] +description = "when second Saturday is some day in the middle of the second week" + +[e34d43ac-f470-44c2-aa5f-e97b78ecaf83] +description = "when second Saturday is another day in the middle of the second week" + +[a57d59fd-1023-47ad-b0df-a6feb21b44fc] +description = "when second Sunday is some day in the middle of the second week" + +[a829a8b0-abdd-4ad1-b66c-5560d843c91a] +description = "when second Sunday is the 14th, the last day of the second week" + +[501a8a77-6038-4fc0-b74c-33634906c29d] +description = "when third Monday is some day in the middle of the third week" + +[49e4516e-cf32-4a58-8bbc-494b7e851c92] +description = "when third Monday is the 15th, the first day of the third week" + +[4db61095-f7c7-493c-85f1-9996ad3012c7] +description = "when third Tuesday is the 21st, the last day of the third week" + +[714fc2e3-58d0-4b91-90fd-61eefd2892c0] +description = "when third Tuesday is some day in the middle of the third week" + +[b08a051a-2c80-445b-9b0e-524171a166d1] +description = "when third Wednesday is some day in the middle of the third week" + +[80bb9eff-3905-4c61-8dc9-bb03016d8ff8] +description = "when third Wednesday is the 21st, the last day of the third week" + +[fa52a299-f77f-4784-b290-ba9189fbd9c9] +description = "when third Thursday is some day in the middle of the third week" + +[f74b1bc6-cc5c-4bf1-ba69-c554a969eb38] +description = "when third Thursday is another day in the middle of the third week" + +[8900f3b0-801a-466b-a866-f42d64667abd] +description = "when third Friday is the 15th, the first day of the third week" + +[538ac405-a091-4314-9ccd-920c4e38e85e] +description = "when third Friday is some day in the middle of the third week" + +[244db35c-2716-4fa0-88ce-afd58e5cf910] +description = "when third Saturday is some day in the middle of the third week" + +[dd28544f-f8fa-4f06-9bcd-0ad46ce68e9e] +description = "when third Saturday is another day in the middle of the third week" + +[be71dcc6-00d2-4b53-a369-cbfae55b312f] +description = "when third Sunday is some day in the middle of the third week" + +[b7d2da84-4290-4ee6-a618-ee124ae78be7] +description = "when third Sunday is the 21st, the last day of the third week" + +[4276dc06-a1bd-4fc2-b6c2-625fee90bc88] +description = "when fourth Monday is some day in the middle of the fourth week" + +[ddbd7976-2deb-4250-8a38-925ac1a8e9a2] +description = "when fourth Monday is the 22nd, the first day of the fourth week" + +[eb714ef4-1656-47cc-913c-844dba4ebddd] +description = "when fourth Tuesday is the 28th, the last day of the fourth week" + +[16648435-7937-4d2d-b118-c3e38fd084bd] +description = "when fourth Tuesday is some day in the middle of the fourth week" + +[de062bdc-9484-437a-a8c5-5253c6f6785a] +description = "when fourth Wednesday is some day in the middle of the fourth week" + +[c2ce6821-169c-4832-8d37-690ef5d9514a] +description = "when fourth Wednesday is the 28th, the last day of the fourth week" + +[d462c631-2894-4391-a8e3-dbb98b7a7303] +description = "when fourth Thursday is some day in the middle of the fourth week" + +[9ff1f7b6-1b72-427d-9ee9-82b5bb08b835] +description = "when fourth Thursday is another day in the middle of the fourth week" + +[83bae8ba-1c49-49bc-b632-b7c7e1d7e35f] +description = "when fourth Friday is the 22nd, the first day of the fourth week" + +[de752d2a-a95e-48d2-835b-93363dac3710] +description = "when fourth Friday is some day in the middle of the fourth week" + +[eedd90ad-d581-45db-8312-4c6dcf9cf560] +description = "when fourth Saturday is some day in the middle of the fourth week" + +[669fedcd-912e-48c7-a0a1-228b34af91d0] +description = "when fourth Saturday is another day in the middle of the fourth week" + +[648e3849-ea49-44a5-a8a3-9f2a43b3bf1b] +description = "when fourth Sunday is some day in the middle of the fourth week" + +[f81321b3-99ab-4db6-9267-69c5da5a7823] +description = "when fourth Sunday is the 28th, the last day of the fourth week" + +[1af5e51f-5488-4548-aee8-11d7d4a730dc] +description = "last Monday in a month with four Mondays" + +[f29999f2-235e-4ec7-9dab-26f137146526] +description = "last Monday in a month with five Mondays" + +[31b097a0-508e-48ac-bf8a-f63cdcf6dc41] +description = "last Tuesday in a month with four Tuesdays" + +[8c022150-0bb5-4a1f-80f9-88b2e2abcba4] +description = "last Tuesday in another month with four Tuesdays" + +[0e762194-672a-4bdf-8a37-1e59fdacef12] +description = "last Wednesday in a month with five Wednesdays" + +[5016386a-f24e-4bd7-b439-95358f491b66] +description = "last Wednesday in a month with four Wednesdays" + +[12ead1a5-cdf9-4192-9a56-2229e93dd149] +description = "last Thursday in a month with four Thursdays" + +[7db89e11-7fbe-4e57-ae3c-0f327fbd7cc7] +description = "last Thursday in a month with five Thursdays" + +[e47a739e-b979-460d-9c8a-75c35ca2290b] +description = "last Friday in a month with five Fridays" + +[5bed5aa9-a57a-4e5d-8997-2cc796a5b0ec] +description = "last Friday in a month with four Fridays" + +[61e54cba-76f3-4772-a2b1-bf443fda2137] +description = "last Saturday in a month with four Saturdays" + +[8b6a737b-2fa9-444c-b1a2-80ce7a2ec72f] +description = "last Saturday in another month with four Saturdays" + +[0b63e682-f429-4d19-9809-4a45bd0242dc] +description = "last Sunday in a month with five Sundays" + +[5232307e-d3e3-4afc-8ba6-4084ad987c00] +description = "last Sunday in a month with four Sundays" + +[0bbd48e8-9773-4e81-8e71-b9a51711e3c5] +description = "when last Wednesday in February in a leap year is the 29th" + +[fe0936de-7eee-4a48-88dd-66c07ab1fefc] +description = "last Wednesday in December that is also the last day of the year" + +[2ccf2488-aafc-4671-a24e-2b6effe1b0e2] +description = "when last Sunday in February in a non-leap year is not the 29th" + +[00c3ce9f-cf36-4b70-90d8-92b32be6830e] +description = "when first Friday is the 7th, the last day of the first week" diff --git a/exercises/practice/meetup/exercism-tools/exercism-tools.factor b/exercises/practice/meetup/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/practice/meetup/exercism-tools/exercism-tools.factor @@ -0,0 +1,37 @@ +USING: accessors command-line continuations debugger io kernel + lexer namespaces sequences source-files.errors.debugger + system tools.test vocabs vocabs.loader ; +IN: exercism-tools + +SYNTAX: STOP-HERE + lexer get [ text>> length ] keep line<< ; + +SYNTAX: TASK: + lexer get next-line ; + +! Label the test that follows with its description. The marker lets the +! wrapper strip this line from captured output and attach it to the next +! test as a name, rather than leaving it in the previous test's output. +: description ( str -- ) + "###DESC### " write print ; + +! Print one failure block in a stable, parser-friendly form. Bracketed by +! markers so a wrapper can split the stream reliably and avoid Factor's +! noisy callstack output (which is interleaved with subsequent failures). +:: print-failure ( failure -- ) + "###FAIL_BEGIN###" print + failure error-location print + failure error>> [ error. ] [ 2drop ] recover + "###FAIL_END###" print + flush ; + +: print-failures ( -- ) + test-failures get [ print-failure ] each ; + +: run-exercism-tests ( -- ) + command-line get first + [ require ] [ test ] bi + test-failures get empty? + [ 0 exit ] [ print-failures 1 exit ] if ; + +MAIN: run-exercism-tests diff --git a/exercises/practice/meetup/meetup/meetup-tests.factor b/exercises/practice/meetup/meetup/meetup-tests.factor new file mode 100644 index 0000000..866ba8c --- /dev/null +++ b/exercises/practice/meetup/meetup/meetup-tests.factor @@ -0,0 +1,289 @@ +USING: calendar exercism-tools io kernel meetup tools.test unicode ; +IN: meetup.tests + +"when teenth Monday is the 13th, the first day of the teenth week" description +{ t } [ 2013 5 "teenth" "Monday" meetup 2013 5 13 = ] unit-test + +STOP-HERE + +"when teenth Monday is the 19th, the last day of the teenth week" description +{ t } [ 2013 8 "teenth" "Monday" meetup 2013 8 19 = ] unit-test + +"when teenth Monday is some day in the middle of the teenth week" description +{ t } [ 2013 9 "teenth" "Monday" meetup 2013 9 16 = ] unit-test + +"when teenth Tuesday is the 19th, the last day of the teenth week" description +{ t } [ 2013 3 "teenth" "Tuesday" meetup 2013 3 19 = ] unit-test + +"when teenth Tuesday is some day in the middle of the teenth week" description +{ t } [ 2013 4 "teenth" "Tuesday" meetup 2013 4 16 = ] unit-test + +"when teenth Tuesday is the 13th, the first day of the teenth week" description +{ t } [ 2013 8 "teenth" "Tuesday" meetup 2013 8 13 = ] unit-test + +"when teenth Wednesday is some day in the middle of the teenth week" description +{ t } [ 2013 1 "teenth" "Wednesday" meetup 2013 1 16 = ] unit-test + +"when teenth Wednesday is the 13th, the first day of the teenth week" description +{ t } [ 2013 2 "teenth" "Wednesday" meetup 2013 2 13 = ] unit-test + +"when teenth Wednesday is the 19th, the last day of the teenth week" description +{ t } [ 2013 6 "teenth" "Wednesday" meetup 2013 6 19 = ] unit-test + +"when teenth Thursday is some day in the middle of the teenth week" description +{ t } [ 2013 5 "teenth" "Thursday" meetup 2013 5 16 = ] unit-test + +"when teenth Thursday is the 13th, the first day of the teenth week" description +{ t } [ 2013 6 "teenth" "Thursday" meetup 2013 6 13 = ] unit-test + +"when teenth Thursday is the 19th, the last day of the teenth week" description +{ t } [ 2013 9 "teenth" "Thursday" meetup 2013 9 19 = ] unit-test + +"when teenth Friday is the 19th, the last day of the teenth week" description +{ t } [ 2013 4 "teenth" "Friday" meetup 2013 4 19 = ] unit-test + +"when teenth Friday is some day in the middle of the teenth week" description +{ t } [ 2013 8 "teenth" "Friday" meetup 2013 8 16 = ] unit-test + +"when teenth Friday is the 13th, the first day of the teenth week" description +{ t } [ 2013 9 "teenth" "Friday" meetup 2013 9 13 = ] unit-test + +"when teenth Saturday is some day in the middle of the teenth week" description +{ t } [ 2013 2 "teenth" "Saturday" meetup 2013 2 16 = ] unit-test + +"when teenth Saturday is the 13th, the first day of the teenth week" description +{ t } [ 2013 4 "teenth" "Saturday" meetup 2013 4 13 = ] unit-test + +"when teenth Saturday is the 19th, the last day of the teenth week" description +{ t } [ 2013 10 "teenth" "Saturday" meetup 2013 10 19 = ] unit-test + +"when teenth Sunday is the 19th, the last day of the teenth week" description +{ t } [ 2013 5 "teenth" "Sunday" meetup 2013 5 19 = ] unit-test + +"when teenth Sunday is some day in the middle of the teenth week" description +{ t } [ 2013 6 "teenth" "Sunday" meetup 2013 6 16 = ] unit-test + +"when teenth Sunday is the 13th, the first day of the teenth week" description +{ t } [ 2013 10 "teenth" "Sunday" meetup 2013 10 13 = ] unit-test + +"when first Monday is some day in the middle of the first week" description +{ t } [ 2013 3 "first" "Monday" meetup 2013 3 4 = ] unit-test + +"when first Monday is the 1st, the first day of the first week" description +{ t } [ 2013 4 "first" "Monday" meetup 2013 4 1 = ] unit-test + +"when first Tuesday is the 7th, the last day of the first week" description +{ t } [ 2013 5 "first" "Tuesday" meetup 2013 5 7 = ] unit-test + +"when first Tuesday is some day in the middle of the first week" description +{ t } [ 2013 6 "first" "Tuesday" meetup 2013 6 4 = ] unit-test + +"when first Wednesday is some day in the middle of the first week" description +{ t } [ 2013 7 "first" "Wednesday" meetup 2013 7 3 = ] unit-test + +"when first Wednesday is the 7th, the last day of the first week" description +{ t } [ 2013 8 "first" "Wednesday" meetup 2013 8 7 = ] unit-test + +"when first Thursday is some day in the middle of the first week" description +{ t } [ 2013 9 "first" "Thursday" meetup 2013 9 5 = ] unit-test + +"when first Thursday is another day in the middle of the first week" description +{ t } [ 2013 10 "first" "Thursday" meetup 2013 10 3 = ] unit-test + +"when first Friday is the 1st, the first day of the first week" description +{ t } [ 2013 11 "first" "Friday" meetup 2013 11 1 = ] unit-test + +"when first Friday is some day in the middle of the first week" description +{ t } [ 2013 12 "first" "Friday" meetup 2013 12 6 = ] unit-test + +"when first Saturday is some day in the middle of the first week" description +{ t } [ 2013 1 "first" "Saturday" meetup 2013 1 5 = ] unit-test + +"when first Saturday is another day in the middle of the first week" description +{ t } [ 2013 2 "first" "Saturday" meetup 2013 2 2 = ] unit-test + +"when first Sunday is some day in the middle of the first week" description +{ t } [ 2013 3 "first" "Sunday" meetup 2013 3 3 = ] unit-test + +"when first Sunday is the 7th, the last day of the first week" description +{ t } [ 2013 4 "first" "Sunday" meetup 2013 4 7 = ] unit-test + +"when second Monday is some day in the middle of the second week" description +{ t } [ 2013 3 "second" "Monday" meetup 2013 3 11 = ] unit-test + +"when second Monday is the 8th, the first day of the second week" description +{ t } [ 2013 4 "second" "Monday" meetup 2013 4 8 = ] unit-test + +"when second Tuesday is the 14th, the last day of the second week" description +{ t } [ 2013 5 "second" "Tuesday" meetup 2013 5 14 = ] unit-test + +"when second Tuesday is some day in the middle of the second week" description +{ t } [ 2013 6 "second" "Tuesday" meetup 2013 6 11 = ] unit-test + +"when second Wednesday is some day in the middle of the second week" description +{ t } [ 2013 7 "second" "Wednesday" meetup 2013 7 10 = ] unit-test + +"when second Wednesday is the 14th, the last day of the second week" description +{ t } [ 2013 8 "second" "Wednesday" meetup 2013 8 14 = ] unit-test + +"when second Thursday is some day in the middle of the second week" description +{ t } [ 2013 9 "second" "Thursday" meetup 2013 9 12 = ] unit-test + +"when second Thursday is another day in the middle of the second week" description +{ t } [ 2013 10 "second" "Thursday" meetup 2013 10 10 = ] unit-test + +"when second Friday is the 8th, the first day of the second week" description +{ t } [ 2013 11 "second" "Friday" meetup 2013 11 8 = ] unit-test + +"when second Friday is some day in the middle of the second week" description +{ t } [ 2013 12 "second" "Friday" meetup 2013 12 13 = ] unit-test + +"when second Saturday is some day in the middle of the second week" description +{ t } [ 2013 1 "second" "Saturday" meetup 2013 1 12 = ] unit-test + +"when second Saturday is another day in the middle of the second week" description +{ t } [ 2013 2 "second" "Saturday" meetup 2013 2 9 = ] unit-test + +"when second Sunday is some day in the middle of the second week" description +{ t } [ 2013 3 "second" "Sunday" meetup 2013 3 10 = ] unit-test + +"when second Sunday is the 14th, the last day of the second week" description +{ t } [ 2013 4 "second" "Sunday" meetup 2013 4 14 = ] unit-test + +"when third Monday is some day in the middle of the third week" description +{ t } [ 2013 3 "third" "Monday" meetup 2013 3 18 = ] unit-test + +"when third Monday is the 15th, the first day of the third week" description +{ t } [ 2013 4 "third" "Monday" meetup 2013 4 15 = ] unit-test + +"when third Tuesday is the 21st, the last day of the third week" description +{ t } [ 2013 5 "third" "Tuesday" meetup 2013 5 21 = ] unit-test + +"when third Tuesday is some day in the middle of the third week" description +{ t } [ 2013 6 "third" "Tuesday" meetup 2013 6 18 = ] unit-test + +"when third Wednesday is some day in the middle of the third week" description +{ t } [ 2013 7 "third" "Wednesday" meetup 2013 7 17 = ] unit-test + +"when third Wednesday is the 21st, the last day of the third week" description +{ t } [ 2013 8 "third" "Wednesday" meetup 2013 8 21 = ] unit-test + +"when third Thursday is some day in the middle of the third week" description +{ t } [ 2013 9 "third" "Thursday" meetup 2013 9 19 = ] unit-test + +"when third Thursday is another day in the middle of the third week" description +{ t } [ 2013 10 "third" "Thursday" meetup 2013 10 17 = ] unit-test + +"when third Friday is the 15th, the first day of the third week" description +{ t } [ 2013 11 "third" "Friday" meetup 2013 11 15 = ] unit-test + +"when third Friday is some day in the middle of the third week" description +{ t } [ 2013 12 "third" "Friday" meetup 2013 12 20 = ] unit-test + +"when third Saturday is some day in the middle of the third week" description +{ t } [ 2013 1 "third" "Saturday" meetup 2013 1 19 = ] unit-test + +"when third Saturday is another day in the middle of the third week" description +{ t } [ 2013 2 "third" "Saturday" meetup 2013 2 16 = ] unit-test + +"when third Sunday is some day in the middle of the third week" description +{ t } [ 2013 3 "third" "Sunday" meetup 2013 3 17 = ] unit-test + +"when third Sunday is the 21st, the last day of the third week" description +{ t } [ 2013 4 "third" "Sunday" meetup 2013 4 21 = ] unit-test + +"when fourth Monday is some day in the middle of the fourth week" description +{ t } [ 2013 3 "fourth" "Monday" meetup 2013 3 25 = ] unit-test + +"when fourth Monday is the 22nd, the first day of the fourth week" description +{ t } [ 2013 4 "fourth" "Monday" meetup 2013 4 22 = ] unit-test + +"when fourth Tuesday is the 28th, the last day of the fourth week" description +{ t } [ 2013 5 "fourth" "Tuesday" meetup 2013 5 28 = ] unit-test + +"when fourth Tuesday is some day in the middle of the fourth week" description +{ t } [ 2013 6 "fourth" "Tuesday" meetup 2013 6 25 = ] unit-test + +"when fourth Wednesday is some day in the middle of the fourth week" description +{ t } [ 2013 7 "fourth" "Wednesday" meetup 2013 7 24 = ] unit-test + +"when fourth Wednesday is the 28th, the last day of the fourth week" description +{ t } [ 2013 8 "fourth" "Wednesday" meetup 2013 8 28 = ] unit-test + +"when fourth Thursday is some day in the middle of the fourth week" description +{ t } [ 2013 9 "fourth" "Thursday" meetup 2013 9 26 = ] unit-test + +"when fourth Thursday is another day in the middle of the fourth week" description +{ t } [ 2013 10 "fourth" "Thursday" meetup 2013 10 24 = ] unit-test + +"when fourth Friday is the 22nd, the first day of the fourth week" description +{ t } [ 2013 11 "fourth" "Friday" meetup 2013 11 22 = ] unit-test + +"when fourth Friday is some day in the middle of the fourth week" description +{ t } [ 2013 12 "fourth" "Friday" meetup 2013 12 27 = ] unit-test + +"when fourth Saturday is some day in the middle of the fourth week" description +{ t } [ 2013 1 "fourth" "Saturday" meetup 2013 1 26 = ] unit-test + +"when fourth Saturday is another day in the middle of the fourth week" description +{ t } [ 2013 2 "fourth" "Saturday" meetup 2013 2 23 = ] unit-test + +"when fourth Sunday is some day in the middle of the fourth week" description +{ t } [ 2013 3 "fourth" "Sunday" meetup 2013 3 24 = ] unit-test + +"when fourth Sunday is the 28th, the last day of the fourth week" description +{ t } [ 2013 4 "fourth" "Sunday" meetup 2013 4 28 = ] unit-test + +"last Monday in a month with four Mondays" description +{ t } [ 2013 3 "last" "Monday" meetup 2013 3 25 = ] unit-test + +"last Monday in a month with five Mondays" description +{ t } [ 2013 4 "last" "Monday" meetup 2013 4 29 = ] unit-test + +"last Tuesday in a month with four Tuesdays" description +{ t } [ 2013 5 "last" "Tuesday" meetup 2013 5 28 = ] unit-test + +"last Tuesday in another month with four Tuesdays" description +{ t } [ 2013 6 "last" "Tuesday" meetup 2013 6 25 = ] unit-test + +"last Wednesday in a month with five Wednesdays" description +{ t } [ 2013 7 "last" "Wednesday" meetup 2013 7 31 = ] unit-test + +"last Wednesday in a month with four Wednesdays" description +{ t } [ 2013 8 "last" "Wednesday" meetup 2013 8 28 = ] unit-test + +"last Thursday in a month with four Thursdays" description +{ t } [ 2013 9 "last" "Thursday" meetup 2013 9 26 = ] unit-test + +"last Thursday in a month with five Thursdays" description +{ t } [ 2013 10 "last" "Thursday" meetup 2013 10 31 = ] unit-test + +"last Friday in a month with five Fridays" description +{ t } [ 2013 11 "last" "Friday" meetup 2013 11 29 = ] unit-test + +"last Friday in a month with four Fridays" description +{ t } [ 2013 12 "last" "Friday" meetup 2013 12 27 = ] unit-test + +"last Saturday in a month with four Saturdays" description +{ t } [ 2013 1 "last" "Saturday" meetup 2013 1 26 = ] unit-test + +"last Saturday in another month with four Saturdays" description +{ t } [ 2013 2 "last" "Saturday" meetup 2013 2 23 = ] unit-test + +"last Sunday in a month with five Sundays" description +{ t } [ 2013 3 "last" "Sunday" meetup 2013 3 31 = ] unit-test + +"last Sunday in a month with four Sundays" description +{ t } [ 2013 4 "last" "Sunday" meetup 2013 4 28 = ] unit-test + +"when last Wednesday in February in a leap year is the 29th" description +{ t } [ 2012 2 "last" "Wednesday" meetup 2012 2 29 = ] unit-test + +"last Wednesday in December that is also the last day of the year" description +{ t } [ 2014 12 "last" "Wednesday" meetup 2014 12 31 = ] unit-test + +"when last Sunday in February in a non-leap year is not the 29th" description +{ t } [ 2015 2 "last" "Sunday" meetup 2015 2 22 = ] unit-test + +"when first Friday is the 7th, the last day of the first week" description +{ t } [ 2012 12 "first" "Friday" meetup 2012 12 7 = ] unit-test diff --git a/exercises/practice/meetup/meetup/meetup.factor b/exercises/practice/meetup/meetup/meetup.factor new file mode 100644 index 0000000..473a057 --- /dev/null +++ b/exercises/practice/meetup/meetup/meetup.factor @@ -0,0 +1,5 @@ +USING: kernel ; +IN: meetup + +: meetup ( year month week dayofweek -- timestamp ) + "unimplemented" throw ; diff --git a/exercises/practice/robot-name/.docs/instructions.md b/exercises/practice/robot-name/.docs/instructions.md new file mode 100644 index 0000000..fca3a41 --- /dev/null +++ b/exercises/practice/robot-name/.docs/instructions.md @@ -0,0 +1,14 @@ +# Instructions + +Manage robot factory settings. + +When a robot comes off the factory floor, it has no name. + +The first time you turn on a robot, a random name is generated in the format of two uppercase letters followed by three digits, such as RX837 or BC811. + +Every once in a while we need to reset a robot to its factory settings, which means that its name gets wiped. +The next time you ask, that robot will respond with a new random name. + +The names must be random: they should not follow a predictable sequence. +Using random names means a risk of collisions. +Your solution must ensure that every existing robot has a unique name. diff --git a/exercises/practice/robot-name/.meta/config.json b/exercises/practice/robot-name/.meta/config.json new file mode 100644 index 0000000..f53883e --- /dev/null +++ b/exercises/practice/robot-name/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "robot-name/robot-name.factor" + ], + "test": [ + "robot-name/robot-name-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Manage robot factory settings.", + "source": "A debugging session with Paul Blackwell at gSchool." +} diff --git a/exercises/practice/robot-name/.meta/example.factor b/exercises/practice/robot-name/.meta/example.factor new file mode 100644 index 0000000..c219c60 --- /dev/null +++ b/exercises/practice/robot-name/.meta/example.factor @@ -0,0 +1,29 @@ +USING: accessors hash-sets kernel math namespaces random +sequences sets strings ; +IN: robot-name + +TUPLE: robot { name string } ; + +SYMBOL: names-issued +HS{ } clone names-issued set-global + +: random-letter ( -- ch ) 26 random CHAR: A + ; +: random-digit ( -- ch ) 10 random CHAR: 0 + ; + +: random-name ( -- name ) + 2 [ random-letter ] replicate + 3 [ random-digit ] replicate + append >string ; + +: fresh-name ( -- name ) + random-name dup names-issued get-global in? + [ drop fresh-name ] when ; + +: claim-name ( name -- name ) + dup names-issued get-global adjoin ; + +: ( -- robot ) + fresh-name claim-name robot boa ; + +: reset-name ( robot -- ) + fresh-name claim-name >>name drop ; diff --git a/exercises/practice/robot-name/exercism-tools/exercism-tools.factor b/exercises/practice/robot-name/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/practice/robot-name/exercism-tools/exercism-tools.factor @@ -0,0 +1,37 @@ +USING: accessors command-line continuations debugger io kernel + lexer namespaces sequences source-files.errors.debugger + system tools.test vocabs vocabs.loader ; +IN: exercism-tools + +SYNTAX: STOP-HERE + lexer get [ text>> length ] keep line<< ; + +SYNTAX: TASK: + lexer get next-line ; + +! Label the test that follows with its description. The marker lets the +! wrapper strip this line from captured output and attach it to the next +! test as a name, rather than leaving it in the previous test's output. +: description ( str -- ) + "###DESC### " write print ; + +! Print one failure block in a stable, parser-friendly form. Bracketed by +! markers so a wrapper can split the stream reliably and avoid Factor's +! noisy callstack output (which is interleaved with subsequent failures). +:: print-failure ( failure -- ) + "###FAIL_BEGIN###" print + failure error-location print + failure error>> [ error. ] [ 2drop ] recover + "###FAIL_END###" print + flush ; + +: print-failures ( -- ) + test-failures get [ print-failure ] each ; + +: run-exercism-tests ( -- ) + command-line get first + [ require ] [ test ] bi + test-failures get empty? + [ 0 exit ] [ print-failures 1 exit ] if ; + +MAIN: run-exercism-tests diff --git a/exercises/practice/robot-name/robot-name/robot-name-tests.factor b/exercises/practice/robot-name/robot-name/robot-name-tests.factor new file mode 100644 index 0000000..5ac0188 --- /dev/null +++ b/exercises/practice/robot-name/robot-name/robot-name-tests.factor @@ -0,0 +1,111 @@ +USING: accessors exercism-tools io kernel locals math +math.statistics regexp robot-name sequences sets tools.test ; +IN: robot-name.tests + +"Robot Name:" print + +: valid-name? ( str -- ? ) + R/ [A-Z]{2}\d{3}/ matches? ; + +"name matches the [A-Z]{2}\\d{3} format" description +{ t } [ + name>> valid-name? +] unit-test + +STOP-HERE + +"name is stable across repeated lookups" description +{ t } [ + [ name>> ] [ name>> ] bi = +] unit-test + +"two robots have different names" description +{ f } [ + name>> + name>> + = +] unit-test + +"reset gives a name in the right format" description +{ t } [ + dup reset-name name>> valid-name? +] unit-test + +"reset replaces the previous name" description +{ f } [ + [let :> r + r name>> + r reset-name + r name>> + = + ] +] unit-test + +! ---------------------------------------------------------------- +! Distribution checks over 260 freshly generated names. +! +! 260 = 26 * 10, so the expected count per bin is 10 for the two +! letter positions (26 bins) and 26 for the three digit positions +! (10 bins). We run a chi-squared test on each position against a +! uniform null hypothesis. Critical thresholds are chosen high +! enough that a truly uniform generator essentially never fails: +! - letters (df = 25): cutoff 80 (well above the chi-sq 10^-6 tail) +! - digits (df = 9): cutoff 50 +! +! The per-position tests catch a generator whose values at any one +! position aren't uniform. They cannot catch a deterministic +! generator that cycles through values so each position is exactly +! uniform on its own. To catch that, we also test independence +! between positions by reducing each character to one bit (its +! parity) and chi-squaring the joint 5-bit distribution against +! uniform over 32 cells (df = 31, expected ~8.125 per cell). +! +! Both alphabets have an even number of symbols, so for any +! cycling-counter generator the parity of position p is a function +! of the underlying counter i. All five parities collapse to a +! function of i, the joint distribution piles onto a small subset +! of the 32 cells, and the chi-squared statistic blows up. The +! cutoff of 100 is well above the chi-sq 10^-9 tail for df = 31. +! ---------------------------------------------------------------- + +:: position-counts ( names i alphabet -- counts ) + alphabet [| ch | names [ i swap nth ch = ] count ] map ; + +:: chi-squared-uniform ( counts expected -- chi2 ) + counts [ expected - sq expected /f ] map-sum ; + +:: chi-squared-position ( names i alphabet -- chi2 ) + names i alphabet position-counts + names length alphabet length /f + chi-squared-uniform ; + +: letter-alphabet ( -- seq ) + 26 [ CHAR: A + ] map ; + +: digit-alphabet ( -- seq ) + 10 [ CHAR: 0 + ] map ; + +: name-parity ( name -- n ) + 0 [ 2 mod swap 2 * + ] reduce ; + +:: parity-counts ( names -- counts ) + 32 [| k | names [ name-parity k = ] count ] map ; + +:: chi-squared-parity ( names -- chi2 ) + names parity-counts + names length 32 /f + chi-squared-uniform ; + +"260 fresh robot names: all valid, all distinct, each position uniform, positions jointly independent" description +{ t 260 t t t t t t } [ + [let 260 [ name>> ] replicate :> names + names [ valid-name? ] all? + names members length + names 0 letter-alphabet chi-squared-position 80 < + names 1 letter-alphabet chi-squared-position 80 < + names 2 digit-alphabet chi-squared-position 50 < + names 3 digit-alphabet chi-squared-position 50 < + names 4 digit-alphabet chi-squared-position 50 < + names chi-squared-parity 100 < + ] +] unit-test diff --git a/exercises/practice/robot-name/robot-name/robot-name.factor b/exercises/practice/robot-name/robot-name/robot-name.factor new file mode 100644 index 0000000..fc3b177 --- /dev/null +++ b/exercises/practice/robot-name/robot-name/robot-name.factor @@ -0,0 +1,10 @@ +USING: kernel ; +IN: robot-name + +TUPLE: robot name ; + +: ( -- robot ) + "unimplemented" throw ; + +: reset-name ( robot -- ) + "unimplemented" throw ;