diff --git a/concepts/assocs/introduction.md b/concepts/assocs/introduction.md index 80aaf59..13dbbe3 100644 --- a/concepts/assocs/introduction.md +++ b/concepts/assocs/introduction.md @@ -20,6 +20,8 @@ set-at ( value key assoc -- ) delete-at ( key assoc -- ) inc-at ( key assoc -- ) change-at ( key assoc quot -- ) +keys ( assoc -- keys ) +values ( assoc -- values ) >alist ( assoc -- alist ) ``` diff --git a/config.json b/config.json index feba064..ad2ee47 100644 --- a/config.json +++ b/config.json @@ -1011,18 +1011,6 @@ ], "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", @@ -1053,6 +1041,20 @@ ], "difficulty": 3 }, + { + "slug": "series", + "name": "Series", + "uuid": "3bb91e4a-d234-40e9-b56c-fb06a9d84985", + "practices": [], + "prerequisites": [ + "strings", + "sequences", + "combinators", + "locals", + "errors" + ], + "difficulty": 3 + }, { "slug": "sum-of-multiples", "name": "Sum of Multiples", @@ -1147,6 +1149,19 @@ ], "difficulty": 4 }, + { + "slug": "diamond", + "name": "Diamond", + "uuid": "3da8114d-a1d4-4b45-9cb6-30bbef6624d3", + "practices": [], + "prerequisites": [ + "strings", + "tabulation", + "higher-order-sequences", + "locals" + ], + "difficulty": 4 + }, { "slug": "flatten-array", "name": "Flatten Array", @@ -1162,6 +1177,21 @@ ], "difficulty": 4 }, + { + "slug": "grade-school", + "name": "Grade School", + "uuid": "fb1fe9a5-d2ce-4e44-850f-7c0518e0422c", + "practices": [], + "prerequisites": [ + "tuples", + "mutation", + "assocs", + "higher-order-sequences", + "curry-compose-fry", + "locals" + ], + "difficulty": 4 + }, { "slug": "kindergarten-garden", "name": "Kindergarten Garden", @@ -1273,6 +1303,20 @@ ], "difficulty": 4 }, + { + "slug": "roman-numerals", + "name": "Roman Numerals", + "uuid": "31e438b9-e2c9-4d35-9b3f-dc8a97dcb787", + "practices": [], + "prerequisites": [ + "numbers", + "tabulation", + "higher-order-sequences", + "arrays", + "locals" + ], + "difficulty": 4 + }, { "slug": "sieve", "name": "Sieve", @@ -1498,6 +1542,18 @@ ], "difficulty": 5 }, + { + "slug": "robot-name", + "name": "Robot Name", + "uuid": "1f3cded8-c357-448d-9640-63e8e6ea8efe", + "practices": [], + "prerequisites": [ + "dynamic-variables", + "randomness", + "hash-sets" + ], + "difficulty": 5 + }, { "slug": "run-length-encoding", "name": "Run-Length Encoding", @@ -1629,6 +1685,19 @@ ], "difficulty": 6 }, + { + "slug": "flower-field", + "name": "Flower Field", + "uuid": "d635beb6-bc14-4846-909c-b6216c3f4d41", + "practices": [], + "prerequisites": [ + "strings", + "tabulation", + "higher-order-sequences", + "locals" + ], + "difficulty": 6 + }, { "slug": "food-chain", "name": "Food Chain", @@ -1643,6 +1712,23 @@ ], "difficulty": 6 }, + { + "slug": "intergalactic-transmission", + "name": "Intergalactic Transmission", + "uuid": "e2ce391c-7d7e-4442-aea6-71f310206ace", + "practices": [], + "prerequisites": [ + "bitwise-operations", + "arrays", + "windows", + "reductions", + "higher-order-sequences", + "combinators", + "strings", + "errors" + ], + "difficulty": 6 + }, { "slug": "ocr-numbers", "name": "OCR Numbers", @@ -1672,6 +1758,22 @@ ], "difficulty": 6 }, + { + "slug": "piecing-it-together", + "name": "Piecing It Together", + "uuid": "46be0bec-ed8e-4d32-9640-556acdbb5221", + "practices": [], + "prerequisites": [ + "assocs", + "combinators", + "arrays", + "higher-order-sequences", + "booleans", + "locals", + "errors" + ], + "difficulty": 6 + }, { "slug": "rail-fence-cipher", "name": "Rail Fence Cipher", diff --git a/exercises/concept/boardwalk-games/.docs/introduction.md b/exercises/concept/boardwalk-games/.docs/introduction.md index b227143..a5f4001 100644 --- a/exercises/concept/boardwalk-games/.docs/introduction.md +++ b/exercises/concept/boardwalk-games/.docs/introduction.md @@ -100,8 +100,8 @@ USING: random random.mersenne-twister ; 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. +same outcome. A word that wraps `with-random` should be marked `inline` +so the quotation's result can flow back 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/pursers-pantry/.docs/introduction.md b/exercises/concept/pursers-pantry/.docs/introduction.md index a8580be..7634d08 100644 --- a/exercises/concept/pursers-pantry/.docs/introduction.md +++ b/exercises/concept/pursers-pantry/.docs/introduction.md @@ -93,11 +93,27 @@ that on every iteration `each` only needs to supply the key. `keep` runs the quotation while preserving the hashtable for the final `.`. -## Converting back to a sequence +## Keys, values, and pairs -`>alist` (in [`assocs`][assocs]) returns a sequence of `{ key value }` -pairs. `sort-keys` (in [`sorting`][sorting]) returns the same -sequence sorted by key. +`keys` and `values` (in [`assocs`][assocs]) return just the keys or +just the values; `>alist` returns the `{ key value }` pairs. + +``` +keys ( assoc -- keys ) +values ( assoc -- values ) +>alist ( assoc -- alist ) +``` + +```factor +H{ { "wood" 11 } { "coal" 7 } } keys . ! the keys (order not guaranteed) +H{ { "wood" 11 } { "coal" 7 } } values . ! the matching values +``` + +`keys` and `values` line up: the value at a given position belongs to +the key at the same position. + +`sort-keys` (in [`sorting`][sorting]) returns the `{ key value }` pairs +sorted by key: ```factor H{ { "wood" 11 } { "coal" 7 } } sort-keys . diff --git a/exercises/practice/diamond/.docs/instructions.md b/exercises/practice/diamond/.docs/instructions.md new file mode 100644 index 0000000..3034802 --- /dev/null +++ b/exercises/practice/diamond/.docs/instructions.md @@ -0,0 +1,52 @@ +# Instructions + +The diamond kata takes as its input a letter, and outputs it in a diamond shape. +Given a letter, it prints a diamond starting with 'A', with the supplied letter at the widest point. + +## Requirements + +- The first row contains one 'A'. +- The last row contains one 'A'. +- All rows, except the first and last, have exactly two identical letters. +- All rows have as many trailing spaces as leading spaces. (This might be 0). +- The diamond is horizontally symmetric. +- The diamond is vertically symmetric. +- The diamond has a square shape (width equals height). +- The letters form a diamond shape. +- The top half has the letters in ascending order. +- The bottom half has the letters in descending order. +- The four corners (containing the spaces) are triangles. + +## Examples + +In the following examples, spaces are indicated by `·` characters. + +Diamond for letter 'A': + +```text +A +``` + +Diamond for letter 'C': + +```text +··A·· +·B·B· +C···C +·B·B· +··A·· +``` + +Diamond for letter 'E': + +```text +····A···· +···B·B··· +··C···C·· +·D·····D· +E·······E +·D·····D· +··C···C·· +···B·B··· +····A···· +``` diff --git a/exercises/practice/diamond/.meta/config.json b/exercises/practice/diamond/.meta/config.json new file mode 100644 index 0000000..dedca19 --- /dev/null +++ b/exercises/practice/diamond/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "diamond/diamond.factor" + ], + "test": [ + "diamond/diamond-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Given a letter, print a diamond starting with 'A' with the supplied letter at the widest point.", + "source": "Seb Rose", + "source_url": "https://web.archive.org/web/20220807163751/http://claysnow.co.uk/recycling-tests-in-tdd/" +} diff --git a/exercises/practice/diamond/.meta/example.factor b/exercises/practice/diamond/.meta/example.factor new file mode 100644 index 0000000..b926a55 --- /dev/null +++ b/exercises/practice/diamond/.meta/example.factor @@ -0,0 +1,19 @@ +USING: kernel math sequences strings ; +IN: diamond + + ch + i zero? + [ ch 1string ] + [ ch 1string i 2 * CHAR: space pad-tail ch suffix ] if + n i + 1 + CHAR: space pad-head + n 2 * 1 + CHAR: space pad-tail ; + +PRIVATE> + +:: rows ( letter -- rows ) + letter CHAR: A - :> n + n 1 + [ n swap diamond-row ] map + dup but-last reverse append ; diff --git a/exercises/practice/diamond/.meta/generator.jl b/exercises/practice/diamond/.meta/generator.jl new file mode 100644 index 0000000..a44e3d6 --- /dev/null +++ b/exercises/practice/diamond/.meta/generator.jl @@ -0,0 +1,9 @@ +module Diamond + +function gen_test_case(case) + letter = case["input"]["letter"] + expected = format_string_array(case["expected"]) + return "{ $(expected) }\n[ CHAR: $(letter) rows ] unit-test" +end + +end diff --git a/exercises/practice/diamond/.meta/tests.toml b/exercises/practice/diamond/.meta/tests.toml new file mode 100644 index 0000000..4e7802e --- /dev/null +++ b/exercises/practice/diamond/.meta/tests.toml @@ -0,0 +1,25 @@ +# 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. + +[202fb4cc-6a38-4883-9193-a29d5cb92076] +description = "Degenerate case with a single 'A' row" + +[bd6a6d78-9302-42e9-8f60-ac1461e9abae] +description = "Degenerate case with no row containing 3 distinct groups of spaces" + +[af8efb49-14ed-447f-8944-4cc59ce3fd76] +description = "Smallest non-degenerate case with odd diamond side length" + +[e0c19a95-9888-4d05-86a0-fa81b9e70d1d] +description = "Smallest non-degenerate case with even diamond side length" + +[82ea9aa9-4c0e-442a-b07e-40204e925944] +description = "Largest possible diamond" diff --git a/exercises/practice/diamond/diamond/diamond-tests.factor b/exercises/practice/diamond/diamond/diamond-tests.factor new file mode 100644 index 0000000..7877377 --- /dev/null +++ b/exercises/practice/diamond/diamond/diamond-tests.factor @@ -0,0 +1,24 @@ +USING: diamond exercism-tools io kernel tools.test unicode ; +IN: diamond.tests + +"Degenerate case with a single 'A' row" description +{ { "A" } } +[ CHAR: A rows ] unit-test + +STOP-HERE + +"Degenerate case with no row containing 3 distinct groups of spaces" description +{ { " A " "B B" " A " } } +[ CHAR: B rows ] unit-test + +"Smallest non-degenerate case with odd diamond side length" description +{ { " A " " B B " "C C" " B B " " A " } } +[ CHAR: C rows ] unit-test + +"Smallest non-degenerate case with even diamond side length" description +{ { " A " " B B " " C C " "D D" " C C " " B B " " A " } } +[ CHAR: D rows ] unit-test + +"Largest possible diamond" description +{ { " A " " B B " " C C " " D D " " E E " " F F " " G G " " H H " " I I " " J J " " K K " " L L " " M M " " N N " " O O " " P P " " Q Q " " R R " " S S " " T T " " U U " " V V " " W W " " X X " " Y Y " "Z Z" " Y Y " " X X " " W W " " V V " " U U " " T T " " S S " " R R " " Q Q " " P P " " O O " " N N " " M M " " L L " " K K " " J J " " I I " " H H " " G G " " F F " " E E " " D D " " C C " " B B " " A " } } +[ CHAR: Z rows ] unit-test diff --git a/exercises/practice/diamond/diamond/diamond.factor b/exercises/practice/diamond/diamond/diamond.factor new file mode 100644 index 0000000..fea6103 --- /dev/null +++ b/exercises/practice/diamond/diamond/diamond.factor @@ -0,0 +1,5 @@ +USING: kernel ; +IN: diamond + +: rows ( letter -- rows ) + "unimplemented" throw ; diff --git a/exercises/practice/diamond/exercism-tools/exercism-tools.factor b/exercises/practice/diamond/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/practice/diamond/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/flower-field/.docs/instructions.md b/exercises/practice/flower-field/.docs/instructions.md new file mode 100644 index 0000000..bbdae0c --- /dev/null +++ b/exercises/practice/flower-field/.docs/instructions.md @@ -0,0 +1,26 @@ +# Instructions + +Your task is to add flower counts to empty squares in a completed Flower Field garden. +The garden itself is a rectangle board composed of squares that are either empty (`' '`) or a flower (`'*'`). + +For each empty square, count the number of flowers adjacent to it (horizontally, vertically, diagonally). +If the empty square has no adjacent flowers, leave it empty. +Otherwise replace it with the count of adjacent flowers. + +For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): + +```text +·*·*· +··*·· +··*·· +····· +``` + +Which your code should transform into this: + +```text +1*3*1 +13*31 +·2*2· +·111· +``` diff --git a/exercises/practice/flower-field/.docs/introduction.md b/exercises/practice/flower-field/.docs/introduction.md new file mode 100644 index 0000000..af9b615 --- /dev/null +++ b/exercises/practice/flower-field/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +[Flower Field][history] is a compassionate reimagining of the popular game Minesweeper. +The object of the game is to find all the flowers in the garden using numeric hints that indicate how many flowers are directly adjacent (horizontally, vertically, diagonally) to a square. +"Flower Field" shipped in regional versions of Microsoft Windows in Italy, Germany, South Korea, Japan and Taiwan. + +[history]: https://web.archive.org/web/20020409051321fw_/http://rcm.usr.dsi.unimi.it/rcmweb/fnm/ diff --git a/exercises/practice/flower-field/.meta/config.json b/exercises/practice/flower-field/.meta/config.json new file mode 100644 index 0000000..f777fa6 --- /dev/null +++ b/exercises/practice/flower-field/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "flower-field/flower-field.factor" + ], + "test": [ + "flower-field/flower-field-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Mark all the flowers in a garden." +} diff --git a/exercises/practice/flower-field/.meta/example.factor b/exercises/practice/flower-field/.meta/example.factor new file mode 100644 index 0000000..0c0b70f --- /dev/null +++ b/exercises/practice/flower-field/.meta/example.factor @@ -0,0 +1,33 @@ +USING: kernel locals math ranges sequences ; +IN: flower-field + +:: flower-at? ( garden r c -- ? ) + r 0 >= r garden length < and [ + r garden nth :> row + c 0 >= c row length < and + [ c row nth CHAR: * = ] [ f ] if + ] [ f ] if ; + +:: neighbor-count ( garden r c -- n ) + 0 + r 1 - r 1 + [a..b] [| nr | + c 1 - c 1 + [a..b] [| nc | + nr r = nc c = and not [ + garden nr nc flower-at? [ 1 + ] when + ] when + ] each + ] each ; + +:: annotate-cell ( garden r c -- ch ) + c r garden nth nth :> cell + cell CHAR: * = [ cell ] [ + garden r c neighbor-count :> n + n 0 = [ CHAR: \s ] [ n CHAR: 0 + ] if + ] if ; + +:: annotate ( garden -- annotated ) + garden length [0..b) [| r | + r garden nth length [0..b) [| c | + garden r c annotate-cell + ] "" map-as + ] map ; diff --git a/exercises/practice/flower-field/.meta/generator.jl b/exercises/practice/flower-field/.meta/generator.jl new file mode 100644 index 0000000..938bafe --- /dev/null +++ b/exercises/practice/flower-field/.meta/generator.jl @@ -0,0 +1,11 @@ +module FlowerField + +function gen_test_case(case) + garden = case["input"]["garden"] + expected = case["expected"] + inp = format_string_array(garden) + exp = format_string_array(expected) + return "{ $(exp) }\n[ $(inp) annotate ] unit-test" +end + +end diff --git a/exercises/practice/flower-field/.meta/tests.toml b/exercises/practice/flower-field/.meta/tests.toml new file mode 100644 index 0000000..965ba8f --- /dev/null +++ b/exercises/practice/flower-field/.meta/tests.toml @@ -0,0 +1,49 @@ +# 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. + +[237ff487-467a-47e1-9b01-8a891844f86c] +description = "no rows" + +[4b4134ec-e20f-439c-a295-664c38950ba1] +description = "no columns" + +[d774d054-bbad-4867-88ae-069cbd1c4f92] +description = "no flowers" + +[225176a0-725e-43cd-aa13-9dced501f16e] +description = "garden full of flowers" + +[3f345495-f1a5-4132-8411-74bd7ca08c49] +description = "flower surrounded by spaces" + +[6cb04070-4199-4ef7-a6fa-92f68c660fca] +description = "space surrounded by flowers" + +[272d2306-9f62-44fe-8ab5-6b0f43a26338] +description = "horizontal line" + +[c6f0a4b2-58d0-4bf6-ad8d-ccf4144f1f8e] +description = "horizontal line, flowers at edges" + +[a54e84b7-3b25-44a8-b8cf-1753c8bb4cf5] +description = "vertical line" + +[b40f42f5-dec5-4abc-b167-3f08195189c1] +description = "vertical line, flowers at edges" + +[58674965-7b42-4818-b930-0215062d543c] +description = "cross" + +[dd9d4ca8-9e68-4f78-a677-a2a70fd7a7b8] +description = "large garden" + +[6e4ac13a-3e43-4728-a2e3-3551d4b1a996] +description = "multiple adjacent flowers" diff --git a/exercises/practice/flower-field/exercism-tools/exercism-tools.factor b/exercises/practice/flower-field/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/practice/flower-field/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/flower-field/flower-field/flower-field-tests.factor b/exercises/practice/flower-field/flower-field/flower-field-tests.factor new file mode 100644 index 0000000..98c568b --- /dev/null +++ b/exercises/practice/flower-field/flower-field/flower-field-tests.factor @@ -0,0 +1,56 @@ +USING: exercism-tools flower-field io kernel tools.test unicode ; +IN: flower-field.tests + +"no rows" description +{ { } } +[ { } annotate ] unit-test + +STOP-HERE + +"no columns" description +{ { "" } } +[ { "" } annotate ] unit-test + +"no flowers" description +{ { " " " " " " } } +[ { " " " " " " } annotate ] unit-test + +"garden full of flowers" description +{ { "***" "***" "***" } } +[ { "***" "***" "***" } annotate ] unit-test + +"flower surrounded by spaces" description +{ { "111" "1*1" "111" } } +[ { " " " * " " " } annotate ] unit-test + +"space surrounded by flowers" description +{ { "***" "*8*" "***" } } +[ { "***" "* *" "***" } annotate ] unit-test + +"horizontal line" description +{ { "1*2*1" } } +[ { " * * " } annotate ] unit-test + +"horizontal line, flowers at edges" description +{ { "*1 1*" } } +[ { "* *" } annotate ] unit-test + +"vertical line" description +{ { "1" "*" "2" "*" "1" } } +[ { " " "*" " " "*" " " } annotate ] unit-test + +"vertical line, flowers at edges" description +{ { "*" "1" " " "1" "*" } } +[ { "*" " " " " " " "*" } annotate ] unit-test + +"cross" description +{ { " 2*2 " "25*52" "*****" "25*52" " 2*2 " } } +[ { " * " " * " "*****" " * " " * " } annotate ] unit-test + +"large garden" description +{ { "1*22*1" "12*322" " 123*2" "112*4*" "1*22*2" "111111" } } +[ { " * * " " * " " * " " * *" " * * " " " } annotate ] unit-test + +"multiple adjacent flowers" description +{ { "1**1" } } +[ { " ** " } annotate ] unit-test diff --git a/exercises/practice/flower-field/flower-field/flower-field.factor b/exercises/practice/flower-field/flower-field/flower-field.factor new file mode 100644 index 0000000..6c9395b --- /dev/null +++ b/exercises/practice/flower-field/flower-field/flower-field.factor @@ -0,0 +1,5 @@ +USING: kernel ; +IN: flower-field + +: annotate ( garden -- annotated ) + "unimplemented" throw ; diff --git a/exercises/practice/grade-school/.docs/instructions.md b/exercises/practice/grade-school/.docs/instructions.md new file mode 100644 index 0000000..3cb1b5d --- /dev/null +++ b/exercises/practice/grade-school/.docs/instructions.md @@ -0,0 +1,21 @@ +# Instructions + +Given students' names along with the grade they are in, create a roster for the school. + +In the end, you should be able to: + +- Add a student's name to the roster for a grade: + - "Add Jim to grade 2." + - "OK." +- Get a list of all students enrolled in a grade: + - "Which students are in grade 2?" + - "We've only got Jim right now." +- Get a sorted list of all students in all grades. + Grades should be sorted as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name. + - "Who is enrolled in school right now?" + - "Let me think. + We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2, and Jim in grade 5. + So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe, and Jim." + +Note that all our students only have one name (it's a small town, what do you want?), and each student cannot be added more than once to a grade or the roster. +If a test attempts to add the same student more than once, your implementation should indicate that this is incorrect. diff --git a/exercises/practice/grade-school/.meta/config.json b/exercises/practice/grade-school/.meta/config.json new file mode 100644 index 0000000..32d6037 --- /dev/null +++ b/exercises/practice/grade-school/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "grade-school/grade-school.factor" + ], + "test": [ + "grade-school/grade-school-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Given students' names along with the grade that they are in, create a roster for the school.", + "source": "A pairing session with Phil Battos at gSchool" +} diff --git a/exercises/practice/grade-school/.meta/example.factor b/exercises/practice/grade-school/.meta/example.factor new file mode 100644 index 0000000..1394aa7 --- /dev/null +++ b/exercises/practice/grade-school/.meta/example.factor @@ -0,0 +1,24 @@ +USING: accessors assocs kernel locals sequences sorting ; +IN: grade-school + +TUPLE: school students ; + +: ( -- school ) + school new H{ } clone >>students ; + +:: add-student ( school name grade -- ? ) + school students>> :> students + name students key? + [ f ] [ grade name students set-at t ] if ; + +: sorted-pairs ( school -- pairs ) + students>> >alist + [ first ] sort-by + [ second ] sort-by ; + +: roster ( school -- names ) + sorted-pairs keys ; + +: grade ( school n -- names ) + [ students>> >alist ] dip + '[ second _ = ] filter keys sort ; diff --git a/exercises/practice/grade-school/.meta/generator.jl b/exercises/practice/grade-school/.meta/generator.jl new file mode 100644 index 0000000..87e7adf --- /dev/null +++ b/exercises/practice/grade-school/.meta/generator.jl @@ -0,0 +1,48 @@ +module GradeSchool + +const EXTRA_VOCABS = ["locals", "sequences"] +const EXTRA_HEADER = """:: add-results ( students -- bools ) + :> s students [ first2 [ s ] 2dip add-student ] map ; +:: add-all ( students -- school ) + :> s students [ first2 [ s ] 2dip add-student drop ] each s ;""" + +# Render a list of [name, grade] pairs as a Factor literal: +# { { "Aimee" 2 } { "Blair" 2 } } (empty -> { }) +function students_literal(students) + isempty(students) && return "{ }" + pairs = ["{ \"$(s[1])\" $(s[2]) }" for s in students] + return "{ " * join(pairs, " ") * " }" +end + +# Render a list of strings as a Factor literal: { "Anna" "Barb" } (empty -> { }) +function names_literal(names) + isempty(names) && return "{ }" + return "{ " * join(["\"$(n)\"" for n in names], " ") * " }" +end + +# Render a list of booleans as a Factor literal: { t t f } (empty -> { }) +function bools_literal(bools) + isempty(bools) && return "{ }" + return "{ " * join([b ? "t" : "f" for b in bools], " ") * " }" +end + +function gen_test_case(case) + property = case["property"] + students = students_literal(case["input"]["students"]) + + if property == "add" + expected = bools_literal(case["expected"]) + return "{ $(expected) } [ $(students) add-results ] unit-test" + elseif property == "roster" + expected = names_literal(case["expected"]) + return "{ $(expected) } [ $(students) add-all roster ] unit-test" + elseif property == "grade" + expected = names_literal(case["expected"]) + desired = case["input"]["desiredGrade"] + return "{ $(expected) } [ $(students) add-all $(desired) grade ] unit-test" + else + error("unknown property: " * property) + end +end + +end diff --git a/exercises/practice/grade-school/.meta/tests.toml b/exercises/practice/grade-school/.meta/tests.toml new file mode 100644 index 0000000..50c9e2e --- /dev/null +++ b/exercises/practice/grade-school/.meta/tests.toml @@ -0,0 +1,86 @@ +# 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. + +[a3f0fb58-f240-4723-8ddc-e644666b85cc] +description = "Roster is empty when no student is added" + +[9337267f-7793-4b90-9b4a-8e3978408824] +description = "Add a student" + +[6d0a30e4-1b4e-472e-8e20-c41702125667] +description = "Student is added to the roster" + +[73c3ca75-0c16-40d7-82f5-ed8fe17a8e4a] +description = "Adding multiple students in the same grade in the roster" + +[233be705-dd58-4968-889d-fb3c7954c9cc] +description = "Multiple students in the same grade are added to the roster" + +[87c871c1-6bde-4413-9c44-73d59a259d83] +description = "Cannot add student to same grade in the roster more than once" + +[c125dab7-2a53-492f-a99a-56ad511940d8] +description = "A student can't be in two different grades" +include = false + +[a0c7b9b8-0e89-47f8-8b4a-c50f885e79d1] +description = "A student can only be added to the same grade in the roster once" +include = false +reimplements = "c125dab7-2a53-492f-a99a-56ad511940d8" + +[d7982c4f-1602-49f6-a651-620f2614243a] +description = "Student not added to same grade in the roster more than once" +reimplements = "a0c7b9b8-0e89-47f8-8b4a-c50f885e79d1" + +[e70d5d8f-43a9-41fd-94a4-1ea0fa338056] +description = "Adding students in multiple grades" + +[75a51579-d1d7-407c-a2f8-2166e984e8ab] +description = "Students in multiple grades are added to the roster" + +[7df542f1-57ce-433c-b249-ff77028ec479] +description = "Cannot add same student to multiple grades in the roster" + +[6a03b61e-1211-4783-a3cc-fc7f773fba3f] +description = "A student cannot be added to more than one grade in the sorted roster" +include = false +reimplements = "c125dab7-2a53-492f-a99a-56ad511940d8" + +[c7ec1c5e-9ab7-4d3b-be5c-29f2f7a237c5] +description = "Student not added to multiple grades in the roster" +reimplements = "6a03b61e-1211-4783-a3cc-fc7f773fba3f" + +[d9af4f19-1ba1-48e7-94d0-dabda4e5aba6] +description = "Students are sorted by grades in the roster" + +[d9fb5bea-f5aa-4524-9d61-c158d8906807] +description = "Students are sorted by name in the roster" + +[180a8ff9-5b94-43fc-9db1-d46b4a8c93b6] +description = "Students are sorted by grades and then by name in the roster" + +[5e67aa3c-a3c6-4407-a183-d8fe59cd1630] +description = "Grade is empty if no students in the roster" + +[1e0cf06b-26e0-4526-af2d-a2e2df6a51d6] +description = "Grade is empty if no students in that grade" + +[2bfc697c-adf2-4b65-8d0f-c46e085f796e] +description = "Student not added to same grade more than once" + +[66c8e141-68ab-4a04-a15a-c28bc07fe6b9] +description = "Student not added to multiple grades" + +[c9c1fc2f-42e0-4d2c-b361-99271f03eda7] +description = "Student not added to other grade for multiple grades" + +[1bfbcef1-e4a3-49e8-8d22-f6f9f386187e] +description = "Students are sorted by name in a grade" diff --git a/exercises/practice/grade-school/exercism-tools/exercism-tools.factor b/exercises/practice/grade-school/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/practice/grade-school/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/grade-school/grade-school/grade-school-tests.factor b/exercises/practice/grade-school/grade-school/grade-school-tests.factor new file mode 100644 index 0000000..f2abddc --- /dev/null +++ b/exercises/practice/grade-school/grade-school/grade-school-tests.factor @@ -0,0 +1,68 @@ +USING: exercism-tools grade-school io kernel locals sequences tools.test unicode ; +IN: grade-school.tests +:: add-results ( students -- bools ) + :> s students [ first2 [ s ] 2dip add-student ] map ; +:: add-all ( students -- school ) + :> s students [ first2 [ s ] 2dip add-student drop ] each s ; + +"Roster is empty when no student is added" description +{ { } } [ { } add-all roster ] unit-test + +STOP-HERE + +"Add a student" description +{ { t } } [ { { "Aimee" 2 } } add-results ] unit-test + +"Student is added to the roster" description +{ { "Aimee" } } [ { { "Aimee" 2 } } add-all roster ] unit-test + +"Adding multiple students in the same grade in the roster" description +{ { t t t } } [ { { "Blair" 2 } { "James" 2 } { "Paul" 2 } } add-results ] unit-test + +"Multiple students in the same grade are added to the roster" description +{ { "Blair" "James" "Paul" } } [ { { "Blair" 2 } { "James" 2 } { "Paul" 2 } } add-all roster ] unit-test + +"Cannot add student to same grade in the roster more than once" description +{ { t t f t } } [ { { "Blair" 2 } { "James" 2 } { "James" 2 } { "Paul" 2 } } add-results ] unit-test + +"Student not added to same grade in the roster more than once" description +{ { "Blair" "James" "Paul" } } [ { { "Blair" 2 } { "James" 2 } { "James" 2 } { "Paul" 2 } } add-all roster ] unit-test + +"Adding students in multiple grades" description +{ { t t } } [ { { "Chelsea" 3 } { "Logan" 7 } } add-results ] unit-test + +"Students in multiple grades are added to the roster" description +{ { "Chelsea" "Logan" } } [ { { "Chelsea" 3 } { "Logan" 7 } } add-all roster ] unit-test + +"Cannot add same student to multiple grades in the roster" description +{ { t t f t } } [ { { "Blair" 2 } { "James" 2 } { "James" 3 } { "Paul" 3 } } add-results ] unit-test + +"Student not added to multiple grades in the roster" description +{ { "Blair" "James" "Paul" } } [ { { "Blair" 2 } { "James" 2 } { "James" 3 } { "Paul" 3 } } add-all roster ] unit-test + +"Students are sorted by grades in the roster" description +{ { "Anna" "Peter" "Jim" } } [ { { "Jim" 3 } { "Peter" 2 } { "Anna" 1 } } add-all roster ] unit-test + +"Students are sorted by name in the roster" description +{ { "Alex" "Peter" "Zoe" } } [ { { "Peter" 2 } { "Zoe" 2 } { "Alex" 2 } } add-all roster ] unit-test + +"Students are sorted by grades and then by name in the roster" description +{ { "Anna" "Barb" "Charlie" "Alex" "Peter" "Zoe" "Jim" } } [ { { "Peter" 2 } { "Anna" 1 } { "Barb" 1 } { "Zoe" 2 } { "Alex" 2 } { "Jim" 3 } { "Charlie" 1 } } add-all roster ] unit-test + +"Grade is empty if no students in the roster" description +{ { } } [ { } add-all 1 grade ] unit-test + +"Grade is empty if no students in that grade" description +{ { } } [ { { "Peter" 2 } { "Zoe" 2 } { "Alex" 2 } { "Jim" 3 } } add-all 1 grade ] unit-test + +"Student not added to same grade more than once" description +{ { "Blair" "James" "Paul" } } [ { { "Blair" 2 } { "James" 2 } { "James" 2 } { "Paul" 2 } } add-all 2 grade ] unit-test + +"Student not added to multiple grades" description +{ { "Blair" "James" } } [ { { "Blair" 2 } { "James" 2 } { "James" 3 } { "Paul" 3 } } add-all 2 grade ] unit-test + +"Student not added to other grade for multiple grades" description +{ { "Paul" } } [ { { "Blair" 2 } { "James" 2 } { "James" 3 } { "Paul" 3 } } add-all 3 grade ] unit-test + +"Students are sorted by name in a grade" description +{ { "Bradley" "Franklin" } } [ { { "Franklin" 5 } { "Bradley" 5 } { "Jeff" 1 } } add-all 5 grade ] unit-test diff --git a/exercises/practice/grade-school/grade-school/grade-school.factor b/exercises/practice/grade-school/grade-school/grade-school.factor new file mode 100644 index 0000000..7c9614c --- /dev/null +++ b/exercises/practice/grade-school/grade-school/grade-school.factor @@ -0,0 +1,16 @@ +USING: kernel ; +IN: grade-school + +TUPLE: school students ; + +: ( -- school ) + "unimplemented" throw ; + +: add-student ( school name grade -- ? ) + "unimplemented" throw ; + +: roster ( school -- names ) + "unimplemented" throw ; + +: grade ( school n -- names ) + "unimplemented" throw ; diff --git a/exercises/practice/intergalactic-transmission/.docs/instructions.md b/exercises/practice/intergalactic-transmission/.docs/instructions.md new file mode 100644 index 0000000..5497088 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.docs/instructions.md @@ -0,0 +1,54 @@ +# Instructions + +Your job is to help implement + +- the transmitter, which calculates the transmission sequence, and +- the receiver, which decodes it. + +A parity bit is simple way of detecting transmission errors. +The transmitters and receivers can only transmit and receive _exactly_ eight bits at a time (including the parity bit). +The parity bit is set so that there is an _even_ number of 1 bits in each transmission, and the parity bit is always the first bit from the right. +So if the receiver receives `11000001`, `01110101` or `01000000` (i.e. a transmission with an odd number of 1 bits), it knows there is an error. + +However, messages are rarely this short, and need to be transmitted in a sequence when they are longer. + +For example, consider the message `11000000 00000001 11000000 11011110` (or `C0 01 C0 DE` in hex). + +Since each transmission contains exactly eight bits, it can only contain seven bits of data and the parity bit. +A parity bit must then be inserted after every seven bits of data: + +```text +11000000 00000001 11000000 11011110 + ↑ ↑ ↑ ↑ (7th bits) +``` + +The transmission sequence for this message looks like this: + +```text +1100000_ 0000000_ 0111000_ 0001101_ 1110 + ↑ ↑ ↑ ↑ (parity bits) +``` + +The data in the first transmission in the sequence (`1100000`) has two 1 bits (an even number), so the parity bit is 0. +The first transmission becomes `11000000` (or `C0` in hex). + +The data in the next transmission (`0000000`) has zero 1 bits (an even number again), so the parity bit is 0 again. +The second transmission thus becomes `00000000` (or `00` in hex). + +The data for the next two transmissions (`0111000` and `0001101`) have three 1 bits. +Their parity bits are set to 1 so that they have an even number of 1 bits in the transmission. +They are transmitted as `01110001` and `00011011` (or `71` and `1B` in hex). + +The last transmission (`1110`) has only four bits of data. +Since exactly eight bits are transmitted at a time and the parity bit is the rightmost bit, three 0 bits and then the parity bit are added to make up eight bits. +It now looks like this (where `_` is the parity bit): + +```text +1110 000_ + ↑↑↑ (added 0 bits) +``` + +There is an odd number of 1 bits again, so the parity bit is 1. +The last transmission in the sequence becomes `11100001` (or `E1` in hex). + +The entire transmission sequence for this message is `11000000 00000000 01110001 00011011 11100001` (or `C0 00 71 1B E1` in hex). diff --git a/exercises/practice/intergalactic-transmission/.docs/introduction.md b/exercises/practice/intergalactic-transmission/.docs/introduction.md new file mode 100644 index 0000000..f19dffb --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.docs/introduction.md @@ -0,0 +1,23 @@ +# Introduction + +Trillions upon trillions of messages zip between Earth and neighboring galaxies every millisecond. +But transmitting over such long distances is tricky. +Pesky solar flares, temporal distortions, stray forces, and even the flap of a space butterfly's wing can cause a random bit to change during transmission. + +Now imagine the consequences: + +- Crashing the Intergalactic Share Market when "buy low" turns to "sell now". +- Losing contact with the Kepler Whirl system when "save new worm hole" becomes "cave new worm hole". +- Or plunging the universe into existential horror by replacing a cowboy emoji 🤠 with a clown emoji 🤡. + +Detecting corrupted messages isn't just important — it's critical. +The receiver _must_ know when something has gone wrong before disaster strikes. + +But how? +Scientists and engineers from across the universe have been battling this problem for eons. +Entire cosmic AI superclusters churn through the data. +And then, one day, a legend resurfaces — an ancient, powerful method, whispered in debugging forums, muttered by engineers who've seen too much... + +The Parity Bit! + +A method so simple, so powerful, that it might just save interstellar communication. diff --git a/exercises/practice/intergalactic-transmission/.meta/config.json b/exercises/practice/intergalactic-transmission/.meta/config.json new file mode 100644 index 0000000..67b3696 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "intergalactic-transmission/intergalactic-transmission.factor" + ], + "test": [ + "intergalactic-transmission/intergalactic-transmission-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Add parity bits to a message for transmission", + "source": "Kah Goh", + "source_url": "https://github.com/exercism/problem-specifications/pull/2543" +} diff --git a/exercises/practice/intergalactic-transmission/.meta/example.factor b/exercises/practice/intergalactic-transmission/.meta/example.factor new file mode 100644 index 0000000..5ae85b2 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/example.factor @@ -0,0 +1,40 @@ +USING: arrays grouping kernel math sequences strings ; +IN: intergalactic-transmission + +value ( ch -- n ) + dup CHAR: a >= [ CHAR: a - 10 + ] [ CHAR: 0 - ] if ; + +: byte>bits ( byte -- bits ) + 8 [ 7 swap - neg shift 1 bitand ] with map ; + +: hex>byte ( str -- byte ) + 2 tail [ hex-char>value ] map + first2 [ 16 * ] dip + ; + +: byte>hex ( byte -- str ) + [ 16 /i ] [ 16 mod ] bi + [ hex-digits nth ] bi@ 2array >string "0x" prepend ; + +: bits>byte ( bits -- byte ) + 0 [ [ 1 shift ] dip bitor ] reduce ; + +: parity ( bits -- p ) + sum 2 mod ; + +PRIVATE> + +: transmit-sequence ( message -- sequence ) + [ hex>byte byte>bits ] map concat + 7 group [ 7 0 pad-tail >array ] map + [ dup parity suffix bits>byte byte>hex ] map ; + +: decode-message ( sequence -- message ) + [ hex>byte byte>bits ] map + dup [ parity 0 = [ "wrong parity" throw ] unless ] each + [ 7 head ] map concat + 8 group [ length 8 = ] filter + [ >array bits>byte byte>hex ] map ; diff --git a/exercises/practice/intergalactic-transmission/.meta/generator.jl b/exercises/practice/intergalactic-transmission/.meta/generator.jl new file mode 100644 index 0000000..6f26348 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/generator.jl @@ -0,0 +1,22 @@ +module IntergalacticTransmission + +const EXTRA_VOCABS = ["sequences"] + +function fmt_array(arr) + "{ " * join(["\"$(s)\"" for s in arr], " ") * (isempty(arr) ? "}" : " }") +end + +function gen_test_case(case) + property = case["property"] + word = property == "transmitSequence" ? "transmit-sequence" : "decode-message" + input = fmt_array(case["input"]["message"]) + expected = case["expected"] + if expected isa Dict + msg = expected["error"] + return """[ $(input) $(word) ] [ "$(msg)" = ] must-fail-with""" + else + return "{ $(fmt_array(expected)) }\n[ $(input) $(word) ] unit-test" + end +end + +end diff --git a/exercises/practice/intergalactic-transmission/.meta/tests.toml b/exercises/practice/intergalactic-transmission/.meta/tests.toml new file mode 100644 index 0000000..64a8aac --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/tests.toml @@ -0,0 +1,88 @@ +# 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. + +[f99d4046-b429-4582-9324-f0bcac7ab51c] +description = "calculate transmit sequences -> empty message" + +[ee27ea2d-8999-4f23-9275-8f6879545f86] +description = "calculate transmit sequences -> 0x00 is transmitted as 0x0000" + +[97f27f98-8020-402d-be85-f21ba54a6df0] +description = "calculate transmit sequences -> 0x02 is transmitted as 0x0300" + +[24712fb9-0336-4e2f-835e-d2350f29c420] +description = "calculate transmit sequences -> 0x06 is transmitted as 0x0600" + +[7630b5a9-dba1-4178-b2a0-4a376f7414e0] +description = "calculate transmit sequences -> 0x05 is transmitted as 0x0581" + +[ab4fe80b-ef8e-4a99-b4fb-001937af415d] +description = "calculate transmit sequences -> 0x29 is transmitted as 0x2881" + +[4e200d84-593b-4449-b7c0-4de1b6a0955e] +description = "calculate transmit sequences -> 0xc001c0de is transmitted as 0xc000711be1" + +[fbc537e9-6b21-4f4a-8c2b-9cf9b702a9b7] +description = "calculate transmit sequences -> six byte message" + +[d5b75adf-b5fc-4f77-b4ab-77653e30f07c] +description = "calculate transmit sequences -> seven byte message" + +[6d8b297b-da1d-435e-bcd7-55fbb1400e73] +description = "calculate transmit sequences -> eight byte message" + +[54a0642a-d5aa-490c-be89-8e171a0cab6f] +description = "calculate transmit sequences -> twenty byte message" + +[9a8084dd-3336-474c-90cb-8a852524604d] +description = "decode received messages -> empty message" + +[879af739-0094-4736-9127-bd441b1ddbbf] +description = "decode received messages -> zero message" + +[7a89eeef-96c5-4329-a246-ec181a8e959a] +description = "decode received messages -> 0x0300 is decoded to 0x02" + +[3e515af7-8b62-417f-960c-3454bca7f806] +description = "decode received messages -> 0x0581 is decoded to 0x05" + +[a1b4a3f7-9f05-4b7a-b86e-d7c6fc3f16a9] +description = "decode received messages -> 0x2881 is decoded to 0x29" + +[2e99d617-4c91-4ad5-9217-e4b2447d6e4a] +description = "decode received messages -> first byte has wrong parity" + +[507e212d-3dae-42e8-88b4-2223838ff8d2] +description = "decode received messages -> second byte has wrong parity" + +[b985692e-6338-46c7-8cea-bc38996d4dfd] +description = "decode received messages -> 0xcf4b00 is decoded to 0xce94" + +[7a1f4d48-696d-4679-917c-21b7da3ff3fd] +description = "decode received messages -> 0xe2566500 is decoded to 0xe2ad90" + +[467549dc-a558-443b-80c5-ff3d4eb305d4] +description = "decode received messages -> six byte message" + +[1f3be5fb-093a-4661-9951-c1c4781c71ea] +description = "decode received messages -> seven byte message" + +[6065b8b3-9dcd-45c9-918c-b427cfdb28c1] +description = "decode received messages -> last byte has wrong parity" + +[98af97b7-9cca-4c4c-9de3-f70e227a4cb1] +description = "decode received messages -> eight byte message" + +[aa7d4785-2bb9-43a4-a38a-203325c464fb] +description = "decode received messages -> twenty byte message" + +[4c86e034-b066-42ac-8497-48f9bc1723c1] +description = "decode received messages -> wrong parity on 16th byte" diff --git a/exercises/practice/intergalactic-transmission/exercism-tools/exercism-tools.factor b/exercises/practice/intergalactic-transmission/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/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/intergalactic-transmission/intergalactic-transmission/intergalactic-transmission-tests.factor b/exercises/practice/intergalactic-transmission/intergalactic-transmission/intergalactic-transmission-tests.factor new file mode 100644 index 0000000..3b09e2f --- /dev/null +++ b/exercises/practice/intergalactic-transmission/intergalactic-transmission/intergalactic-transmission-tests.factor @@ -0,0 +1,104 @@ +USING: exercism-tools intergalactic-transmission io kernel sequences tools.test unicode ; +IN: intergalactic-transmission.tests + +"empty message" description +{ { } } +[ { } transmit-sequence ] unit-test + +STOP-HERE + +"0x00 is transmitted as 0x0000" description +{ { "0x00" "0x00" } } +[ { "0x00" } transmit-sequence ] unit-test + +"0x02 is transmitted as 0x0300" description +{ { "0x03" "0x00" } } +[ { "0x02" } transmit-sequence ] unit-test + +"0x06 is transmitted as 0x0600" description +{ { "0x06" "0x00" } } +[ { "0x06" } transmit-sequence ] unit-test + +"0x05 is transmitted as 0x0581" description +{ { "0x05" "0x81" } } +[ { "0x05" } transmit-sequence ] unit-test + +"0x29 is transmitted as 0x2881" description +{ { "0x28" "0x81" } } +[ { "0x29" } transmit-sequence ] unit-test + +"0xc001c0de is transmitted as 0xc000711be1" description +{ { "0xc0" "0x00" "0x71" "0x1b" "0xe1" } } +[ { "0xc0" "0x01" "0xc0" "0xde" } transmit-sequence ] unit-test + +"six byte message" description +{ { "0x47" "0xb8" "0x99" "0xac" "0x17" "0xa0" "0x84" } } +[ { "0x47" "0x72" "0x65" "0x61" "0x74" "0x21" } transmit-sequence ] unit-test + +"seven byte message" description +{ { "0x47" "0xb8" "0x99" "0xac" "0x17" "0xa0" "0xc5" "0x42" } } +[ { "0x47" "0x72" "0x65" "0x61" "0x74" "0x31" "0x21" } transmit-sequence ] unit-test + +"eight byte message" description +{ { "0xc0" "0x00" "0x44" "0x66" "0x7d" "0x06" "0x78" "0x42" "0x21" "0x81" } } +[ { "0xc0" "0x01" "0x13" "0x37" "0xc0" "0xde" "0x21" "0x21" } transmit-sequence ] unit-test + +"twenty byte message" description +{ { "0x44" "0xbd" "0x18" "0xaf" "0x27" "0x1b" "0xa5" "0xe7" "0x6c" "0x90" "0x1b" "0x2e" "0x33" "0x03" "0x84" "0xee" "0x65" "0xb8" "0xdb" "0xed" "0xd7" "0x28" "0x84" } } +[ { "0x45" "0x78" "0x65" "0x72" "0x63" "0x69" "0x73" "0x6d" "0x20" "0x69" "0x73" "0x20" "0x61" "0x77" "0x65" "0x73" "0x6f" "0x6d" "0x65" "0x21" } transmit-sequence ] unit-test + +"empty message" description +{ { } } +[ { } decode-message ] unit-test + +"zero message" description +{ { "0x00" } } +[ { "0x00" "0x00" } decode-message ] unit-test + +"0x0300 is decoded to 0x02" description +{ { "0x02" } } +[ { "0x03" "0x00" } decode-message ] unit-test + +"0x0581 is decoded to 0x05" description +{ { "0x05" } } +[ { "0x05" "0x81" } decode-message ] unit-test + +"0x2881 is decoded to 0x29" description +{ { "0x29" } } +[ { "0x28" "0x81" } decode-message ] unit-test + +"first byte has wrong parity" description +[ { "0x07" "0x00" } decode-message ] [ "wrong parity" = ] must-fail-with + +"second byte has wrong parity" description +[ { "0x03" "0x68" } decode-message ] [ "wrong parity" = ] must-fail-with + +"0xcf4b00 is decoded to 0xce94" description +{ { "0xce" "0x94" } } +[ { "0xcf" "0x4b" "0x00" } decode-message ] unit-test + +"0xe2566500 is decoded to 0xe2ad90" description +{ { "0xe2" "0xad" "0x90" } } +[ { "0xe2" "0x56" "0x65" "0x00" } decode-message ] unit-test + +"six byte message" description +{ { "0x47" "0x72" "0x65" "0x61" "0x74" "0x21" } } +[ { "0x47" "0xb8" "0x99" "0xac" "0x17" "0xa0" "0x84" } decode-message ] unit-test + +"seven byte message" description +{ { "0x47" "0x72" "0x65" "0x61" "0x74" "0x31" "0x21" } } +[ { "0x47" "0xb8" "0x99" "0xac" "0x17" "0xa0" "0xc5" "0x42" } decode-message ] unit-test + +"last byte has wrong parity" description +[ { "0x47" "0xb8" "0x99" "0xac" "0x17" "0xa0" "0xc5" "0x43" } decode-message ] [ "wrong parity" = ] must-fail-with + +"eight byte message" description +{ { "0xc0" "0x01" "0x13" "0x37" "0xc0" "0xde" "0x21" "0x21" } } +[ { "0xc0" "0x00" "0x44" "0x66" "0x7d" "0x06" "0x78" "0x42" "0x21" "0x81" } decode-message ] unit-test + +"twenty byte message" description +{ { "0x45" "0x78" "0x65" "0x72" "0x63" "0x69" "0x73" "0x6d" "0x20" "0x69" "0x73" "0x20" "0x61" "0x77" "0x65" "0x73" "0x6f" "0x6d" "0x65" "0x21" } } +[ { "0x44" "0xbd" "0x18" "0xaf" "0x27" "0x1b" "0xa5" "0xe7" "0x6c" "0x90" "0x1b" "0x2e" "0x33" "0x03" "0x84" "0xee" "0x65" "0xb8" "0xdb" "0xed" "0xd7" "0x28" "0x84" } decode-message ] unit-test + +"wrong parity on 16th byte" description +[ { "0x44" "0xbd" "0x18" "0xaf" "0x27" "0x1b" "0xa5" "0xe7" "0x6c" "0x90" "0x1b" "0x2e" "0x33" "0x03" "0x84" "0xef" "0x65" "0xb8" "0xdb" "0xed" "0xd7" "0x28" "0x84" } decode-message ] [ "wrong parity" = ] must-fail-with diff --git a/exercises/practice/intergalactic-transmission/intergalactic-transmission/intergalactic-transmission.factor b/exercises/practice/intergalactic-transmission/intergalactic-transmission/intergalactic-transmission.factor new file mode 100644 index 0000000..3945f3a --- /dev/null +++ b/exercises/practice/intergalactic-transmission/intergalactic-transmission/intergalactic-transmission.factor @@ -0,0 +1,8 @@ +USING: kernel ; +IN: intergalactic-transmission + +: transmit-sequence ( message -- sequence ) + "unimplemented" throw ; + +: decode-message ( sequence -- message ) + "unimplemented" throw ; diff --git a/exercises/practice/piecing-it-together/.docs/instructions.md b/exercises/practice/piecing-it-together/.docs/instructions.md new file mode 100644 index 0000000..8a2781d --- /dev/null +++ b/exercises/practice/piecing-it-together/.docs/instructions.md @@ -0,0 +1,47 @@ +# Instructions + +Given partial information about a jigsaw puzzle, add the missing pieces. + +If the information is insufficient to complete the details, or if given parts are in contradiction, the user should be notified. + +The full information about the jigsaw puzzle contains the following parts: + +- `pieces`: Total number of pieces +- `border`: Number of border pieces +- `inside`: Number of inside (non-border) pieces +- `rows`: Number of rows +- `columns`: Number of columns +- `aspectRatio`: Aspect ratio of columns to rows +- `format`: Puzzle format, which can be `portrait`, `square`, or `landscape` + +For this exercise, you may assume square pieces, so that the format can be derived from the aspect ratio: + +- If the aspect ratio is less than 1, it's `portrait` +- If it is equal to 1, it's `square` +- If it is greater than 1, it's `landscape` + +## Three examples + +### Portrait + +A portrait jigsaw puzzle with 6 pieces, all of which are border pieces and none are inside pieces. +It has 2 columns and 3 rows. +The aspect ratio is 0.666666... (2/3). + +![A 2 by 3 jigsaw puzzle](https://assets.exercism.org/images/exercises/piecing-it-together/jigsaw-puzzle-2x3.svg) + +### Square + +A square jigsaw puzzle with 9 pieces, all of which are border pieces except for the one in the center, which is an inside piece. +It has 3 columns and 3 rows. +The aspect ratio is 1.0 (3/3). + +![A 3 by 3 jigsaw puzzle](https://assets.exercism.org/images/exercises/piecing-it-together/jigsaw-puzzle-3x3.svg) + +### Landscape + +A landscape jigsaw puzzle with 12 pieces, 10 of which are border pieces and 2 are inside pieces. +It has 4 columns and 3 rows. +The aspect ratio is 1.333333... (4/3). + +![A 4 by 3 jigsaw puzzle](https://assets.exercism.org/images/exercises/piecing-it-together/jigsaw-puzzle-4x3.svg) diff --git a/exercises/practice/piecing-it-together/.docs/introduction.md b/exercises/practice/piecing-it-together/.docs/introduction.md new file mode 100644 index 0000000..2fa20f6 --- /dev/null +++ b/exercises/practice/piecing-it-together/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +Your best friend has started collecting jigsaw puzzles and wants to build a detailed catalog of their collection — recording information such as the total number of pieces, the number of rows and columns, the number of border and inside pieces, aspect ratio, and puzzle format. +Even with your powers combined, it takes multiple hours to solve a single jigsaw puzzle with one thousand pieces — and then you still need to count the rows and columns and calculate the remaining numbers manually. +The even larger puzzles with thousands of pieces look quite daunting now. +"There has to be a better way!" you exclaim and sit down in front of your computer to solve the problem. diff --git a/exercises/practice/piecing-it-together/.meta/config.json b/exercises/practice/piecing-it-together/.meta/config.json new file mode 100644 index 0000000..3e5b41c --- /dev/null +++ b/exercises/practice/piecing-it-together/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "piecing-it-together/piecing-it-together.factor" + ], + "test": [ + "piecing-it-together/piecing-it-together-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Fill in missing jigsaw puzzle details from partial data", + "source": "atk just started another 1000-pieces jigsaw puzzle when this idea hit him", + "source_url": "https://github.com/exercism/problem-specifications/pull/2554" +} diff --git a/exercises/practice/piecing-it-together/.meta/example.factor b/exercises/practice/piecing-it-together/.meta/example.factor new file mode 100644 index 0000000..8d4dd14 --- /dev/null +++ b/exercises/practice/piecing-it-together/.meta/example.factor @@ -0,0 +1,141 @@ +USING: arrays assocs combinators kernel locals math math.functions +math.order sequences ; +IN: piecing-it-together + +format ( ratio -- format ) + { + { [ dup 1.0 < ] [ drop "portrait" ] } + { [ dup 1.0 = ] [ drop "square" ] } + [ drop "landscape" ] + } cond ; + +! Format implied by a rows/columns pair. +:: pair-format ( rows columns -- format ) + columns rows /f ratio>format ; + +! Integer roots of x^2 - sum*x + product = 0, as a possibly empty +! sequence of { rows columns } pairs ({ larger smaller } and its swap). +:: sum-product>pairs ( sum product -- pairs ) + sum sq product 4 * - :> disc + disc 0 >= [ + disc sqrt :> root + sum root - 2 /f :> a + sum root + 2 /f :> b + a b [ >integer ] bi@ :> ( ai bi ) + ai dup >integer = bi dup >integer = and + ai bi + sum = and [ + { { ai bi } { bi ai } } + ] [ { } ] if + ] [ { } ] if ; + +! Solve (rows-2)*(columns-2) = inside with columns = ratio * rows. +! ratio*r^2 - 2(1+ratio)*r + (4 - inside) = 0 +:: inside-ratio>pair ( inside ratio -- pair/f ) + ratio :> a + -2 1 ratio + * :> b + 4 inside - :> c + b sq 4 a c * * - :> disc + disc 0 >= [ + disc sqrt :> root + b neg root + 2 a * /f round >integer :> r + r ratio * round >integer :> c2 + r 2 >= c2 2 >= and + r 2 - c2 2 - * inside = and + [ r c2 2array ] [ f ] if + ] [ f ] if ; + +! --- gathering candidate { rows columns } pairs --- + +: g ( partial key -- value/f ) swap at ; + +! pieces & inside, or pieces & border, or inside & border: +! determine product (pieces) and inside, then sum = rows+columns. +:: pieces&inside>pairs ( pieces inside -- pairs ) + pieces inside - 4 + 2 /f >integer :> sum + sum pieces sum-product>pairs ; + +:: counts>pairs ( partial -- pairs ) + partial "pieces" g :> pieces + partial "inside" g :> inside + partial "border" g :> border + { + { [ pieces inside and ] [ pieces inside pieces&inside>pairs ] } + { [ pieces border and ] + [ pieces pieces border - pieces&inside>pairs ] } + { [ inside border and ] + [ inside border + inside pieces&inside>pairs ] } + [ { } ] + } cond ; + +:: ratio>candidate ( partial ratio -- pair/f ) + partial "rows" g :> rows + partial "columns" g :> columns + partial "pieces" g :> pieces + partial "inside" g :> inside + { + { [ rows ] [ rows rows ratio * round >integer 2array ] } + { [ columns ] + [ columns ratio / round >integer columns 2array ] } + { [ pieces ] + [ pieces ratio * sqrt round >integer :> c + pieces c /f round >integer c 2array ] } + { [ inside ] [ inside ratio inside-ratio>pair ] } + [ f ] + } cond ; + +! Effective aspect ratio if directly given or implied by a square format. +:: effective-ratio ( partial -- ratio/f ) + partial "aspectRatio" g :> given + partial "format" g :> format + given [ given ] + [ format "square" = [ 1.0 ] [ f ] if ] if ; + +! Choose the unique valid { rows columns } pair, or f. +:: candidate ( partial -- pair/f ) + partial "rows" g :> rows + partial "columns" g :> columns + partial "format" g :> format + partial effective-ratio :> ratio + { + ! rows & columns directly + { [ rows columns and ] [ rows columns 2array ] } + ! something plus an aspect ratio (given or square) + { [ ratio ] [ partial ratio ratio>candidate ] } + ! piece counts, disambiguated by format + [ partial counts>pairs + [ first2 2 >= [ 2 >= ] dip and ] filter + format [ + [ first2 pair-format format = ] filter + ] when + dup length 1 = [ first ] [ drop f ] if ] + } cond ; + +! Build the full assoc from a { rows columns } pair. +:: build ( rows columns -- full ) + rows columns * :> pieces + rows 2 - columns 2 - * :> inside + pieces inside - :> border + columns rows /f :> ratio + H{ } clone + pieces "pieces" pick set-at + border "border" pick set-at + inside "inside" pick set-at + rows "rows" pick set-at + columns "columns" pick set-at + ratio "aspectRatio" pick set-at + ratio ratio>format "format" pick set-at ; + +! Every key present in partial must match the built full assoc. +:: consistent? ( partial full -- ? ) + partial >alist [ first2 [ full at ] dip = ] all? ; + +PRIVATE> + +: jigsaw-data ( partial -- full ) + dup candidate [ + first2 build ! partial full + 2dup consistent? ! partial full ? + [ nip ] [ 2drop "Contradictory data" throw ] if + ] [ drop "Insufficient data" throw ] if* ; diff --git a/exercises/practice/piecing-it-together/.meta/generator.jl b/exercises/practice/piecing-it-together/.meta/generator.jl new file mode 100644 index 0000000..087772b --- /dev/null +++ b/exercises/practice/piecing-it-together/.meta/generator.jl @@ -0,0 +1,45 @@ +module PiecingItTogether + +const EXTRA_VOCABS = ["assocs"] + +# Render a single { "key" value } entry for a Factor hashtable. +# aspectRatio must always be a Factor float literal so it compares +# equal to the float the solution computes. +function render_value(key, value) + if key == "aspectRatio" + return "{ \"$(key)\" $(repr(Float64(value))) }" + elseif value isa AbstractString + return "{ \"$(key)\" \"$(value)\" }" + elseif value isa AbstractFloat + return "{ \"$(key)\" $(repr(value)) }" + elseif value isa Integer + return "{ \"$(key)\" $(Int(value)) }" + else + error("unsupported value type for $(key): $(value)") + end +end + +function render_hashtable(dict) + keys_order = ["pieces", "border", "inside", "rows", "columns", "aspectRatio", "format"] + parts = String[] + for k in keys_order + if haskey(dict, k) + push!(parts, render_value(k, dict[k])) + end + end + return "H{ " * join(parts, " ") * " }" +end + +function gen_test_case(case) + input = render_hashtable(case["input"]) + expected = case["expected"] + if expected isa Dict && haskey(expected, "error") + msg = expected["error"] + return """[ $(input) jigsaw-data ] [ "$(msg)" = ] must-fail-with""" + else + full = render_hashtable(expected) + return "{ $(full) }\n[ $(input) jigsaw-data ] unit-test" + end +end + +end diff --git a/exercises/practice/piecing-it-together/.meta/tests.toml b/exercises/practice/piecing-it-together/.meta/tests.toml new file mode 100644 index 0000000..f462c15 --- /dev/null +++ b/exercises/practice/piecing-it-together/.meta/tests.toml @@ -0,0 +1,31 @@ +# 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. + +[ad626f23-09a2-4f5f-ba22-eec0671fa2a9] +description = "1000 pieces puzzle with 1.6 aspect ratio" + +[3e0c5919-3561-42f5-b9ed-26d70c20214e] +description = "square puzzle with 32 rows" + +[1126f160-b094-4dc2-bf37-13e36e394867] +description = "400 pieces square puzzle with only inside pieces and aspect ratio" + +[a9743178-5642-4cc0-8fdb-00d6b031c3a0] +description = "1500 pieces landscape puzzle with 30 rows and 1.6 aspect ratio" + +[f6378369-989c-497f-a6e2-f30b1fa76cba] +description = "300 pieces portrait puzzle with 70 border pieces" + +[f53f82ba-5663-4c7e-9e86-57fdbb3e53d2] +description = "puzzle with insufficient data" + +[a3d5c31a-cc74-44bf-b4fc-9e4d65f1ac1a] +description = "puzzle with contradictory data" diff --git a/exercises/practice/piecing-it-together/exercism-tools/exercism-tools.factor b/exercises/practice/piecing-it-together/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/practice/piecing-it-together/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/piecing-it-together/piecing-it-together/piecing-it-together-tests.factor b/exercises/practice/piecing-it-together/piecing-it-together/piecing-it-together-tests.factor new file mode 100644 index 0000000..93a1762 --- /dev/null +++ b/exercises/practice/piecing-it-together/piecing-it-together/piecing-it-together-tests.factor @@ -0,0 +1,30 @@ +USING: assocs exercism-tools io kernel piecing-it-together tools.test unicode ; +IN: piecing-it-together.tests + +"1000 pieces puzzle with 1.6 aspect ratio" description +{ H{ { "pieces" 1000 } { "border" 126 } { "inside" 874 } { "rows" 25 } { "columns" 40 } { "aspectRatio" 1.6 } { "format" "landscape" } } } +[ H{ { "pieces" 1000 } { "aspectRatio" 1.6 } } jigsaw-data ] unit-test + +STOP-HERE + +"square puzzle with 32 rows" description +{ H{ { "pieces" 1024 } { "border" 124 } { "inside" 900 } { "rows" 32 } { "columns" 32 } { "aspectRatio" 1.0 } { "format" "square" } } } +[ H{ { "rows" 32 } { "format" "square" } } jigsaw-data ] unit-test + +"400 pieces square puzzle with only inside pieces and aspect ratio" description +{ H{ { "pieces" 400 } { "border" 76 } { "inside" 324 } { "rows" 20 } { "columns" 20 } { "aspectRatio" 1.0 } { "format" "square" } } } +[ H{ { "inside" 324 } { "aspectRatio" 1.0 } } jigsaw-data ] unit-test + +"1500 pieces landscape puzzle with 30 rows and 1.6 aspect ratio" description +{ H{ { "pieces" 1500 } { "border" 156 } { "inside" 1344 } { "rows" 30 } { "columns" 50 } { "aspectRatio" 1.6666666666666667 } { "format" "landscape" } } } +[ H{ { "rows" 30 } { "aspectRatio" 1.6666666666666667 } } jigsaw-data ] unit-test + +"300 pieces portrait puzzle with 70 border pieces" description +{ H{ { "pieces" 300 } { "border" 70 } { "inside" 230 } { "rows" 25 } { "columns" 12 } { "aspectRatio" 0.48 } { "format" "portrait" } } } +[ H{ { "pieces" 300 } { "border" 70 } { "format" "portrait" } } jigsaw-data ] unit-test + +"puzzle with insufficient data" description +[ H{ { "pieces" 1500 } { "format" "landscape" } } jigsaw-data ] [ "Insufficient data" = ] must-fail-with + +"puzzle with contradictory data" description +[ H{ { "rows" 100 } { "columns" 1000 } { "format" "square" } } jigsaw-data ] [ "Contradictory data" = ] must-fail-with diff --git a/exercises/practice/piecing-it-together/piecing-it-together/piecing-it-together.factor b/exercises/practice/piecing-it-together/piecing-it-together/piecing-it-together.factor new file mode 100644 index 0000000..32b6ecd --- /dev/null +++ b/exercises/practice/piecing-it-together/piecing-it-together/piecing-it-together.factor @@ -0,0 +1,5 @@ +USING: kernel ; +IN: piecing-it-together + +: jigsaw-data ( partial -- full ) + "unimplemented" throw ; diff --git a/exercises/practice/roman-numerals/.docs/instructions.md b/exercises/practice/roman-numerals/.docs/instructions.md new file mode 100644 index 0000000..50e2f5b --- /dev/null +++ b/exercises/practice/roman-numerals/.docs/instructions.md @@ -0,0 +1,12 @@ +# Introduction + +Your task is to convert a number from Arabic numerals to Roman numerals. + +For this exercise, we are only concerned about traditional Roman numerals, in which the largest number is MMMCMXCIX (or 3,999). + +~~~~exercism/note +There are lots of different ways to convert between Arabic and Roman numerals. +We recommend taking a naive approach first to familiarise yourself with the concept of Roman numerals and then search for more efficient methods. + +Make sure to check out our Deep Dive video at the end to explore the different approaches you can take! +~~~~ diff --git a/exercises/practice/roman-numerals/.docs/introduction.md b/exercises/practice/roman-numerals/.docs/introduction.md new file mode 100644 index 0000000..6fd942f --- /dev/null +++ b/exercises/practice/roman-numerals/.docs/introduction.md @@ -0,0 +1,59 @@ +# Description + +Today, most people in the world use Arabic numerals (0–9). +But if you travelled back two thousand years, you'd find that most Europeans were using Roman numerals instead. + +To write a Roman numeral we use the following Latin letters, each of which has a value: + +| M | D | C | L | X | V | I | +| ---- | --- | --- | --- | --- | --- | --- | +| 1000 | 500 | 100 | 50 | 10 | 5 | 1 | + +A Roman numeral is a sequence of these letters, and its value is the sum of the letters' values. +For example, `XVIII` has the value 18 (`10 + 5 + 1 + 1 + 1 = 18`). + +There's one rule that makes things trickier though, and that's that **the same letter cannot be used more than three times in succession**. +That means that we can't express numbers such as 4 with the seemingly natural `IIII`. +Instead, for those numbers, we use a subtraction method between two letters. +So we think of `4` not as `1 + 1 + 1 + 1` but instead as `5 - 1`. +And slightly confusingly to our modern thinking, we write the smaller number first. +This applies only in the following cases: 4 (`IV`), 9 (`IX`), 40 (`XL`), 90 (`XC`), 400 (`CD`) and 900 (`CM`). + +Order matters in Roman numerals! +Letters (and the special compounds above) must be ordered by decreasing value from left to right. + +Here are some examples: + +```text + 105 => CV +---- => -- + 100 => C ++ 5 => V +``` + +```text + 106 => CVI +---- => -- + 100 => C ++ 5 => V ++ 1 => I +``` + +```text + 104 => CIV +---- => --- + 100 => C ++ 4 => IV +``` + +And a final more complex example: + +```text + 1996 => MCMXCVI +----- => ------- + 1000 => M ++ 900 => CM ++ 90 => XC ++ 5 => V ++ 1 => I +``` diff --git a/exercises/practice/roman-numerals/.meta/config.json b/exercises/practice/roman-numerals/.meta/config.json new file mode 100644 index 0000000..129f2f7 --- /dev/null +++ b/exercises/practice/roman-numerals/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "roman-numerals/roman-numerals.factor" + ], + "test": [ + "roman-numerals/roman-numerals-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Convert modern Arabic numbers into Roman numerals.", + "source": "The Roman Numeral Kata", + "source_url": "https://codingdojo.org/kata/RomanNumerals/" +} diff --git a/exercises/practice/roman-numerals/.meta/example.factor b/exercises/practice/roman-numerals/.meta/example.factor new file mode 100644 index 0000000..dd46a81 --- /dev/null +++ b/exercises/practice/roman-numerals/.meta/example.factor @@ -0,0 +1,17 @@ +USING: kernel locals math sequences ; +IN: roman-numerals + +CONSTANT: numerals { + { 1000 "M" } { 900 "CM" } { 500 "D" } { 400 "CD" } + { 100 "C" } { 90 "XC" } { 50 "L" } { 40 "XL" } + { 10 "X" } { 9 "IX" } { 5 "V" } { 4 "IV" } { 1 "I" } +} + +:: roman ( n -- str ) + n "" numerals [ + first2 :> ( value symbol ) + :> ( remaining acc ) + remaining value /mod :> ( quotient rest ) + rest + acc quotient [ drop symbol ] map concat append + ] each nip ; diff --git a/exercises/practice/roman-numerals/.meta/generator.jl b/exercises/practice/roman-numerals/.meta/generator.jl new file mode 100644 index 0000000..10f9e0f --- /dev/null +++ b/exercises/practice/roman-numerals/.meta/generator.jl @@ -0,0 +1,9 @@ +module RomanNumerals + +function gen_test_case(case) + number = Int(case["input"]["number"]) + expected = case["expected"] + return "{ \"$(expected)\" } [ $(number) roman ] unit-test" +end + +end diff --git a/exercises/practice/roman-numerals/.meta/tests.toml b/exercises/practice/roman-numerals/.meta/tests.toml new file mode 100644 index 0000000..709011b --- /dev/null +++ b/exercises/practice/roman-numerals/.meta/tests.toml @@ -0,0 +1,91 @@ +# 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. + +[19828a3a-fbf7-4661-8ddd-cbaeee0e2178] +description = "1 is I" + +[f088f064-2d35-4476-9a41-f576da3f7b03] +description = "2 is II" + +[b374a79c-3bea-43e6-8db8-1286f79c7106] +description = "3 is III" + +[05a0a1d4-a140-4db1-82e8-fcc21fdb49bb] +description = "4 is IV" + +[57c0f9ad-5024-46ab-975d-de18c430b290] +description = "5 is V" + +[20a2b47f-e57f-4797-a541-0b3825d7f249] +description = "6 is VI" + +[ff3fb08c-4917-4aab-9f4e-d663491d083d] +description = "9 is IX" + +[6d1d82d5-bf3e-48af-9139-87d7165ed509] +description = "16 is XVI" + +[2bda64ca-7d28-4c56-b08d-16ce65716cf6] +description = "27 is XXVII" + +[a1f812ef-84da-4e02-b4f0-89c907d0962c] +description = "48 is XLVIII" + +[607ead62-23d6-4c11-a396-ef821e2e5f75] +description = "49 is XLIX" + +[d5b283d4-455d-4e68-aacf-add6c4b51915] +description = "59 is LIX" + +[4465ffd5-34dc-44f3-ada5-56f5007b6dad] +description = "66 is LXVI" + +[46b46e5b-24da-4180-bfe2-2ef30b39d0d0] +description = "93 is XCIII" + +[30494be1-9afb-4f84-9d71-db9df18b55e3] +description = "141 is CXLI" + +[267f0207-3c55-459a-b81d-67cec7a46ed9] +description = "163 is CLXIII" + +[902ad132-0b4d-40e3-8597-ba5ed611dd8d] +description = "166 is CLXVI" + +[cdb06885-4485-4d71-8bfb-c9d0f496b404] +description = "402 is CDII" + +[6b71841d-13b2-46b4-ba97-dec28133ea80] +description = "575 is DLXXV" + +[dacb84b9-ea1c-4a61-acbb-ce6b36674906] +description = "666 is DCLXVI" + +[432de891-7fd6-4748-a7f6-156082eeca2f] +description = "911 is CMXI" + +[e6de6d24-f668-41c0-88d7-889c0254d173] +description = "1024 is MXXIV" + +[efbe1d6a-9f98-4eb5-82bc-72753e3ac328] +description = "1666 is MDCLXVI" + +[bb550038-d4eb-4be2-a9ce-f21961ac3bc6] +description = "3000 is MMM" + +[3bc4b41c-c2e6-49d9-9142-420691504336] +description = "3001 is MMMI" + +[2f89cad7-73f6-4d1b-857b-0ef531f68b7e] +description = "3888 is MMMDCCCLXXXVIII" + +[4e18e96b-5fbb-43df-a91b-9cb511fe0856] +description = "3999 is MMMCMXCIX" diff --git a/exercises/practice/roman-numerals/exercism-tools/exercism-tools.factor b/exercises/practice/roman-numerals/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/practice/roman-numerals/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/roman-numerals/roman-numerals/roman-numerals-tests.factor b/exercises/practice/roman-numerals/roman-numerals/roman-numerals-tests.factor new file mode 100644 index 0000000..61141f2 --- /dev/null +++ b/exercises/practice/roman-numerals/roman-numerals/roman-numerals-tests.factor @@ -0,0 +1,85 @@ +USING: exercism-tools io kernel roman-numerals tools.test unicode ; +IN: roman-numerals.tests + +"1 is I" description +{ "I" } [ 1 roman ] unit-test + +STOP-HERE + +"2 is II" description +{ "II" } [ 2 roman ] unit-test + +"3 is III" description +{ "III" } [ 3 roman ] unit-test + +"4 is IV" description +{ "IV" } [ 4 roman ] unit-test + +"5 is V" description +{ "V" } [ 5 roman ] unit-test + +"6 is VI" description +{ "VI" } [ 6 roman ] unit-test + +"9 is IX" description +{ "IX" } [ 9 roman ] unit-test + +"16 is XVI" description +{ "XVI" } [ 16 roman ] unit-test + +"27 is XXVII" description +{ "XXVII" } [ 27 roman ] unit-test + +"48 is XLVIII" description +{ "XLVIII" } [ 48 roman ] unit-test + +"49 is XLIX" description +{ "XLIX" } [ 49 roman ] unit-test + +"59 is LIX" description +{ "LIX" } [ 59 roman ] unit-test + +"66 is LXVI" description +{ "LXVI" } [ 66 roman ] unit-test + +"93 is XCIII" description +{ "XCIII" } [ 93 roman ] unit-test + +"141 is CXLI" description +{ "CXLI" } [ 141 roman ] unit-test + +"163 is CLXIII" description +{ "CLXIII" } [ 163 roman ] unit-test + +"166 is CLXVI" description +{ "CLXVI" } [ 166 roman ] unit-test + +"402 is CDII" description +{ "CDII" } [ 402 roman ] unit-test + +"575 is DLXXV" description +{ "DLXXV" } [ 575 roman ] unit-test + +"666 is DCLXVI" description +{ "DCLXVI" } [ 666 roman ] unit-test + +"911 is CMXI" description +{ "CMXI" } [ 911 roman ] unit-test + +"1024 is MXXIV" description +{ "MXXIV" } [ 1024 roman ] unit-test + +"1666 is MDCLXVI" description +{ "MDCLXVI" } [ 1666 roman ] unit-test + +"3000 is MMM" description +{ "MMM" } [ 3000 roman ] unit-test + +"3001 is MMMI" description +{ "MMMI" } [ 3001 roman ] unit-test + +"3888 is MMMDCCCLXXXVIII" description +{ "MMMDCCCLXXXVIII" } [ 3888 roman ] unit-test + +"3999 is MMMCMXCIX" description +{ "MMMCMXCIX" } [ 3999 roman ] unit-test diff --git a/exercises/practice/roman-numerals/roman-numerals/roman-numerals.factor b/exercises/practice/roman-numerals/roman-numerals/roman-numerals.factor new file mode 100644 index 0000000..4517ee8 --- /dev/null +++ b/exercises/practice/roman-numerals/roman-numerals/roman-numerals.factor @@ -0,0 +1,5 @@ +USING: kernel ; +IN: roman-numerals + +: roman ( n -- str ) + "unimplemented" throw ; diff --git a/exercises/practice/series/.docs/instructions.md b/exercises/practice/series/.docs/instructions.md new file mode 100644 index 0000000..fd97a67 --- /dev/null +++ b/exercises/practice/series/.docs/instructions.md @@ -0,0 +1,19 @@ +# Instructions + +Given a string of digits, output all the contiguous substrings of length `n` in that string in the order that they appear. + +For example, the string "49142" has the following 3-digit series: + +- "491" +- "914" +- "142" + +And the following 4-digit series: + +- "4914" +- "9142" + +And if you ask for a 6-digit series from a 5-digit string, you deserve whatever you get. + +Note that these series are only required to occupy _adjacent positions_ in the input; +the digits need not be _numerically consecutive_. diff --git a/exercises/practice/series/.meta/config.json b/exercises/practice/series/.meta/config.json new file mode 100644 index 0000000..c6afec3 --- /dev/null +++ b/exercises/practice/series/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "series/series.factor" + ], + "test": [ + "series/series-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Given a string of digits, output all the contiguous substrings of length `n` in that string.", + "source": "A subset of the Problem 8 at Project Euler", + "source_url": "https://projecteuler.net/problem=8" +} diff --git a/exercises/practice/series/.meta/example.factor b/exercises/practice/series/.meta/example.factor new file mode 100644 index 0000000..f97686d --- /dev/null +++ b/exercises/practice/series/.meta/example.factor @@ -0,0 +1,13 @@ +USING: combinators kernel locals math sequences ; +IN: series + +:: slices ( series len -- slices ) + { + { [ len 0 = ] [ "slice length cannot be zero" throw ] } + { [ len 0 < ] [ "slice length cannot be negative" throw ] } + { [ series length 0 = ] [ "series cannot be empty" throw ] } + { [ len series length > ] + [ "slice length cannot be greater than series length" throw ] } + [ series length len - 1 + + [| i | i i len + series subseq ] map ] + } cond ; diff --git a/exercises/practice/series/.meta/generator.jl b/exercises/practice/series/.meta/generator.jl new file mode 100644 index 0000000..adccad8 --- /dev/null +++ b/exercises/practice/series/.meta/generator.jl @@ -0,0 +1,20 @@ +module Series + +function escape(s) + return replace(string(s), "\\" => "\\\\", "\"" => "\\\"") +end + +function gen_test_case(case) + series = escape(case["input"]["series"]) + len = Int(case["input"]["sliceLength"]) + expected = case["expected"] + if expected isa Dict + msg = escape(expected["error"]) + return """[ "$(series)" $(len) slices ] [ "$(msg)" = ] must-fail-with""" + else + items = join(["\"$(escape(s))\"" for s in expected], " ") + return "{ { $(items) } } [ \"$(series)\" $(len) slices ] unit-test" + end +end + +end diff --git a/exercises/practice/series/.meta/tests.toml b/exercises/practice/series/.meta/tests.toml new file mode 100644 index 0000000..9696f51 --- /dev/null +++ b/exercises/practice/series/.meta/tests.toml @@ -0,0 +1,43 @@ +# 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. + +[7ae7a46a-d992-4c2a-9c15-a112d125ebad] +description = "slices of one from one" + +[3143b71d-f6a5-4221-aeae-619f906244d2] +description = "slices of one from two" + +[dbb68ff5-76c5-4ccd-895a-93dbec6d5805] +description = "slices of two" + +[19bbea47-c987-4e11-a7d1-e103442adf86] +description = "slices of two overlap" + +[8e17148d-ba0a-4007-a07f-d7f87015d84c] +description = "slices can include duplicates" + +[bd5b085e-f612-4f81-97a8-6314258278b0] +description = "slices of a long series" + +[6d235d85-46cf-4fae-9955-14b6efef27cd] +description = "slice length is too large" + +[d7957455-346d-4e47-8e4b-87ed1564c6d7] +description = "slice length is way too large" + +[d34004ad-8765-4c09-8ba1-ada8ce776806] +description = "slice length cannot be zero" + +[10ab822d-8410-470a-a85d-23fbeb549e54] +description = "slice length cannot be negative" + +[c7ed0812-0e4b-4bf3-99c4-28cbbfc246a2] +description = "empty series is invalid" diff --git a/exercises/practice/series/exercism-tools/exercism-tools.factor b/exercises/practice/series/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..428c2d3 --- /dev/null +++ b/exercises/practice/series/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/series/series/series-tests.factor b/exercises/practice/series/series/series-tests.factor new file mode 100644 index 0000000..a46b5ac --- /dev/null +++ b/exercises/practice/series/series/series-tests.factor @@ -0,0 +1,37 @@ +USING: exercism-tools io kernel series tools.test unicode ; +IN: series.tests + +"slices of one from one" description +{ { "1" } } [ "1" 1 slices ] unit-test + +STOP-HERE + +"slices of one from two" description +{ { "1" "2" } } [ "12" 1 slices ] unit-test + +"slices of two" description +{ { "35" } } [ "35" 2 slices ] unit-test + +"slices of two overlap" description +{ { "91" "14" "42" } } [ "9142" 2 slices ] unit-test + +"slices can include duplicates" description +{ { "777" "777" "777" "777" } } [ "777777" 3 slices ] unit-test + +"slices of a long series" description +{ { "91849" "18493" "84939" "49390" "93904" "39042" "90424" "04243" } } [ "918493904243" 5 slices ] unit-test + +"slice length is too large" description +[ "12345" 6 slices ] [ "slice length cannot be greater than series length" = ] must-fail-with + +"slice length is way too large" description +[ "12345" 42 slices ] [ "slice length cannot be greater than series length" = ] must-fail-with + +"slice length cannot be zero" description +[ "12345" 0 slices ] [ "slice length cannot be zero" = ] must-fail-with + +"slice length cannot be negative" description +[ "123" -1 slices ] [ "slice length cannot be negative" = ] must-fail-with + +"empty series is invalid" description +[ "" 1 slices ] [ "series cannot be empty" = ] must-fail-with diff --git a/exercises/practice/series/series/series.factor b/exercises/practice/series/series/series.factor new file mode 100644 index 0000000..36ebcab --- /dev/null +++ b/exercises/practice/series/series/series.factor @@ -0,0 +1,5 @@ +USING: kernel ; +IN: series + +: slices ( series length -- slices ) + "unimplemented" throw ;