diff --git a/README.md b/README.md index 458aa432..51e2db0f 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,12 @@ Rename *bob.lua* to *example.lua*, and add the exercise to the [lua/config.json] Optionally, you can generate the spec from the upstream canonical data. To use the spec generator, create a file called `spec_generator.lua` in the `.meta/` directory of the exercise. This file should be a Lua module returning a table with two fields: -- `module_name` - A string containing the name of the Lua variable to which the module under test will be bound +- `module_name` - A string containing the name of the Lua variable to which the module under test will be bound. - `generate_test` - A function that returns a string representation of a test given the corresponding case from the canonical data. +Optionally, a third field can be included: +- `test_helpers` - A string that will be injected at the top of the outermost `describe` block. This can include helper functions that are used in multiple tests or other setup code. + To use the test generator, run: ```shell diff --git a/bin/generate-spec b/bin/generate-spec index faa77ec7..dc1f5f70 100755 --- a/bin/generate-spec +++ b/bin/generate-spec @@ -41,6 +41,10 @@ local function process(node) table.insert(output, "describe('" .. node.description:lower() .. "', function()") else table.insert(output, "describe('" .. exercise_name .. "', function()") + + if spec_generator.test_helpers then + table.insert(output, spec_generator.test_helpers) + end end local cases = {} diff --git a/config.json b/config.json index 8fa9978e..a6ae3bec 100644 --- a/config.json +++ b/config.json @@ -1343,6 +1343,14 @@ "practices": [], "prerequisites": [], "difficulty": 5 + }, + { + "slug": "saddle-points", + "name": "Saddle Points", + "uuid": "1fe6b8c5-776f-4b32-aac3-9957a8fe7925", + "practices": [], + "prerequisites": [], + "difficulty": 3 } ] }, diff --git a/exercises/practice/rotational-cipher/.meta/config.json b/exercises/practice/rotational-cipher/.meta/config.json index 2df088f6..f47a4609 100644 --- a/exercises/practice/rotational-cipher/.meta/config.json +++ b/exercises/practice/rotational-cipher/.meta/config.json @@ -1,5 +1,7 @@ { - "authors": ["ryanplusplus"], + "authors": [ + "ryanplusplus" + ], "files": { "solution": [ "rotational-cipher.lua" diff --git a/exercises/practice/saddle-points/.busted b/exercises/practice/saddle-points/.busted new file mode 100644 index 00000000..86b84e7c --- /dev/null +++ b/exercises/practice/saddle-points/.busted @@ -0,0 +1,5 @@ +return { + default = { + ROOT = { '.' } + } +} diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md new file mode 100644 index 00000000..c585568b --- /dev/null +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -0,0 +1,26 @@ +# Instructions + +Your task is to find the potential trees where you could build your tree house. + +The data company provides the data as grids that show the heights of the trees. +The rows of the grid represent the east-west direction, and the columns represent the north-south direction. + +An acceptable tree will be the largest in its row, while being the smallest in its column. + +A grid might not have any good trees at all. +Or it might have one, or even several. + +Here is a grid that has exactly one candidate tree. + +```text + 1 2 3 4 + |----------- +1 | 9 8 7 8 +2 | 5 3 2 4 <--- potential tree house at row 2, column 1, for tree with height 5 +3 | 6 6 7 1 +``` + +- Row 2 has values 5, 3, 2, and 4. The largest value is 5. +- Column 1 has values 9, 5, and 6. The smallest value is 5. + +So the point at `[2, 1]` (row: 2, column: 1) is a great spot for a tree house. diff --git a/exercises/practice/saddle-points/.docs/introduction.md b/exercises/practice/saddle-points/.docs/introduction.md new file mode 100644 index 00000000..34b2c77e --- /dev/null +++ b/exercises/practice/saddle-points/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +You plan to build a tree house in the woods near your house so that you can watch the sun rise and set. + +You've obtained data from a local survey company that show the height of every tree in each rectangular section of the map. +You need to analyze each grid on the map to find good trees for your tree house. + +A good tree is both: + +- taller than every tree to the east and west, so that you have the best possible view of the sunrises and sunsets. +- shorter than every tree to the north and south, to minimize the amount of tree climbing. diff --git a/exercises/practice/saddle-points/.meta/config.json b/exercises/practice/saddle-points/.meta/config.json new file mode 100644 index 00000000..75726b78 --- /dev/null +++ b/exercises/practice/saddle-points/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "ryanplusplus" + ], + "files": { + "solution": [ + "saddle-points.lua" + ], + "test": [ + "saddle-points_spec.lua" + ], + "example": [ + ".meta/example.lua" + ] + }, + "blurb": "Detect saddle points in a matrix.", + "source": "J Dalbey's Programming Practice problems", + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" +} diff --git a/exercises/practice/saddle-points/.meta/example.lua b/exercises/practice/saddle-points/.meta/example.lua new file mode 100644 index 00000000..5b588676 --- /dev/null +++ b/exercises/practice/saddle-points/.meta/example.lua @@ -0,0 +1,32 @@ +return function(matrix) + if #matrix == 0 or #matrix[1] == 0 then + return {} + end + + local row_max = {} + local column_min = {} + + for row, row_values in ipairs(matrix) do + row_max[row] = math.max(table.unpack(row_values)) + end + + for column = 1, #matrix[1] do + local column_values = {} + for _, row_values in ipairs(matrix) do + table.insert(column_values, row_values[column]) + end + column_min[column] = math.min(table.unpack(column_values)) + end + + local saddle_points = {} + + for row = 1, #matrix do + for column = 1, #matrix[1] do + if matrix[row][column] == row_max[row] and matrix[row][column] == column_min[column] then + table.insert(saddle_points, { row = row, column = column }) + end + end + end + + return saddle_points +end diff --git a/exercises/practice/saddle-points/.meta/spec_generator.lua b/exercises/practice/saddle-points/.meta/spec_generator.lua new file mode 100644 index 00000000..2f181010 --- /dev/null +++ b/exercises/practice/saddle-points/.meta/spec_generator.lua @@ -0,0 +1,49 @@ +local map = function(t, f) + local mapped = {} + for i, v in ipairs(t) do + mapped[i] = f(v) + end + return mapped +end + +local function render_matrix(matrix) + return table.concat(map(matrix, function(row) + return '{' .. table.concat(row, ', ') .. '}, --' + end), '\n') +end + +local function render_result(result) + return table.concat(map(result, function(point) + return ('{ row = %s, column = %s }, --'):format(point.row, point.column) + end), '\n') +end + +return { + module_name = 'saddle_points', + + test_helpers = [[ + local function assert_saddle_points_are_equal(expected, actual) + if #expected ~= #actual then + return false + end + for i = 1, #expected do + if expected[i].row ~= actual[i].row or expected[i].column ~= actual[i].column then + return false + end + end + return true + end + ]], + + generate_test = function(case) + local template = [[ + local matrix = { + %s + } + local expected = { + %s + } + assert_saddle_points_are_equal(expected, saddle_points(matrix))]] + return template:format(render_matrix(case.input.matrix), render_result(case.expected)) + end +} diff --git a/exercises/practice/saddle-points/.meta/tests.toml b/exercises/practice/saddle-points/.meta/tests.toml new file mode 100644 index 00000000..ca008520 --- /dev/null +++ b/exercises/practice/saddle-points/.meta/tests.toml @@ -0,0 +1,37 @@ +# 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. + +[3e374e63-a2e0-4530-a39a-d53c560382bd] +description = "Can identify single saddle point" + +[6b501e2b-6c1f-491f-b1bb-7f278f760534] +description = "Can identify that empty matrix has no saddle points" + +[8c27cc64-e573-4fcb-a099-f0ae863fb02f] +description = "Can identify lack of saddle points when there are none" + +[6d1399bd-e105-40fd-a2c9-c6609507d7a3] +description = "Can identify multiple saddle points in a column" + +[3e81dce9-53b3-44e6-bf26-e328885fd5d1] +description = "Can identify multiple saddle points in a row" + +[88868621-b6f4-4837-bb8b-3fad8b25d46b] +description = "Can identify saddle point in bottom right corner" + +[5b9499ca-fcea-4195-830a-9c4584a0ee79] +description = "Can identify saddle points in a non square matrix" + +[ee99ccd2-a1f1-4283-ad39-f8c70f0cf594] +description = "Can identify that saddle points in a single column matrix are those with the minimum value" + +[63abf709-a84b-407f-a1b3-456638689713] +description = "Can identify that saddle points in a single row matrix are those with the maximum value" diff --git a/exercises/practice/saddle-points/saddle-points.lua b/exercises/practice/saddle-points/saddle-points.lua new file mode 100644 index 00000000..b91d0bbb --- /dev/null +++ b/exercises/practice/saddle-points/saddle-points.lua @@ -0,0 +1,3 @@ +return function(matrix) + +end diff --git a/exercises/practice/saddle-points/saddle-points_spec.lua b/exercises/practice/saddle-points/saddle-points_spec.lua new file mode 100644 index 00000000..582fd2f9 --- /dev/null +++ b/exercises/practice/saddle-points/saddle-points_spec.lua @@ -0,0 +1,122 @@ +local saddle_points = require('saddle-points') + +describe('saddle-points', function() + local function assert_saddle_points_are_equal(expected, actual) + if #expected ~= #actual then + return false + end + for i = 1, #expected do + if expected[i].row ~= actual[i].row or expected[i].column ~= actual[i].column then + return false + end + end + return true + end + + it('can identify single saddle point', function() + local matrix = { + { 9, 8, 7 }, -- + { 5, 3, 2 }, -- + { 6, 6, 7 } -- + } + local expected = { + { row = 2, column = 1 } -- + } + assert_saddle_points_are_equal(expected, saddle_points(matrix)) + end) + + it('can identify that empty matrix has no saddle points', function() + local matrix = { + {} -- + } + local expected = {} + assert_saddle_points_are_equal(expected, saddle_points(matrix)) + end) + + it('can identify lack of saddle points when there are none', function() + local matrix = { + { 1, 2, 3 }, -- + { 3, 1, 2 }, -- + { 2, 3, 1 } -- + } + local expected = {} + assert_saddle_points_are_equal(expected, saddle_points(matrix)) + end) + + it('can identify multiple saddle points in a column', function() + local matrix = { + { 4, 5, 4 }, -- + { 3, 5, 5 }, -- + { 1, 5, 4 } -- + } + local expected = { + { row = 1, column = 2 }, -- + { row = 2, column = 2 }, -- + { row = 3, column = 2 } -- + } + assert_saddle_points_are_equal(expected, saddle_points(matrix)) + end) + + it('can identify multiple saddle points in a row', function() + local matrix = { + { 6, 7, 8 }, -- + { 5, 5, 5 }, -- + { 7, 5, 6 } -- + } + local expected = { + { row = 2, column = 1 }, -- + { row = 2, column = 2 }, -- + { row = 2, column = 3 } -- + } + assert_saddle_points_are_equal(expected, saddle_points(matrix)) + end) + + it('can identify saddle point in bottom right corner', function() + local matrix = { + { 8, 7, 9 }, -- + { 6, 7, 6 }, -- + { 3, 2, 5 } -- + } + local expected = { + { row = 3, column = 3 } -- + } + assert_saddle_points_are_equal(expected, saddle_points(matrix)) + end) + + it('can identify saddle points in a non square matrix', function() + local matrix = { + { 3, 1, 3 }, -- + { 3, 2, 4 } -- + } + local expected = { + { row = 1, column = 3 }, -- + { row = 1, column = 1 } -- + } + assert_saddle_points_are_equal(expected, saddle_points(matrix)) + end) + + it('can identify that saddle points in a single column matrix are those with the minimum value', function() + local matrix = { + { 2 }, -- + { 1 }, -- + { 4 }, -- + { 1 } -- + } + local expected = { + { row = 2, column = 1 }, -- + { row = 4, column = 1 } -- + } + assert_saddle_points_are_equal(expected, saddle_points(matrix)) + end) + + it('can identify that saddle points in a single row matrix are those with the maximum value', function() + local matrix = { + { 2, 5, 3, 5 } -- + } + local expected = { + { row = 1, column = 2 }, -- + { row = 1, column = 4 } -- + } + assert_saddle_points_are_equal(expected, saddle_points(matrix)) + end) +end) diff --git a/exercises/practice/two-bucket/.meta/config.json b/exercises/practice/two-bucket/.meta/config.json index 4b0227fb..41070f2f 100644 --- a/exercises/practice/two-bucket/.meta/config.json +++ b/exercises/practice/two-bucket/.meta/config.json @@ -1,5 +1,7 @@ { - "authors": ["ryanplusplus"], + "authors": [ + "ryanplusplus" + ], "files": { "solution": [ "two-bucket.lua" diff --git a/exercises/practice/wordy/.meta/config.json b/exercises/practice/wordy/.meta/config.json index 97993ee0..141cb148 100644 --- a/exercises/practice/wordy/.meta/config.json +++ b/exercises/practice/wordy/.meta/config.json @@ -1,5 +1,7 @@ { - "authors": ["ryanplusplus"], + "authors": [ + "ryanplusplus" + ], "files": { "solution": [ "wordy.lua" diff --git a/exercises/practice/yacht/.meta/config.json b/exercises/practice/yacht/.meta/config.json index ae1151c6..985c15d0 100644 --- a/exercises/practice/yacht/.meta/config.json +++ b/exercises/practice/yacht/.meta/config.json @@ -1,5 +1,7 @@ { - "authors": ["ryanplusplus"], + "authors": [ + "ryanplusplus" + ], "files": { "solution": [ "yacht.lua"