diff --git a/config.json b/config.json index 21fcfea..f130212 100644 --- a/config.json +++ b/config.json @@ -422,6 +422,14 @@ "practices": [], "prerequisites": [], "difficulty": 4 + }, + { + "slug": "word-count", + "name": "Word Count", + "uuid": "4b927a16-b0f2-4b71-980f-4d4f36288814", + "practices": [], + "prerequisites": [], + "difficulty": 4 } ] }, diff --git a/exercises/practice/word-count/.busted b/exercises/practice/word-count/.busted new file mode 100644 index 0000000..86b84e7 --- /dev/null +++ b/exercises/practice/word-count/.busted @@ -0,0 +1,5 @@ +return { + default = { + ROOT = { '.' } + } +} diff --git a/exercises/practice/word-count/.docs/instructions.md b/exercises/practice/word-count/.docs/instructions.md new file mode 100644 index 0000000..064393c --- /dev/null +++ b/exercises/practice/word-count/.docs/instructions.md @@ -0,0 +1,47 @@ +# Instructions + +Your task is to count how many times each word occurs in a subtitle of a drama. + +The subtitles from these dramas use only ASCII characters. + +The characters often speak in casual English, using contractions like _they're_ or _it's_. +Though these contractions come from two words (e.g. _we are_), the contraction (_we're_) is considered a single word. + +Words can be separated by any form of punctuation (e.g. ":", "!", or "?") or whitespace (e.g. "\t", "\n", or " "). +The only punctuation that does not separate words is the apostrophe in contractions. + +Numbers are considered words. +If the subtitles say _It costs 100 dollars._ then _100_ will be its own word. + +Words are case insensitive. +For example, the word _you_ occurs three times in the following sentence: + +> You come back, you hear me? DO YOU HEAR ME? + +The ordering of the word counts in the results doesn't matter. + +Here's an example that incorporates several of the elements discussed above: + +- simple words +- contractions +- numbers +- case insensitive words +- punctuation (including apostrophes) to separate words +- different forms of whitespace to separate words + +`"That's the password: 'PASSWORD 123'!", cried the Special Agent.\nSo I fled.` + +The mapping for this subtitle would be: + +```text +123: 1 +agent: 1 +cried: 1 +fled: 1 +i: 1 +password: 2 +so: 1 +special: 1 +that's: 1 +the: 2 +``` diff --git a/exercises/practice/word-count/.docs/introduction.md b/exercises/practice/word-count/.docs/introduction.md new file mode 100644 index 0000000..1654508 --- /dev/null +++ b/exercises/practice/word-count/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You teach English as a foreign language to high school students. + +You've decided to base your entire curriculum on TV shows. +You need to analyze which words are used, and how often they're repeated. + +This will let you choose the simplest shows to start with, and to gradually increase the difficulty as time passes. diff --git a/exercises/practice/word-count/.meta/config.json b/exercises/practice/word-count/.meta/config.json new file mode 100644 index 0000000..f39edf8 --- /dev/null +++ b/exercises/practice/word-count/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "word_count.moon" + ], + "test": [ + "word_count_spec.moon" + ], + "example": [ + ".meta/example.moon" + ] + }, + "blurb": "Given a phrase, count the occurrences of each word in that phrase.", + "source": "This is a classic toy problem, but we were reminded of it by seeing it in the Go Tour." +} diff --git a/exercises/practice/word-count/.meta/example.moon b/exercises/practice/word-count/.meta/example.moon new file mode 100644 index 0000000..30fb9c9 --- /dev/null +++ b/exercises/practice/word-count/.meta/example.moon @@ -0,0 +1,10 @@ +trim = (str) -> str\gsub("^'+", '')\gsub("'+$", '') + +{ + count_words: (sentence) -> + result = {} + for word in sentence\lower!\gmatch("[%w+']+") + w = trim word + result[w] = (result[w] or 0) + 1 + result +} diff --git a/exercises/practice/word-count/.meta/spec_generator.moon b/exercises/practice/word-count/.meta/spec_generator.moon new file mode 100644 index 0000000..e17389d --- /dev/null +++ b/exercises/practice/word-count/.meta/spec_generator.moon @@ -0,0 +1,39 @@ +json = require 'dkjson' + +kv_table = (tbl, level) -> + lines = {'{'} + for k, v in pairs tbl + key = if k\match('^%a%w*$') then k else "#{quote k}" + table.insert lines, indent "#{key}: #{v},", level + 1 + table.insert lines, indent '}', level + table.concat lines, '\n' + + +{ + module_imports: {'count_words'}, + + test_helpers: [[ + -- ---------------------------------------------------------- + same_kv = (state, arguments) -> + expected = arguments[1] + actual = arguments[2] + return false if #expected != #actual + for k, v in pairs expected + return false if actual[k] != v + true + + say = require 'say' + say\set 'assertion.same_kv.positive', 'Expected\n%s\nto have the same keys and values as\n%s' + say\set 'assertion.same_kv.negative', 'Expected\n%s\nto not have the same keys and values as\n%s' + assert\register 'assertion', 'same_kv', same_kv, 'assertion.same_kv.positive', 'assertion.same_kv.negative' + -- ---------------------------------------------------------- +]] + + generate_test: (case, level) -> + lines = { + "result = count_words #{json.encode case.input.sentence}", + "expected = #{kv_table case.expected, level}", + "assert.has.same_kv expected, result" + } + table.concat [indent line, level for line in *lines], '\n' +} diff --git a/exercises/practice/word-count/.meta/tests.toml b/exercises/practice/word-count/.meta/tests.toml new file mode 100644 index 0000000..1be425b --- /dev/null +++ b/exercises/practice/word-count/.meta/tests.toml @@ -0,0 +1,57 @@ +# 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. + +[61559d5f-2cad-48fb-af53-d3973a9ee9ef] +description = "count one word" + +[5abd53a3-1aed-43a4-a15a-29f88c09cbbd] +description = "count one of each word" + +[2a3091e5-952e-4099-9fac-8f85d9655c0e] +description = "multiple occurrences of a word" + +[e81877ae-d4da-4af4-931c-d923cd621ca6] +description = "handles cramped lists" + +[7349f682-9707-47c0-a9af-be56e1e7ff30] +description = "handles expanded lists" + +[a514a0f2-8589-4279-8892-887f76a14c82] +description = "ignore punctuation" + +[d2e5cee6-d2ec-497b-bdc9-3ebe092ce55e] +description = "include numbers" + +[dac6bc6a-21ae-4954-945d-d7f716392dbf] +description = "normalize case" + +[4185a902-bdb0-4074-864c-f416e42a0f19] +description = "with apostrophes" +include = false + +[4ff6c7d7-fcfc-43ef-b8e7-34ff1837a2d3] +description = "with apostrophes" +reimplements = "4185a902-bdb0-4074-864c-f416e42a0f19" + +[be72af2b-8afe-4337-b151-b297202e4a7b] +description = "with quotations" + +[8d6815fe-8a51-4a65-96f9-2fb3f6dc6ed6] +description = "substrings from the beginning" + +[c5f4ef26-f3f7-4725-b314-855c04fb4c13] +description = "multiple spaces not detected as a word" + +[50176e8a-fe8e-4f4c-b6b6-aa9cf8f20360] +description = "alternating word separators not detected as a word" + +[6d00f1db-901c-4bec-9829-d20eb3044557] +description = "quotation for word with apostrophe" diff --git a/exercises/practice/word-count/word_count.moon b/exercises/practice/word-count/word_count.moon new file mode 100644 index 0000000..604fd4e --- /dev/null +++ b/exercises/practice/word-count/word_count.moon @@ -0,0 +1,4 @@ +{ + count_words: (sentence) -> + error 'Implement me' +} diff --git a/exercises/practice/word-count/word_count_spec.moon b/exercises/practice/word-count/word_count_spec.moon new file mode 100644 index 0000000..a6877e3 --- /dev/null +++ b/exercises/practice/word-count/word_count_spec.moon @@ -0,0 +1,155 @@ +import count_words from require 'word_count' + +describe 'word-count', -> + -- ---------------------------------------------------------- + same_kv = (state, arguments) -> + expected = arguments[1] + actual = arguments[2] + return false if #expected != #actual + for k, v in pairs expected + return false if actual[k] != v + true + + say = require 'say' + say\set 'assertion.same_kv.positive', 'Expected\n%s\nto have the same keys and values as\n%s' + say\set 'assertion.same_kv.negative', 'Expected\n%s\nto not have the same keys and values as\n%s' + assert\register 'assertion', 'same_kv', same_kv, 'assertion.same_kv.positive', 'assertion.same_kv.negative' + -- ---------------------------------------------------------- + + it 'count one word', -> + result = count_words "word" + expected = { + word: 1, + } + assert.has.same_kv expected, result + + pending 'count one of each word', -> + result = count_words "one of each" + expected = { + each: 1, + one: 1, + of: 1, + } + assert.has.same_kv expected, result + + pending 'multiple occurrences of a word', -> + result = count_words "one fish two fish red fish blue fish" + expected = { + fish: 4, + blue: 1, + one: 1, + two: 1, + red: 1, + } + assert.has.same_kv expected, result + + pending 'handles cramped lists', -> + result = count_words "one,two,three" + expected = { + two: 1, + one: 1, + three: 1, + } + assert.has.same_kv expected, result + + pending 'handles expanded lists', -> + result = count_words "one,\ntwo,\nthree" + expected = { + two: 1, + one: 1, + three: 1, + } + assert.has.same_kv expected, result + + pending 'ignore punctuation', -> + result = count_words "car: carpet as java: javascript!!&@$%^&" + expected = { + car: 1, + as: 1, + java: 1, + javascript: 1, + carpet: 1, + } + assert.has.same_kv expected, result + + pending 'include numbers', -> + result = count_words "testing, 1, 2 testing" + expected = { + testing: 2, + '2': 1, + '1': 1, + } + assert.has.same_kv expected, result + + pending 'normalize case', -> + result = count_words "go Go GO Stop stop" + expected = { + go: 3, + stop: 2, + } + assert.has.same_kv expected, result + + pending 'with apostrophes', -> + result = count_words "'First: don't laugh. Then: don't cry. You're getting it.'" + expected = { + then: 1, + laugh: 1, + "don't": 2, + it: 1, + first: 1, + getting: 1, + "you're": 1, + cry: 1, + } + assert.has.same_kv expected, result + + pending 'with quotations', -> + result = count_words "Joe can't tell between 'large' and large." + expected = { + joe: 1, + tell: 1, + large: 2, + and: 1, + between: 1, + "can't": 1, + } + assert.has.same_kv expected, result + + pending 'substrings from the beginning', -> + result = count_words "Joe can't tell between app, apple and a." + expected = { + a: 1, + and: 1, + joe: 1, + apple: 1, + tell: 1, + app: 1, + between: 1, + "can't": 1, + } + assert.has.same_kv expected, result + + pending 'multiple spaces not detected as a word', -> + result = count_words " multiple whitespaces" + expected = { + multiple: 1, + whitespaces: 1, + } + assert.has.same_kv expected, result + + pending 'alternating word separators not detected as a word', -> + result = count_words ",\n,one,\n ,two \n 'three'" + expected = { + two: 1, + one: 1, + three: 1, + } + assert.has.same_kv expected, result + + pending 'quotation for word with apostrophe', -> + result = count_words "can, can't, 'can't'" + expected = { + "can't": 2, + can: 1, + } + assert.has.same_kv expected, result