← run

go-02-word-frequency

0.000
0/1 tests· data
Challenge · difficulty 2/5
# Word frequency

Implement **`solution.go`** in `package challenge` exporting:

```go
func WordFrequency(text string) map[string]int
```

Count how many times each word occurs in `text` and return the counts in a map.

Rules:

- Split `text` into tokens on **whitespace** (spaces, tabs, newlines).
- For each token, strip any **surrounding ASCII punctuation** (leading and trailing).
  Punctuation in the middle of a token is kept.
- **Lowercase** each word before counting.
- If, after stripping, a token is empty, skip it (do not count an empty string).
- For empty or whitespace-only input, return an **empty, non-nil** map (length 0).

ASCII punctuation is the set of characters for which Go's `unicode.IsPunct` returns true
together with symbols such as `+`, `<`, `=`, etc. For this challenge, treat a byte as
"punctuation to strip" when it is an ASCII byte that is **not** a letter or digit.

Examples:

- `WordFrequency("the cat sat on the mat")` →
  `{"the": 2, "cat": 1, "sat": 1, "on": 1, "mat": 1}`
- `WordFrequency("Hello, hello! HELLO.")` → `{"hello": 3}`
- `WordFrequency("don't stop")` → `{"don't": 1, "stop": 1}` (interior apostrophe kept)
- `WordFrequency("   ")` → `{}` (empty, non-nil map)
tests/solution_test.go
package challenge

import (
	"reflect"
	"testing"
)

func TestWordFrequency(t *testing.T) {
	cases := []struct {
		name string
		in   string
		want map[string]int
	}{
		{
			name: "simple repeats",
			in:   "the cat sat on the mat",
			want: map[string]int{"the": 2, "cat": 1, "sat": 1, "on": 1, "mat": 1},
		},
		{
			name: "punctuation and case",
			in:   "Hello, hello! HELLO.",
			want: map[string]int{"hello": 3},
		},
		{
			name: "interior apostrophe kept",
			in:   "don't stop don't",
			want: map[string]int{"don't": 2, "stop": 1},
		},
		{
			name: "tabs and newlines as whitespace",
			in:   "a\tb\nc a",
			want: map[string]int{"a": 2, "b": 1, "c": 1},
		},
		{
			name: "leading and trailing punctuation stripped",
			in:   "(go) [go]; {GO}",
			want: map[string]int{"go": 3},
		},
		{
			name: "digits are words",
			in:   "42 42 forty-two",
			want: map[string]int{"42": 2, "forty-two": 1},
		},
		{
			name: "token that is only punctuation is skipped",
			in:   "hi --- !!! bye",
			want: map[string]int{"hi": 1, "bye": 1},
		},
		{
			name: "empty input",
			in:   "",
			want: map[string]int{},
		},
		{
			name: "whitespace only",
			in:   "   \t \n  ",
			want: map[string]int{},
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			got := WordFrequency(c.in)
			if got == nil {
				t.Fatalf("WordFrequency(%q) returned nil map, want non-nil", c.in)
			}
			if !reflect.DeepEqual(got, c.want) {
				t.Errorf("WordFrequency(%q) = %v, want %v", c.in, got, c.want)
			}
		})
	}
}

func TestWordFrequencyEmptyIsNonNil(t *testing.T) {
	got := WordFrequency("")
	if got == nil {
		t.Fatal("WordFrequency(\"\") = nil, want non-nil empty map")
	}
	if len(got) != 0 {
		t.Errorf("WordFrequency(\"\") = %v, want empty map", got)
	}
}
System prompt
You are an expert programmer. Solve the task exactly as specified. Output your solution as fenced code blocks using the required file name(s) and the exact function/type signatures requested. Prefer correctness; do not include prose outside code unless asked.
Proposed solution
```go
package challenge

import (
	"strings"
	"unicode"
)

func WordFrequency(text string) map[string]int {
	freq := make(map[string]int)
	words := strings.Fields(text)
	for _, word := range words {
		stripped := stripPunctuation(word)
		if stripped == "" {
			continue
		}
		lower := strings.ToLower(stripped)
		freq[lower]++
	}
	return freq
}

func stripPunctuation(s string) string {
	start := 0
	end := len(s) - 1

	// Trim leading punctuation
	for start <= end && isPunctuation(s[start]) {
		start++
	}

	// Trim trailing punctuation
	for end >= start && isPunctuation(s[end]) {
		end--
	}

	if start > end {
		return ""
	}
	return s[start : end+1]
}

func isPunctuation(b byte) bool {
	return b < 'a' || b > 'z' && b < 'A' || b > 'Z' && b < '0' || b > '9'
}
```

