Count words in a sentence
Turn this sentence into a small frequency table:
Small steps. Small programs. Clear steps.
The result should count small and steps twice, and programs and clear once. This gives us a concrete contract before writing the loop.
1. Decide what counts as a word
Section titled “1. Decide what counts as a word”For this example, a word is a nonempty piece separated by a space, after lowercasing the sentence and removing periods.
That is intentionally a small rule. It does not handle every punctuation mark, line break, or linguistic definition of a word. Keeping the rule explicit makes the result easy to check.
let text = "Small steps. Small programs. Clear steps."let normalized = text.to_lower().replace(".", "")let words = normalized.split(" ")
print(normalized)print(words.len)This prints the normalized sentence and 6. to_lower and replace produce strings; split produces an Arr[Str]. The array’s length is a field, so it is written words.len.
2. Give each word a count
Section titled “2. Give each word a count”The table is a Map[Str, Int]: string keys, integer values. Create it with Map[Str, Int].new().
For each word, look up its previous count with counts.get(word). The result is an Opt[Int], because the key might not exist yet. unwrap_or(0) gives a new word a starting count of zero.
Then assign previous + 1 through counts[word]. The same operation handles first occurrences and repeated occurrences. There is no separate “insert or update” branch to keep consistent.
Skip an empty word with cont. That handles repeated spaces without counting the empty string.
3. Choose an output order
Section titled “3. Choose an output order”Hash-map traversal order is not an alphabetical or insertion-order contract. For this fixed input, report the known vocabulary in a small array:
["small", "steps", "programs", "clear"]
This keeps the output deterministic and leaves the counting map responsible only for lookup. A general text-analysis tool would need to collect and sort its keys or maintain an explicit order separately; KataScript does not currently supply a general sorting method.
4. Run the complete program
Section titled “4. Run the complete program”let text = "Small steps. Small programs. Clear steps."let words = text.to_lower().replace(".", "").split(" ")let counts = Map[Str, Int].new()for word in words { if word == "" { cont } let previous = counts.get(word).unwrap_or(0) counts[word] = previous + 1}for word in ["small", "steps", "programs", "clear"] { print("{word}: {counts[word]}")}print("distinct words: {counts.len()}")Original example: checked output and syntax tree
These records belong to the original downloadable program. Run the editor above to see results for your changes.
small: 2 steps: 2 programs: 1 clear: 1 distinct words: 4
Original AST
[
{
"node": {
"Let": {
"pattern": {
"node": {
"Binding": {
"node": "text",
"span": [
4,
8
]
}
},
"span": [
4,
8
]
},
"type_ann": null,
"value": {
"node": {
"Str": "Small steps. Small programs. Clear steps."
},
"span": [
11,
54
]
}
}
},
"span": [
0,
54
]
},
{
"node": {
"Let": {
"pattern": {
"node": {
"Binding": {
"node": "words",
"span": [
59,
64
]
}
},
"span": [
59,
64
]
},
"type_ann": null,
"value": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Name": "text"
},
"span": [
67,
71
]
},
"name": "to_lower",
"name_span": [
72,
80
]
}
},
"span": [
67,
80
]
},
"args": [],
"args_span": [
80,
82
]
}
},
"span": [
67,
82
]
},
"name": "replace",
"name_span": [
83,
90
]
}
},
"span": [
67,
90
]
},
"args": [
{
"node": {
"Str": "."
},
"span": [
91,
94
]
},
{
"node": {
"Str": ""
},
"span": [
96,
98
]
}
],
"args_span": [
90,
99
]
}
},
"span": [
67,
99
]
},
"name": "split",
"name_span": [
100,
105
]
}
},
"span": [
67,
105
]
},
"args": [
{
"node": {
"Str": " "
},
"span": [
106,
109
]
}
],
"args_span": [
105,
110
]
}
},
"span": [
67,
110
]
}
}
},
"span": [
55,
110
]
},
{
"node": {
"Let": {
"pattern": {
"node": {
"Binding": {
"node": "counts",
"span": [
115,
121
]
}
},
"span": [
115,
121
]
},
"type_ann": null,
"value": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Map"
},
"span": [
124,
127
]
},
"args": [
{
"node": {
"Name": "Str"
},
"span": [
128,
131
]
},
{
"node": {
"Name": "Int"
},
"span": [
133,
136
]
}
]
}
},
"span": [
124,
137
]
},
"name": "new",
"name_span": [
138,
141
]
}
},
"span": [
124,
141
]
},
"args": [],
"args_span": [
141,
143
]
}
},
"span": [
124,
143
]
}
}
},
"span": [
111,
143
]
},
{
"node": {
"Expr": {
"node": {
"For": {
"pattern": {
"node": {
"Binding": {
"node": "word",
"span": [
148,
152
]
}
},
"span": [
148,
152
]
},
"iter_expr": {
"node": {
"Name": "words"
},
"span": [
156,
161
]
},
"body": [
{
"node": {
"Expr": {
"node": {
"If": {
"cond": {
"node": {
"BinOp": {
"op": "Eq",
"left": {
"node": {
"Name": "word"
},
"span": [
171,
175
]
},
"right": {
"node": {
"Str": ""
},
"span": [
179,
181
]
}
}
},
"span": [
171,
181
]
},
"then_body": [
{
"node": {
"Cont": {
"keyword": [
184,
188
]
}
},
"span": [
184,
188
]
}
],
"else_body": null
}
},
"span": [
168,
190
]
}
},
"span": [
168,
190
]
},
{
"node": {
"Let": {
"pattern": {
"node": {
"Binding": {
"node": "previous",
"span": [
199,
207
]
}
},
"span": [
199,
207
]
},
"type_ann": null,
"value": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Name": "counts"
},
"span": [
210,
216
]
},
"name": "get",
"name_span": [
217,
220
]
}
},
"span": [
210,
220
]
},
"args": [
{
"node": {
"Name": "word"
},
"span": [
221,
225
]
}
],
"args_span": [
220,
226
]
}
},
"span": [
210,
226
]
},
"name": "unwrap_or",
"name_span": [
227,
236
]
}
},
"span": [
210,
236
]
},
"args": [
{
"node": {
"Int": "0"
},
"span": [
237,
238
]
}
],
"args_span": [
236,
239
]
}
},
"span": [
210,
239
]
}
}
},
"span": [
195,
239
]
},
{
"node": {
"Assign": {
"target": {
"Item": {
"object": {
"node": {
"Name": "counts"
},
"span": [
244,
250
]
},
"args": [
{
"node": {
"Name": "word"
},
"span": [
251,
255
]
}
]
}
},
"value": {
"node": {
"BinOp": {
"op": "Add",
"left": {
"node": {
"Name": "previous"
},
"span": [
259,
267
]
},
"right": {
"node": {
"Int": "1"
},
"span": [
270,
271
]
}
}
},
"span": [
259,
271
]
}
}
},
"span": [
244,
271
]
}
]
}
},
"span": [
144,
273
]
}
},
"span": [
144,
273
]
},
{
"node": {
"Expr": {
"node": {
"For": {
"pattern": {
"node": {
"Binding": {
"node": "word",
"span": [
278,
282
]
}
},
"span": [
278,
282
]
},
"iter_expr": {
"node": {
"ArrLit": {
"elements": [
{
"node": {
"Str": "small"
},
"span": [
287,
294
]
},
{
"node": {
"Str": "steps"
},
"span": [
296,
303
]
},
{
"node": {
"Str": "programs"
},
"span": [
305,
315
]
},
{
"node": {
"Str": "clear"
},
"span": [
317,
324
]
}
]
}
},
"span": [
286,
325
]
},
"body": [
{
"node": {
"Expr": {
"node": {
"Call": {
"callee": {
"node": {
"Name": "print"
},
"span": [
332,
337
]
},
"args": [
{
"node": {
"Interp": {
"parts": [
{
"Expr": {
"node": {
"Name": "word"
},
"span": [
340,
344
]
}
},
{
"Lit": ": "
},
{
"Expr": {
"node": {
"Item": {
"object": {
"node": {
"Name": "counts"
},
"span": [
348,
354
]
},
"args": [
{
"node": {
"Name": "word"
},
"span": [
355,
359
]
}
]
}
},
"span": [
348,
360
]
}
}
]
}
},
"span": [
338,
362
]
}
],
"args_span": [
337,
363
]
}
},
"span": [
332,
363
]
}
},
"span": [
332,
363
]
}
]
}
},
"span": [
274,
365
]
}
},
"span": [
274,
365
]
},
{
"node": {
"Expr": {
"node": {
"Call": {
"callee": {
"node": {
"Name": "print"
},
"span": [
366,
371
]
},
"args": [
{
"node": {
"Interp": {
"parts": [
{
"Lit": "distinct words: "
},
{
"Expr": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Name": "counts"
},
"span": [
390,
396
]
},
"name": "len",
"name_span": [
397,
400
]
}
},
"span": [
390,
400
]
},
"args": [],
"args_span": [
400,
402
]
}
},
"span": [
390,
402
]
}
}
]
}
},
"span": [
372,
404
]
}
],
"args_span": [
371,
405
]
}
},
"span": [
366,
405
]
}
},
"span": [
366,
405
]
}
]The loop adds six occurrences across four keys. The final line checks the number of distinct words, while the preceding lines let you verify the individual counts.
The map uses len(), a method; the earlier array used len, a field. The collection reference puts both APIs side by side.
5. Change one rule at a time
Section titled “5. Change one rule at a time”Replace the input with "Small steps. Small programs. Clear steps.". The extra space should leave the output unchanged because the empty piece is skipped.
Next, add another "Small" to the sentence. Its count should increase to three while the distinct-word count stays four. If you add a new word, add it to the reporting array too.
This program mutates one map binding, uses string keys, and never deletes entries. Those choices avoid the current map deletion and numeric-key defects. They are useful boundaries for this example, not a claim that the collection implementation is finished.