diff --git a/config.json b/config.json index 745b61777..4f1adc9e7 100644 --- a/config.json +++ b/config.json @@ -40,6 +40,7 @@ "circular-buffer", "robot-name", "react", + "counter", "custom-set", "atbash-cipher", "phone-number", diff --git a/counter/counter_test.go b/counter/counter_test.go new file mode 100644 index 000000000..90ebbe5a6 --- /dev/null +++ b/counter/counter_test.go @@ -0,0 +1,3 @@ +package counter + +// Define your tests here diff --git a/counter/example.go b/counter/example.go new file mode 100644 index 000000000..1cba4b9ee --- /dev/null +++ b/counter/example.go @@ -0,0 +1,63 @@ +package counter + +// NOTE: This file is called example.go and not example_test.go because if it's +// called example_test.go it will get picked up by the test suite and fail +// because COUNTER_IMPL isn't set (see maker.go). + +import "testing" + +func TestNoAdd(t *testing.T) { + counter := makeCounter() + if counter.Lines() != 0 { + t.Errorf("Lines mismatch: got %d, expected %d", counter.Lines(), 0) + } + if counter.Letters() != 0 { + t.Errorf("Letters mismatch: got %d, expected %d", counter.Letters(), 0) + } + if counter.Characters() != 0 { + t.Errorf("Characters mismatch: got %d, expected %d", counter.Characters(), 0) + } +} + +func TestEmptyString(t *testing.T) { + counter := makeCounter() + counter.AddString("") + if counter.Lines() != 0 { + t.Errorf("Lines mismatch: got %d, expected %d", counter.Lines(), 0) + } + if counter.Letters() != 0 { + t.Errorf("Letters mismatch: got %d, expected %d", counter.Letters(), 0) + } + if counter.Characters() != 0 { + t.Errorf("Characters mismatch: got %d, expected %d", counter.Characters(), 0) + } +} + +func TestASCIIString(t *testing.T) { + counter := makeCounter() + counter.AddString("Hello\nworld!") + if counter.Lines() != 2 { + t.Errorf("Lines mismatch: got %d, expected %d", counter.Lines(), 2) + } + if counter.Letters() != 10 { + t.Errorf("Letters mismatch: got %d, expected %d", counter.Letters(), 10) + } + if counter.Characters() != 12 { + t.Errorf("Characters mismatch: got %d, expected %d", counter.Characters(), 12) + } +} + +func TestRussianString(t *testing.T) { + counter := makeCounter() + // Lifted this translation from the ru.po file of GNU hello + counter.AddString("здравствуй, мир\n") + if counter.Lines() != 1 { + t.Errorf("Lines mismatch: got %d, expected %d", counter.Lines(), 2) + } + if counter.Letters() != 13 { + t.Errorf("Letters mismatch: got %d, expected %d", counter.Letters(), 10) + } + if counter.Characters() != 16 { + t.Errorf("Characters mismatch: got %d, expected %d", counter.Characters(), 12) + } +} diff --git a/counter/impl1.go b/counter/impl1.go new file mode 100644 index 000000000..e279510b5 --- /dev/null +++ b/counter/impl1.go @@ -0,0 +1,23 @@ +package counter + +import "unicode" + +// Incorrect implementation: wrongly counts lines. +type Impl1 struct { + lines, characters, letters int +} + +func (c *Impl1) AddString(s string) { + for _, char := range s { + if char == '\n' { + c.lines++ + } else if unicode.IsLetter(char) { + c.letters++ + } + c.characters++ + } +} + +func (c Impl1) Lines() int { return c.lines } +func (c Impl1) Letters() int { return c.letters } +func (c Impl1) Characters() int { return c.characters } diff --git a/counter/impl2.go b/counter/impl2.go new file mode 100644 index 000000000..0ed404153 --- /dev/null +++ b/counter/impl2.go @@ -0,0 +1,33 @@ +package counter + +// Incorrect implementation: wrongly determines characters. +type Impl2 struct { + newlines, characters, letters int + lastChar rune +} + +func (c *Impl2) AddString(s string) { + for _, char := range s { + c.lastChar = char + if char == '\n' { + c.newlines++ + } else if (char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') { + c.letters++ + } + c.characters++ + } +} + +func (c Impl2) Lines() int { + switch { + case c.characters == 0: + return 0 + case c.lastChar == '\n': + return c.newlines + default: + return c.newlines + 1 + } +} + +func (c Impl2) Letters() int { return c.letters } +func (c Impl2) Characters() int { return c.characters } diff --git a/counter/impl3.go b/counter/impl3.go new file mode 100644 index 000000000..36c9125bd --- /dev/null +++ b/counter/impl3.go @@ -0,0 +1,36 @@ +package counter + +import "unicode" + +// Incorrect implementation: assumes ASCII. +type Impl3 struct { + newlines, characters, letters int + lastChar rune +} + +func (c *Impl3) AddString(s string) { + for i := 0; i < len(s); i++ { + char := rune(s[i]) + c.lastChar = char + if char == '\n' { + c.newlines++ + } else if unicode.IsLetter(char) { + c.letters++ + } + c.characters++ + } +} + +func (c Impl3) Lines() int { + switch { + case c.characters == 0: + return 0 + case c.lastChar == '\n': + return c.newlines + default: + return c.newlines + 1 + } +} + +func (c Impl3) Letters() int { return c.letters } +func (c Impl3) Characters() int { return c.characters } diff --git a/counter/impl4.go b/counter/impl4.go new file mode 100644 index 000000000..7081eefd3 --- /dev/null +++ b/counter/impl4.go @@ -0,0 +1,35 @@ +package counter + +import "unicode" + +// Correct implementation +type Impl4 struct { + newlines, characters, letters int + lastChar rune +} + +func (c *Impl4) AddString(s string) { + for _, char := range s { + c.lastChar = char + if char == '\n' { + c.newlines++ + } else if unicode.IsLetter(char) { + c.letters++ + } + c.characters++ + } +} + +func (c Impl4) Lines() int { + switch { + case c.characters == 0: + return 0 + case c.lastChar == '\n': + return c.newlines + default: + return c.newlines + 1 + } +} + +func (c Impl4) Letters() int { return c.letters } +func (c Impl4) Characters() int { return c.characters } diff --git a/counter/interface.go b/counter/interface.go new file mode 100644 index 000000000..5286a9e74 --- /dev/null +++ b/counter/interface.go @@ -0,0 +1,9 @@ +package counter + +type Counter interface { + AddString(string) + + Lines() int + Letters() int + Characters() int +} diff --git a/counter/maker.go b/counter/maker.go new file mode 100644 index 000000000..674f17dcd --- /dev/null +++ b/counter/maker.go @@ -0,0 +1,29 @@ +package counter + +import ( + "log" + "os" +) + +// A little trick to help test the various versions, +// set the COUNTER_IMPL environment variable to the +// number of the implementation you want to test. +// E.g. run `COUNTER_IMPL=4 go test` to get Impl4. + +func makeCounter() Counter { + switch os.Getenv("COUNTER_IMPL") { + case "1": + return &Impl1{} + case "2": + return &Impl2{} + case "3": + return &Impl3{} + case "4": + return &Impl4{} + case "": + log.Fatalf("Don't forget to set COUNTER_IMPL") + default: + log.Fatalf("Unknown COUNTER_IMPL value: %s", os.Getenv("COUNTER_IMPL")) + } + panic("not reachable, but go's return analysis needs it") +}