Test output (stdout)

{"ImportPath":"challenge [challenge.test]","Action":"build-output","Output":"# challenge [challenge.test]\n"}
{"ImportPath":"challenge [challenge.test]","Action":"build-output","Output":"./solution.go:5:2: \"unicode\" imported and not used\n"}
{"ImportPath":"challenge [challenge.test]","Action":"build-fail"}
{"Time":"2026-07-01T23:34:54.166584059+02:00","Action":"start","Package":"challenge"}
{"Time":"2026-07-01T23:34:54.16664372+02:00","Action":"output","Package":"challenge","Output":"FAIL\tchallenge [build failed]\n"}
{"Time":"2026-07-01T23:34:54.16664875+02:00","Action":"fail","Package":"challenge","Elapsed":0,"FailedBuild":"challenge [challenge.test]"}
Self-repair attempts
[
  {
    "total": 1,
    "answer": "```go\npackage challenge\n\nimport (\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc WordFrequency(text string) map[string]int {\n\tfreq := make(map[string]int)\n\twords := strings.Fields(text)\n\tfor _, word := range words {\n\t\tstripped := stripPunctuation(word)\n\t\tif stripped == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tlower := strings.ToLower(stripped)\n\t\tfreq[lower]++\n\t}\n\treturn freq\n}\n\nfunc stripPunctuation(s string) string {\n\tstart := 0\n\tend := len(s) - 1\n\n\t// Trim leading punctuation\n\tfor start <= end && isPunctuation(s[start]) {\n\t\tstart++\n\t}\n\n\t// Trim trailing punctuation\n\tfor end >= start && isPunctuation(s[end]) {\n\t\tend--\n\t}\n\n\tif start > end {\n\t\treturn \"\"\n\t}\n\treturn s[start : end+1]\n}\n\nfunc isPunctuation(b byte) bool {\n\treturn b < 'a' || b > 'z' && b < 'A' || b > 'Z' && b < '0' || b > '9'\n}\n```",
    "passed": 0,
    "test_error": "{\"ImportPath\":\"challenge [challenge.test]\",\"Action\":\"build-output\",\"Output\":\"# challenge [challenge.test]\\n\"}\n{\"ImportPath\":\"challenge [challenge.test]\",\"Action\":\"build-output\",\"Output\":\"./solution.go:5:2: \\\"unicode\\\" imported and not used\\n\"}\n{\"ImportPath\":\"challenge [challenge.test]\",\"Action\":\"build-fail\"}\n{\"Time\":\"2026-07-01T23:34:54.166584059+02:00\",\"Action\":\"start\",\"Package\":\"challenge\"}\n{\"Time\":\"2026-07-01T23:34:54.16664372+02:00\",\"Action\":\"output\",\"Package\":\"challenge\",\"Output\":\"FAIL\\tchallenge [build failed]\\n\"}\n{\"Time\":\"2026-07-01T23:34:54.16664875+02:00\",\"Action\":\"fail\",\"Package\":\"challenge\",\"Elapsed\":0,\"FailedBuild\":\"challenge [challenge.test]\"}"
  },
  {
    "total": 2,
    "answer": "```go\npackage challenge\n\nimport (\n\t\"strings\"\n)\n\nfunc WordFrequency(text string) map[string]int {\n\tfreq := make(map[string]int)\n\twords := strings.Fields(text)\n\tfor _, word := range words {\n\t\tstripped := stripPunctuation(word)\n\t\tif stripped == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tlower := strings.ToLower(stripped)\n\t\tfreq[lower]++\n\t}\n\treturn freq\n}\n\nfunc stripPunctuation(s string) string {\n\tstart := 0\n\tend := len(s) - 1\n\n\t// Trim leading punctuation\n\tfor start <= end && isPunctuation(s[start]) {\n\t\tstart++\n\t}\n\n\t// Trim trailing punctuation\n\tfor end >= start && isPunctuation(s[end]) {\n\t\tend--\n\t}\n\n\tif start > end {\n\t\treturn \"\"\n\t}\n\treturn s[start : end+1]\n}\n\nfunc isPunctuation(b byte) bool {\n\treturn b < 'a' || b > 'z' && b < 'A' || b > 'Z' && b < '0' || b > '9'\n}\n```",
    "passed": 1,
    "test_error": "T23:35:00.430027138+02:00\",\"Action\":\"run\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequency/empty_input\"}\n{\"Time\":\"2026-07-01T23:35:00.430028408+02:00\",\"Action\":\"output\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequency/empty_input\",\"Output\":\"=== RUN   TestWordFrequency/empty_input\\n\"}\n{\"Time\":\"2026-07-01T23:35:00.430030678+02:00\",\"Action\":\"output\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequency/empty_input\",\"Output\":\"--- PASS: TestWordFrequency/empty_input (0.00s)\\n\"}\n{\"Time\":\"2026-07-01T23:35:00.430032248+02:00\",\"Action\":\"pass\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequency/empty_input\",\"Elapsed\":0}\n{\"Time\":\"2026-07-01T23:35:00.430034108+02:00\",\"Action\":\"run\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequency/whitespace_only\"}\n{\"Time\":\"2026-07-01T23:35:00.430035688+02:00\",\"Action\":\"output\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequency/whitespace_only\",\"Output\":\"=== RUN   TestWordFrequency/whitespace_only\\n\"}\n{\"Time\":\"2026-07-01T23:35:00.430038779+02:00\",\"Action\":\"output\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequency/whitespace_only\",\"Output\":\"--- PASS: TestWordFrequency/whitespace_only (0.00s)\\n\"}\n{\"Time\":\"2026-07-01T23:35:00.430040799+02:00\",\"Action\":\"pass\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequency/whitespace_only\",\"Elapsed\":0}\n{\"Time\":\"2026-07-01T23:35:00.430042619+02:00\",\"Action\":\"output\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequency\",\"Output\":\"--- FAIL: TestWordFrequency (0.00s)\\n\"}\n{\"Time\":\"2026-07-01T23:35:00.430044229+02:00\",\"Action\":\"fail\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequency\",\"Elapsed\":0}\n{\"Time\":\"2026-07-01T23:35:00.430045869+02:00\",\"Action\":\"run\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequencyEmptyIsNonNil\"}\n{\"Time\":\"2026-07-01T23:35:00.430047059+02:00\",\"Action\":\"output\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequencyEmptyIsNonNil\",\"Output\":\"=== RUN   TestWordFrequencyEmptyIsNonNil\\n\"}\n{\"Time\":\"2026-07-01T23:35:00.430051949+02:00\",\"Action\":\"output\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequencyEmptyIsNonNil\",\"Output\":\"--- PASS: TestWordFrequencyEmptyIsNonNil (0.00s)\\n\"}\n{\"Time\":\"2026-07-01T23:35:00.430054089+02:00\",\"Action\":\"pass\",\"Package\":\"challenge\",\"Test\":\"TestWordFrequencyEmptyIsNonNil\",\"Elapsed\":0}\n{\"Time\":\"2026-07-01T23:35:00.430055929+02:00\",\"Action\":\"output\",\"Package\":\"challenge\",\"Output\":\"FAIL\\n\"}\n{\"Time\":\"2026-07-01T23:35:00.43025515+02:00\",\"Action\":\"output\",\"Package\":\"challenge\",\"Output\":\"FAIL\\tchallenge\\t0.001s\\n\"}\n{\"Time\":\"2026-07-01T23:35:00.43026826+02:00\",\"Action\":\"fail\",\"Package\":\"challenge\",\"Elapsed\":0.001}"
  },
  {
    "total": 2,
    "answer": "```go\npackage challenge\n\nimport (\n\t\"strings\"\n)\n\nfunc WordFrequency(text string) map[string]int {\n\tfreq := make(map[string]int)\n\twords := strings.Fields(text)\n\tfor _, word := range words {\n\t\tstripped := stripPunctuation(word)\n\t\tif stripped == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tlower := strings.ToLower(stripped)\n\t\tfreq[lower]++\n\t}\n\treturn freq\n}\n\nfunc stripPunctuation(s string) string {\n\tstart := 0\n\tend := len(s) - 1\n\n\t// Trim leading punctuation\n\tfor start <= end && isPunctuation(s[start]) {\n\t\tstart++\n\t}\n\n\t// Trim trailing punctuation\n\tfor end >= start && isPunctuation(s[end]) {\n\t\tend--\n\t}\n\n\tif start > end {\n\t\treturn \"\"\n\t}\n\treturn s[start : end+1]\n}\n\nfunc isPunctuation(b byte) bool {\n\treturn b < 'a' || b > 'z' && b < 'A' || b > 'Z' && b < '0' || b > '9'\n}\n```",
    "passed": 1,
    "test_error": ""
  }
]