diff --git a/config.json b/config.json index f3c1e1b..09ce1d2 100644 --- a/config.json +++ b/config.json @@ -595,6 +595,14 @@ "prerequisites": [], "difficulty": 4 }, + { + "slug": "ocr-numbers", + "name": "OCR Numbers", + "uuid": "e4d5e487-e017-4a3b-8338-28920d7d5a01", + "practices": [], + "prerequisites": [], + "difficulty": 4 + }, { "slug": "perfect-numbers", "name": "Perfect Numbers", diff --git a/exercises/practice/ocr-numbers/.docs/instructions.md b/exercises/practice/ocr-numbers/.docs/instructions.md new file mode 100644 index 0000000..7beb257 --- /dev/null +++ b/exercises/practice/ocr-numbers/.docs/instructions.md @@ -0,0 +1,79 @@ +# Instructions + +Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is represented, or whether it is garbled. + +## Step One + +To begin with, convert a simple binary font to a string containing 0 or 1. + +The binary font uses pipes and underscores, four rows high and three columns wide. + +```text + _ # + | | # zero. + |_| # + # the fourth row is always blank +``` + +Is converted to "0" + +```text + # + | # one. + | # + # (blank fourth row) +``` + +Is converted to "1" + +If the input is the correct size, but not recognizable, your program should return '?' + +If the input is the incorrect size, your program should return an error. + +## Step Two + +Update your program to recognize multi-character binary strings, replacing garbled numbers with ? + +## Step Three + +Update your program to recognize all numbers 0 through 9, both individually and as part of a larger string. + +```text + _ + _| +|_ + +``` + +Is converted to "2" + +```text + _ _ _ _ _ _ _ _ # + | _| _||_||_ |_ ||_||_|| | # decimal numbers. + ||_ _| | _||_| ||_| _||_| # + # fourth line is always blank +``` + +Is converted to "1234567890" + +## Step Four + +Update your program to handle multiple numbers, one per line. +When converting several lines, join the lines with commas. + +```text + _ _ + | _| _| + ||_ _| + + _ _ +|_||_ |_ + | _||_| + + _ _ _ + ||_||_| + ||_| _| + +``` + +Is converted to "123,456,789". diff --git a/exercises/practice/ocr-numbers/.meta/Example.roc b/exercises/practice/ocr-numbers/.meta/Example.roc new file mode 100644 index 0000000..1d2f432 --- /dev/null +++ b/exercises/practice/ocr-numbers/.meta/Example.roc @@ -0,0 +1,72 @@ +module [convert] + +BadGridSize : [HeightWasNotAMultipleOf4, WidthWasNotAMultipleOf3, GridShapeWasNotRectangular] + +convert : Str -> Result Str BadGridSize +convert = \grid -> + if grid == "" then + Ok "" + else + + gridChars = grid |> Str.split "\n" |> List.map Str.toUtf8 + size = checkSize? gridChars + gridChars + |> List.chunksOf 4 # split vertically into groups of 4 rows + |> List.map \rowGroup -> + getDigitGrids rowGroup size.width + |> List.map identifyDigit + |> Str.joinWith "" + |> Str.joinWith "," + |> Ok + +checkSize : List (List U8) -> Result { height : U64, width : U64 } BadGridSize +checkSize = \gridChars -> + height = List.len gridChars + if height % 4 != 0 then + Err HeightWasNotAMultipleOf4 + else + + width = gridChars |> List.map List.len |> List.max |> Result.withDefault 0 + if width % 3 != 0 then + Err WidthWasNotAMultipleOf3 + else + + isRectangular = gridChars |> List.all \row -> List.len row == width + if isRectangular then + Ok { height, width } + else + Err GridShapeWasNotRectangular + +## Given four rows from the full grid, return the 3x4 grid for each digit +getDigitGrids : List (List U8), U64 -> List (List (List U8)) +getDigitGrids = \rowGroup, fullGridWidth -> + chunkedRows = + rowGroup + |> List.map \row -> + row |> List.chunksOf 3 + numHorizontalChunks = fullGridWidth // 3 + List.range { start: At 0, end: Before numHorizontalChunks } + |> List.map \chunkIndex -> + chunkedRows + |> List.map \chunkedRow -> + when chunkedRow |> List.get chunkIndex is + Ok chunk -> chunk + Err OutOfBounds -> crash "Unreachable: we checked the grid size" + +identifyDigit : List (List U8) -> Str +identifyDigit = \digitGrid -> + # _ _ _ _ _ _ _ _ + # | | | _| _||_||_ |_ ||_||_| + # |_| ||_ _| | _||_| ||_| _| + when digitGrid is + [[' ', '_', ' '], ['|', ' ', '|'], ['|', '_', '|'], [' ', ' ', ' ']] -> "0" + [[' ', ' ', ' '], [' ', ' ', '|'], [' ', ' ', '|'], [' ', ' ', ' ']] -> "1" + [[' ', '_', ' '], [' ', '_', '|'], ['|', '_', ' '], [' ', ' ', ' ']] -> "2" + [[' ', '_', ' '], [' ', '_', '|'], [' ', '_', '|'], [' ', ' ', ' ']] -> "3" + [[' ', ' ', ' '], ['|', '_', '|'], [' ', ' ', '|'], [' ', ' ', ' ']] -> "4" + [[' ', '_', ' '], ['|', '_', ' '], [' ', '_', '|'], [' ', ' ', ' ']] -> "5" + [[' ', '_', ' '], ['|', '_', ' '], ['|', '_', '|'], [' ', ' ', ' ']] -> "6" + [[' ', '_', ' '], [' ', ' ', '|'], [' ', ' ', '|'], [' ', ' ', ' ']] -> "7" + [[' ', '_', ' '], ['|', '_', '|'], ['|', '_', '|'], [' ', ' ', ' ']] -> "8" + [[' ', '_', ' '], ['|', '_', '|'], [' ', '_', '|'], [' ', ' ', ' ']] -> "9" + _ -> "?" diff --git a/exercises/practice/ocr-numbers/.meta/config.json b/exercises/practice/ocr-numbers/.meta/config.json new file mode 100644 index 0000000..2ea4f1f --- /dev/null +++ b/exercises/practice/ocr-numbers/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "ageron" + ], + "files": { + "solution": [ + "OcrNumbers.roc" + ], + "test": [ + "ocr-numbers-test.roc" + ], + "example": [ + ".meta/Example.roc" + ] + }, + "blurb": "Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is represented, or whether it is garbled.", + "source": "Inspired by the Bank OCR kata", + "source_url": "https://codingdojo.org/kata/BankOCR/" +} diff --git a/exercises/practice/ocr-numbers/.meta/template.j2 b/exercises/practice/ocr-numbers/.meta/template.j2 new file mode 100644 index 0000000..382ff3c --- /dev/null +++ b/exercises/practice/ocr-numbers/.meta/template.j2 @@ -0,0 +1,19 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} +{{ macros.header() }} + +import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_camel }}] + +{% for case in cases -%} +# {{ case["description"] }} +expect + grid = {{ case["input"]["rows"] | to_roc_multiline_string | indent(8) }} + result = {{ case["property"] | to_camel }} grid + {%- if case["expected"]["error"] %} + result |> Result.isErr + {%- else %} + expected = Ok {{ case["expected"] | to_roc }} + result == expected + {%- endif %} + +{% endfor %} diff --git a/exercises/practice/ocr-numbers/.meta/tests.toml b/exercises/practice/ocr-numbers/.meta/tests.toml new file mode 100644 index 0000000..0d7a5b7 --- /dev/null +++ b/exercises/practice/ocr-numbers/.meta/tests.toml @@ -0,0 +1,61 @@ +# 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. + +[5ee54e1a-b554-4bf3-a056-9a7976c3f7e8] +description = "Recognizes 0" + +[027ada25-17fd-4d78-aee6-35a19623639d] +description = "Recognizes 1" + +[3cce2dbd-01d9-4f94-8fae-419a822e89bb] +description = "Unreadable but correctly sized inputs return ?" + +[cb19b733-4e36-4cf9-a4a1-6e6aac808b9a] +description = "Input with a number of lines that is not a multiple of four raises an error" + +[235f7bd1-991b-4587-98d4-84206eec4cc6] +description = "Input with a number of columns that is not a multiple of three raises an error" + +[4a841794-73c9-4da9-a779-1f9837faff66] +description = "Recognizes 110101100" + +[70c338f9-85b1-4296-a3a8-122901cdfde8] +description = "Garbled numbers in a string are replaced with ?" + +[ea494ff4-3610-44d7-ab7e-72fdef0e0802] +description = "Recognizes 2" + +[1acd2c00-412b-4268-93c2-bd7ff8e05a2c] +description = "Recognizes 3" + +[eaec6a15-be17-4b6d-b895-596fae5d1329] +description = "Recognizes 4" + +[440f397a-f046-4243-a6ca-81ab5406c56e] +description = "Recognizes 5" + +[f4c9cf6a-f1e2-4878-bfc3-9b85b657caa0] +description = "Recognizes 6" + +[e24ebf80-c611-41bb-a25a-ac2c0f232df5] +description = "Recognizes 7" + +[b79cad4f-e264-4818-9d9e-77766792e233] +description = "Recognizes 8" + +[5efc9cfc-9227-4688-b77d-845049299e66] +description = "Recognizes 9" + +[f60cb04a-42be-494e-a535-3451c8e097a4] +description = "Recognizes string of decimal numbers" + +[b73ecf8b-4423-4b36-860d-3710bdb8a491] +description = "Numbers separated by empty lines are recognized. Lines are joined by commas." diff --git a/exercises/practice/ocr-numbers/OcrNumbers.roc b/exercises/practice/ocr-numbers/OcrNumbers.roc new file mode 100644 index 0000000..4b1dea6 --- /dev/null +++ b/exercises/practice/ocr-numbers/OcrNumbers.roc @@ -0,0 +1,5 @@ +module [convert] + +convert : Str -> Result Str _ +convert = \grid -> + crash "Please implement the 'convert' function" diff --git a/exercises/practice/ocr-numbers/ocr-numbers-test.roc b/exercises/practice/ocr-numbers/ocr-numbers-test.roc new file mode 100644 index 0000000..0657f64 --- /dev/null +++ b/exercises/practice/ocr-numbers/ocr-numbers-test.roc @@ -0,0 +1,238 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/ocr-numbers/canonical-data.json +# File last updated on 2024-10-13 +app [main] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br", +} + +main = + Task.ok {} + +import OcrNumbers exposing [convert] + +# Recognizes 0 +expect + grid = + """ + _ + | | + |_| + + """ + result = convert grid + expected = Ok "0" + result == expected + +# Recognizes 1 +expect + grid = + """ + + | + | + + """ + result = convert grid + expected = Ok "1" + result == expected + +# Unreadable but correctly sized inputs return ? +expect + grid = + """ + + _ + | + + """ + result = convert grid + expected = Ok "?" + result == expected + +# Input with a number of lines that is not a multiple of four raises an error +expect + grid = + """ + _ + | | + + """ + result = convert grid + result |> Result.isErr + +# Input with a number of columns that is not a multiple of three raises an error +expect + grid = + """ + + | + | + + """ + result = convert grid + result |> Result.isErr + +# Recognizes 110101100 +expect + grid = + """ + _ _ _ _ + | || | || | | || || | + | ||_| ||_| | ||_||_| + + """ + result = convert grid + expected = Ok "110101100" + result == expected + +# Garbled numbers in a string are replaced with ? +expect + grid = + """ + _ _ _ + | || | || | || || | + | | _| ||_| | ||_||_| + + """ + result = convert grid + expected = Ok "11?10?1?0" + result == expected + +# Recognizes 2 +expect + grid = + """ + _ + _| + |_ + + """ + result = convert grid + expected = Ok "2" + result == expected + +# Recognizes 3 +expect + grid = + """ + _ + _| + _| + + """ + result = convert grid + expected = Ok "3" + result == expected + +# Recognizes 4 +expect + grid = + """ + + |_| + | + + """ + result = convert grid + expected = Ok "4" + result == expected + +# Recognizes 5 +expect + grid = + """ + _ + |_ + _| + + """ + result = convert grid + expected = Ok "5" + result == expected + +# Recognizes 6 +expect + grid = + """ + _ + |_ + |_| + + """ + result = convert grid + expected = Ok "6" + result == expected + +# Recognizes 7 +expect + grid = + """ + _ + | + | + + """ + result = convert grid + expected = Ok "7" + result == expected + +# Recognizes 8 +expect + grid = + """ + _ + |_| + |_| + + """ + result = convert grid + expected = Ok "8" + result == expected + +# Recognizes 9 +expect + grid = + """ + _ + |_| + _| + + """ + result = convert grid + expected = Ok "9" + result == expected + +# Recognizes string of decimal numbers +expect + grid = + """ + _ _ _ _ _ _ _ _ + | _| _||_||_ |_ ||_||_|| | + ||_ _| | _||_| ||_| _||_| + + """ + result = convert grid + expected = Ok "1234567890" + result == expected + +# Numbers separated by empty lines are recognized. Lines are joined by commas. +expect + grid = + """ + _ _ + | _| _| + ||_ _| + + _ _ + |_||_ |_ + | _||_| + + _ _ _ + ||_||_| + ||_| _| + + """ + result = convert grid + expected = Ok "123,456,789" + result == expected +