diff --git a/go/current b/go/current index eff4962..ab81e1c 120000 --- a/go/current +++ b/go/current @@ -1 +1 @@ -hexadecimal \ No newline at end of file +binary-search-tree \ No newline at end of file diff --git a/go/kindergarten-garden/README.md b/go/kindergarten-garden/README.md new file mode 100644 index 0000000..5a38784 --- /dev/null +++ b/go/kindergarten-garden/README.md @@ -0,0 +1,77 @@ +# Kindergarten Garden + +Write a program that, given a diagram, can tell you which plants each child in the kindergarten class is responsible for. + +The kindergarten class is learning about growing plants. The teachers +thought it would be a good idea to give them actual seeds, plant them in +actual dirt, and grow actual plants. + +They've chosen to grow grass, clover, radishes, and violets. + +To this end, they've put little styrofoam cups along the window sills, +and planted one type of plant in each cup, choosing randomly from the +available types of seeds. + +```plain +[window][window][window] +........................ # each dot represents a styrofoam cup +........................ +``` + +There are 12 children in the class: + +- Alice, Bob, Charlie, David, +- Eve, Fred, Ginny, Harriet, +- Ileana, Joseph, Kincaid, and Larry. + +Each child gets 4 cups, two on each row. The children are assigned to +cups in alphabetical order. + +The following diagram represents Alice's plants: + +```plain +[window][window][window] +VR...................... +RG...................... +``` + +So in the row nearest the window, she has a violet and a radish; in the +row behind that, she has a radish and some grass. + +Your program will be given the plants from left-to-right starting with +the row nearest the windows. From this, it should be able to determine +which plants belong to which students. + +For example, if it's told that the garden looks like so: + +```plain +[window][window][window] +VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV +``` + +Then if asked for Alice's plants, it should provide: + +- Violets, radishes, violets, radishes + +While asking for Bob's plants would yield: + +- Clover, grass, clover, clover + +To run the tests simply run the command `go test` in the exercise directory. + +If the test suite contains benchmarks, you can run these with the `-bench` +flag: + + go test -bench . + +For more detailed info about the Go track see the [help +page](http://exercism.io/languages/go). + +## Source + +Random musings during airplane trip. [http://jumpstartlab.com](http://jumpstartlab.com) + +## Submitting Incomplete Problems +It's possible to submit an incomplete solution so you can see how others have completed the exercise. + diff --git a/go/kindergarten-garden/kindergarten.go b/go/kindergarten-garden/kindergarten.go new file mode 100644 index 0000000..58dab55 --- /dev/null +++ b/go/kindergarten-garden/kindergarten.go @@ -0,0 +1,93 @@ +package kindergarten + +import ( + "errors" + "sort" + "strings" +) + +type Garden struct { + children []string + rows []string +} + +func NewGarden(diagram string, children []string) (*Garden, error) { + var err error + g := new(Garden) + g.rows = strings.Split(diagram[1:], "\n") + g.children = children + if diagram[0] != '\n' { + err = errors.New("Wrong diagram format") + } else if len(g.rows) == 0 { + err = errors.New("No rows") + } else if len(g.children) == 0 { + err = errors.New("No children") + } else if len(g.rows[0]) != len(g.rows[1]) { + err = errors.New("Mismatched rows") + } else { + for i := range g.rows { + if len(g.rows[i])%2 != 0 { + err = errors.New("Odd number of cups") + break + } + for j := range g.rows[i] { + tst := g.rows[i][j] + switch tst { + case 'R': + case 'C': + case 'G': + case 'V': + default: + err = errors.New("Invalid Cup Codes") + } + } + if err != nil { + break + } + } + for i := range g.children { + if i > 0 { + if g.children[i] == g.children[i-1] { + err = errors.New("Duplicate Children") + break + } + } + } + } + return g, err +} + +func (g *Garden) Plants(child string) ([]string, bool) { + var i int + var ret []string + var found bool + c := g.children + sort.Strings(c) + for i = 0; i < len(c); i++ { + if c[i] == child { + found = true + break + } + } + if !found || len(g.rows) == 0 || len(g.rows[0]) <= 2*i { + return ret, found + } + for j := range []int{0, 1} { + for k := range []int{0, 1} { + switch g.rows[j][(i*2 + k)] { + case 'G': + ret = append(ret, "grass") + case 'C': + ret = append(ret, "clover") + case 'R': + ret = append(ret, "radishes") + case 'V': + ret = append(ret, "violets") + } + } + } + return ret, found +} + +// kindergarten_garden_test.go:140: Garden 6 lookup Patricia = +// [radishes radishes grass clover], want [violets clover radishes violets]. diff --git a/go/kindergarten-garden/kindergarten_garden_test.go b/go/kindergarten-garden/kindergarten_garden_test.go new file mode 100644 index 0000000..df7fa5f --- /dev/null +++ b/go/kindergarten-garden/kindergarten_garden_test.go @@ -0,0 +1,211 @@ +// Kindergarten garden +// +// You must define a type Garden with constructor +// +// func NewGarden(diagram string, children []string) (*Garden, error) +// +// and method +// +// func (g *Garden) Plants(child string) ([]string, bool) +// +// The diagram argument starts each row with a '\n'. This allows Go's +// raw string literals to present diagrams in source code nicely as two +// rows flush left, for example, +// +// diagram := ` +// VVCCGG +// VVCCGG` + +package kindergarten + +import ( + "reflect" + "sort" + "testing" +) + +type lookup struct { + child string + plants []string + ok bool +} + +type gardenTest struct { + number int + diagram string + children []string + ok bool + lookups []lookup +} + +var tests = []gardenTest{ + {1, ` +RC +GG`, []string{"Alice"}, true, []lookup{ + {"Alice", []string{"radishes", "clover", "grass", "grass"}, true}, + }}, + {2, ` +VC +RC`, []string{"Alice"}, true, []lookup{ + {"Alice", []string{"violets", "clover", "radishes", "clover"}, true}, + }}, + {3, ` +VVCG +VVRC`, []string{"Alice", "Bob"}, true, []lookup{ + {"Bob", []string{"clover", "grass", "radishes", "clover"}, true}, + }}, + {4, ` +VVCCGG +VVCCGG`, []string{"Alice", "Bob", "Charlie"}, true, []lookup{ + {"Bob", []string{"clover", "clover", "clover", "clover"}, true}, + {"Charlie", []string{"grass", "grass", "grass", "grass"}, true}, + }}, + test5, // full garden test + test6, // out of order names test + + // failure tests + {7, "RC\nGG", []string{"Alice"}, false, nil}, // wrong diagram format + {8, ` +RCCC +GG`, []string{""}, false, nil}, // mismatched rows + {9, ` +RCC +GGC`, []string{"Alice"}, false, nil}, // odd number of cups + {10, ` +RCCC +GGCC`, []string{"Alice", "Alice"}, false, nil}, // duplicate name + {11, ` +rc +gg`, []string{"Alice"}, false, nil}, // invaid cup codes + {12, ` +RC +GG`, []string{"Alice"}, true, []lookup{ // lookup invalid name + {"Bob", []string{"radishes", "clover", "grass", "grass"}, false}, + }}, +} + +// full garden test +var test5 = gardenTest{5, ` +VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV`, []string{ + "Alice", "Bob", "Charlie", "David", "Eve", "Fred", + "Ginny", "Harriet", "Ileana", "Joseph", "Kincaid", "Larry"}, true, []lookup{ + {"Alice", []string{"violets", "radishes", "violets", "radishes"}, true}, + {"Bob", []string{"clover", "grass", "clover", "clover"}, true}, + {"Charlie", []string{"violets", "violets", "clover", "grass"}, true}, + {"David", []string{"radishes", "violets", "clover", "radishes"}, true}, + {"Eve", []string{"clover", "grass", "radishes", "grass"}, true}, + {"Fred", []string{"grass", "clover", "violets", "clover"}, true}, + {"Ginny", []string{"clover", "grass", "grass", "clover"}, true}, + {"Harriet", []string{"violets", "radishes", "radishes", "violets"}, true}, + {"Ileana", []string{"grass", "clover", "violets", "clover"}, true}, + {"Joseph", []string{"violets", "clover", "violets", "grass"}, true}, + {"Kincaid", []string{"grass", "clover", "clover", "grass"}, true}, + {"Larry", []string{"grass", "violets", "clover", "violets"}, true}, +}} + +// out of order names test +var ( + test6names = []string{"Samantha", "Patricia", "Xander", "Roger"} + test6 = gardenTest{6, ` +VCRRGVRG +RVGCCGCV`, append([]string{}, test6names...), true, []lookup{ + {"Patricia", []string{"violets", "clover", "radishes", "violets"}, true}, + {"Roger", []string{"radishes", "radishes", "grass", "clover"}, true}, + {"Samantha", []string{"grass", "violets", "clover", "grass"}, true}, + {"Xander", []string{"radishes", "grass", "clover", "violets"}, true}, + }} +) + +func TestGarden(t *testing.T) { + for _, test := range tests { + g, err := NewGarden(test.diagram, test.children) + switch { + case err != nil: + if test.ok { + t.Fatalf("NewGarden test %d returned error %q. Error not expected.", + test.number, err) + } + case !test.ok: + t.Fatalf("NewGarden test %d error = %v. Expected error.", + test.number, err) + } + for _, l := range test.lookups { + switch plants, ok := g.Plants(l.child); { + case ok != l.ok: + t.Fatalf("Garden %d lookup %s returned ok = %t, want %t.", + test.number, l.child, ok, l.ok) + case ok && !reflect.DeepEqual(plants, l.plants): + t.Fatalf("Garden %d lookup %s = %v, want %v.", + test.number, l.child, plants, l.plants) + } + } + } +} + +// The lazy way to meet the alphabetizing requirement is with sort.Strings +// on the argument slice. That's an in-place sort though and it's bad practice +// to have a side effect. +func TestNamesNotModified(t *testing.T) { + cp := append([]string{}, test6names...) + _, err := NewGarden(test6.diagram, cp) + if err != nil { + t.Skip("TestNamesNotModified requires valid garden") + } + if !reflect.DeepEqual(cp, test6names) { + t.Fatalf("NewGarden modified children argment. " + + "Arguments should not be modified.") + } + sort.Strings(cp) + if reflect.DeepEqual(cp, test6names) { + t.Skip("TestNamesNotModified requires names out of order") + } +} + +// A test taken from the Ruby tests. It checks that Garden objects +// are self-contained and do not rely on package variables. +func TestTwoGardens(t *testing.T) { + diagram := ` +VCRRGVRG +RVGCCGCV` + g1, err1 := NewGarden(diagram, []string{"Alice", "Bob", "Charlie", "Dan"}) + g2, err2 := NewGarden(diagram, []string{"Bob", "Charlie", "Dan", "Erin"}) + if err1 != nil || err2 != nil { + t.Skip("Two garden test needs valid gardens") + } + tf := func(g *Garden, n int, child string, expPlants []string) { + switch plants, ok := g.Plants(child); { + case !ok: + t.Skip("Garden %d lookup %s returned ok = false, want true.", + n, child) + case !reflect.DeepEqual(plants, expPlants): + t.Fatalf("Garden %d lookup %s = %v, want %v.", + n, child, plants, expPlants) + } + } + tf(g1, 1, "Bob", []string{"radishes", "radishes", "grass", "clover"}) + tf(g2, 2, "Bob", []string{"violets", "clover", "radishes", "violets"}) + tf(g1, 1, "Charlie", []string{"grass", "violets", "clover", "grass"}) + tf(g2, 2, "Charlie", []string{"radishes", "radishes", "grass", "clover"}) +} + +func BenchmarkNewGarden(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, test := range tests { + NewGarden(test.diagram, test.children) + } + } +} + +func BenchmarkGarden_Plants(b *testing.B) { + g, err := NewGarden(test5.diagram, test5.children) + if err != nil { + b.Skip("BenchmarkGarden_Plants requires valid garden") + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, l := range test5.lookups { + g.Plants(l.child) + } + } +} diff --git a/go/paasio/README.md b/go/paasio/README.md new file mode 100644 index 0000000..eab122e --- /dev/null +++ b/go/paasio/README.md @@ -0,0 +1,32 @@ +# Paasio + +Write a program that reports network IO statistics + +You are writing a [PaaS][], and you need a way to bill customers based +on network and filesystem usage. + +Create a wrapper for network connections and files that can report IO +statistics. The wrapper must report: + +- The total number of bytes read/written. +- The total number of read/write operations. + +[PaaS]: http://en.wikipedia.org/wiki/Platform_as_a_service + +To run the tests simply run the command `go test` in the exercise directory. + +If the test suite contains benchmarks, you can run these with the `-bench` +flag: + + go test -bench . + +For more detailed info about the Go track see the [help +page](http://exercism.io/languages/go). + +## Source + +Brian Matsuo [https://github.com/bmatsuo](https://github.com/bmatsuo) + +## Submitting Incomplete Problems +It's possible to submit an incomplete solution so you can see how others have completed the exercise. + diff --git a/go/paasio/interface.go b/go/paasio/interface.go new file mode 100644 index 0000000..1cccfe2 --- /dev/null +++ b/go/paasio/interface.go @@ -0,0 +1,40 @@ +package paasio + +import "io" + +// ReadCounter is an interface describing objects that can be read from, +// and that can count the number of times they have been read from. +// +// If multiple goroutines concurrently call Read, implementations are not +// required to provide any guarantees about interleaving of the Read calls. +// However, implementations MUST guarantee that calls to ReadCount always return +// correct results even in the presence of concurrent Read calls. +type ReadCounter interface { + io.Reader + // ReadCount returns the total number of bytes successfully read along + // with the total number of calls to Read(). + ReadCount() (n int64, nops int) +} + +// WriteCounter is an interface describing objects that can be written to, +// and that can count the number of times they have been written to. +// +// If multiple goroutines concurrently call Write, implementations are not +// required to provide any guarantees about interleaving of the Write calls. +// However, implementations MUST guarantee that calls to WriteCount always return +// correct results even in the presence of concurrent Write calls. +type WriteCounter interface { + io.Writer + // WriteCount returns the total number of bytes successfully written along + // with the total number of calls to Write(). + WriteCount() (n int64, nops int) +} + +// ReadWriteCounter is the union of ReadCounter and WriteCounter. +// +// All guarantees that apply to either of ReadCounter or WriteCounter +// also apply to ReadWriteCounter. +type ReadWriteCounter interface { + ReadCounter + WriteCounter +} diff --git a/go/paasio/paasio_test.go b/go/paasio/paasio_test.go new file mode 100644 index 0000000..a9fde3d --- /dev/null +++ b/go/paasio/paasio_test.go @@ -0,0 +1,223 @@ +package paasio + +import ( + "bytes" + "crypto/rand" + "io" + "runtime" + "strings" + "sync" + "testing" + "time" +) + +// testVersion identifies the API tested by the test program. +const targetTestVersion = 3 + +func TestMultiThreaded(t *testing.T) { + if testVersion != targetTestVersion { + t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion) + } + mincpu := 2 + minproc := 2 + ncpu := runtime.NumCPU() + if ncpu < mincpu { + t.Fatalf("at least %d cpu cores are required", mincpu) + } + nproc := runtime.GOMAXPROCS(0) + if nproc < minproc { + t.Errorf("at least %d threads are required; rerun the tests", minproc) + t.Errorf("") + t.Errorf("\tgo test -cpu %d ...", minproc) + } +} + +// this test could be improved to test that error conditions are preserved. +func testWrite(t *testing.T, writer func(io.Writer) WriteCounter) { + for i, test := range []struct { + writes []string + }{ + {nil}, + {[]string{""}}, + {[]string{"I", " ", "never met ", "", "a gohper"}}, + } { + var buf bytes.Buffer + buft := writer(&buf) + for _, s := range test.writes { + n, err := buft.Write([]byte(s)) + if err != nil { + t.Errorf("test %d: Write(%q) unexpected error: %v", i, s, err) + continue + } + if n != len(s) { + t.Errorf("test %d: Write(%q) unexpected number of bytes written: %v", i, s, n) + continue + } + } + out := buf.String() + if out != strings.Join(test.writes, "") { + t.Errorf("test %d: unexpected content in underlying writer: %q", i, out) + } + } +} + +func TestWriteWriter(t *testing.T) { + testWrite(t, NewWriteCounter) +} + +func TestWriteReadWriter(t *testing.T) { + testWrite(t, func(w io.Writer) WriteCounter { + var r nopReader + return NewReadWriteCounter(readWriter{r, w}) + }) +} + +// this test could be improved to test exact number of operations as well as +// ensure that error conditions are preserved. +func testRead(t *testing.T, reader func(io.Reader) ReadCounter) { + chunkLen := 10 << 20 // 10MB + orig := make([]byte, 10<<20) + _, err := rand.Read(orig) + if err != nil { + t.Fatalf("error reading random data") + } + buf := bytes.NewBuffer(orig) + rc := reader(buf) + var obuf bytes.Buffer + ncopy, err := io.Copy(&obuf, rc) + if err != nil { + t.Fatalf("error reading: %v", err) + } + if ncopy != int64(chunkLen) { + t.Fatalf("copied %d bytes instead of %d", ncopy, chunkLen) + } + if string(orig) != obuf.String() { + t.Fatalf("unexpected output from Read()") + } + n, nops := rc.ReadCount() + if n != int64(chunkLen) { + t.Fatalf("reported %d bytes read instead of %d", n, chunkLen) + } + if nops < 2 { + t.Fatalf("unexpected number of reads: %v", nops) + } +} + +func TestReadReader(t *testing.T) { + testRead(t, NewReadCounter) +} + +func TestReadReadWriter(t *testing.T) { + testRead(t, func(r io.Reader) ReadCounter { + var w nopWriter + return NewReadWriteCounter(readWriter{r, w}) + }) +} + +func testReadTotal(t *testing.T, rc ReadCounter) { + numGo := 8000 + numBytes := 50 + totalBytes := int64(numGo) * int64(numBytes) + p := make([]byte, numBytes) + + t.Logf("Calling Read() for %d*%d=%d bytes", numGo, numBytes, totalBytes) + wg := new(sync.WaitGroup) + wg.Add(numGo) + start := make(chan struct{}) + for i := 0; i < numGo; i++ { + go func() { + <-start + rc.Read(p) + wg.Done() + }() + } + close(start) + + wg.Wait() + n, nops := rc.ReadCount() + if n != totalBytes { + t.Errorf("expected %d bytes read; %d bytes reported", totalBytes, n) + } + if nops != numGo { + t.Errorf("expected %d read operations; %d operations reported", numGo, nops) + } +} + +func TestReadTotalReader(t *testing.T) { + var r nopReader + testReadTotal(t, NewReadCounter(r)) +} + +func TestReadTotalReadWriter(t *testing.T) { + var rw nopReadWriter + testReadTotal(t, NewReadWriteCounter(rw)) +} + +func testWriteTotal(t *testing.T, wt WriteCounter) { + numGo := 8000 + numBytes := 50 + totalBytes := int64(numGo) * int64(numBytes) + p := make([]byte, numBytes) + + t.Logf("Calling Write() with %d*%d=%d bytes", numGo, numBytes, totalBytes) + wg := new(sync.WaitGroup) + wg.Add(numGo) + start := make(chan struct{}) + for i := 0; i < numGo; i++ { + go func() { + <-start + wt.Write(p) + wg.Done() + }() + } + close(start) + + wg.Wait() + n, nops := wt.WriteCount() + if n != totalBytes { + t.Errorf("expected %d bytes written; %d bytes reported", totalBytes, n) + } + if nops != numGo { + t.Errorf("expected %d write operations; %d operations reported", numGo, nops) + } +} + +func TestWriteTotalWriter(t *testing.T) { + var w nopWriter + testWriteTotal(t, NewWriteCounter(w)) +} + +func TestWriteTotalReadWriter(t *testing.T) { + var rw nopReadWriter + testWriteTotal(t, NewReadWriteCounter(rw)) +} + +type nopWriter struct{ error } + +func (w nopWriter) Write(p []byte) (int, error) { + time.Sleep(1) + if w.error != nil { + return 0, w.error + } + return len(p), nil +} + +type nopReader struct{ error } + +func (r nopReader) Read(p []byte) (int, error) { + time.Sleep(1) + if r.error != nil { + return 0, r.error + } + return len(p), nil +} + +type nopReadWriter struct { + nopReader + nopWriter +} + +type readWriter struct { + io.Reader + io.Writer +} diff --git a/go/simple-cipher/README.md b/go/simple-cipher/README.md new file mode 100644 index 0000000..5b15834 --- /dev/null +++ b/go/simple-cipher/README.md @@ -0,0 +1,100 @@ +# Simple Cipher + +Implement a simple shift cipher like Caesar and a more secure substitution cipher + +## Step 1 + +"If he had anything confidential to say, he wrote it in cipher, that is, +by so changing the order of the letters of the alphabet, that not a word +could be made out. If anyone wishes to decipher these, and get at their +meaning, he must substitute the fourth letter of the alphabet, namely D, +for A, and so with the others." +—Suetonius, Life of Julius Caesar + +Ciphers are very straight-forward algorithms that allow us to render +text less readable while still allowing easy deciphering. They are +vulnerable to many forms of cryptoanalysis, but we are lucky that +generally our little sisters are not cryptoanalysts. + +The Caesar Cipher was used for some messages from Julius Caesar that +were sent afield. Now Caesar knew that the cipher wasn't very good, but +he had one ally in that respect: almost nobody could read well. So even +being a couple letters off was sufficient so that people couldn't +recognize the few words that they did know. + +Your task is to create a simple shift cipher like the Caesar Cipher. +This image is a great example of the Caesar Cipher: ![Caesar Cipher][1] + +For example: + +Giving "iamapandabear" as input to the encode function returns the cipher "ldpdsdqgdehdu". Obscure enough to keep our message secret in transit. + +When "ldpdsdqgdehdu" is put into the decode function it would return +the original "iamapandabear" letting your friend read your original +message. + +## Step 2 + +Shift ciphers are no fun though when your kid sister figures it out. Try +amending the code to allow us to specify a key and use that for the +shift distance. This is called a substitution cipher. + +Here's an example: + +Given the key "aaaaaaaaaaaaaaaaaa", encoding the string "iamapandabear" +would return the original "iamapandabear". + +Given the key "ddddddddddddddddd", encoding our string "iamapandabear" +would return the obscured "lpdsdqgdehdu" + +In the example above, we've set a = 0 for the key value. So when the +plaintext is added to the key, we end up with the same message coming +out. So "aaaa" is not an ideal key. But if we set the key to "dddd", we +would get the same thing as the Caesar Cipher. + +## Step 3 + +The weakest link in any cipher is the human being. Let's make your +substitution cipher a little more fault tolerant by providing a source +of randomness and ensuring that they key is not composed of numbers or +capital letters. + +If someone doesn't submit a key at all, generate a truly random key of +at least 100 characters in length, accessible via Cipher#key (the # +syntax means instance variable) + +If the key submitted has capital letters or numbers, throw an +ArgumentError with a message to that effect. + +## Extensions + +Shift ciphers work by making the text slightly odd, but are vulnerable +to frequency analysis. Substitution ciphers help that, but are still +very vulnerable when the key is short or if spaces are preserved. Later +on you'll see one solution to this problem in the exercise +"crypto-square". + +If you want to go farther in this field, the questions begin to be about +how we can exchange keys in a secure way. Take a look at [Diffie-Hellman +on Wikipedia][dh] for one of the first implementations of this scheme. + +[1]: http://upload.wikimedia.org/wikipedia/en/7/75/Caesar3.png +[dh]: http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange + +To run the tests simply run the command `go test` in the exercise directory. + +If the test suite contains benchmarks, you can run these with the `-bench` +flag: + + go test -bench . + +For more detailed info about the Go track see the [help +page](http://exercism.io/languages/go). + +## Source + +Substitution Cipher at Wikipedia [http://en.wikipedia.org/wiki/Substitution_cipher](http://en.wikipedia.org/wiki/Substitution_cipher) + +## Submitting Incomplete Problems +It's possible to submit an incomplete solution so you can see how others have completed the exercise. + diff --git a/go/simple-cipher/cipher.go b/go/simple-cipher/cipher.go new file mode 100644 index 0000000..919d56e --- /dev/null +++ b/go/simple-cipher/cipher.go @@ -0,0 +1,143 @@ +package cipher + +import ( + "regexp" + "strings" +) + +// Cipher is an interface for all ciphers +type Cipher interface { + Encode(string) string + Decode(string) string +} + +// ShiftCipher is a cipher that shifts all letters a fixed distance +type ShiftCipher struct { + width int +} + +// NewShift returns a new ShiftCipher +func NewShift(w int) *ShiftCipher { + if w == 0 || w < -25 || w > 25 { + return nil + } + c := new(ShiftCipher) + c.width = w + return c +} + +// Encode encodes a string using the current ShiftCipher +func (c *ShiftCipher) Encode(s string) string { + var ret string + s = strings.ToLower(s) + re := regexp.MustCompile("[^a-z]") + s = re.ReplaceAllString(s, "") + for i := range s { + newChar := s[i] + byte(c.width) + if newChar < 'a' { + newChar = 'z' - ('a' - newChar) + 1 + } + if newChar > 'z' { + newChar = 'a' + (newChar - 'z') - 1 + } + ret = ret + string(newChar) + } + return ret +} + +// Decode decodes a string using the current ShiftCipher +func (c *ShiftCipher) Decode(s string) string { + var ret string + s = strings.ToLower(s) + re := regexp.MustCompile("[^a-z]") + s = re.ReplaceAllString(s, "") + for i := range s { + newChar := s[i] - byte(c.width) + if newChar < 'a' { + newChar = 'z' - ('a' - newChar) + 1 + } + if newChar > 'z' { + newChar = 'a' + (newChar - 'z') - 1 + } + ret = ret + string(newChar) + } + return ret +} + +// A CeasarCipher is a ShiftCipher, just hardcoded at 3 +type CeasarCipher struct { + c *ShiftCipher +} + +// NewCaesar creates a new CaesarCipher +func NewCaesar() *CeasarCipher { + c := new(CeasarCipher) + c.c = NewShift(3) + return c +} + +// Encode just calls the shift cipher in the caesar cipher +func (c *CeasarCipher) Encode(s string) string { + return c.c.Encode(s) +} + +// Decode just calls the shift cipher in the caesar cipher +func (c *CeasarCipher) Decode(s string) string { + return c.c.Decode(s) +} + +// VigenereCipher a cipher where each character can have a different shift width +type VigenereCipher struct { + key string +} + +// Encode encodes the given string +func (c *VigenereCipher) Encode(s string) string { + var ret string + s = strings.ToLower(s) + re := regexp.MustCompile("[^a-z]") + s = re.ReplaceAllString(s, "") + for i := range s { + keyChar := c.key[i%len(c.key)] - 'a' + newChar := s[i] + keyChar + if newChar < 'a' { + newChar = 'z' - ('a' - newChar) + 1 + } + if newChar > 'z' { + newChar = 'a' + (newChar - 'z') - 1 + } + ret = ret + string(newChar) + } + return ret +} + +// Decode decodes the given string +func (c *VigenereCipher) Decode(s string) string { + var ret string + s = strings.ToLower(s) + re := regexp.MustCompile("[^a-z]") + s = re.ReplaceAllString(s, "") + for i := range s { + keyChar := c.key[i%len(c.key)] - 'a' + newChar := s[i] - keyChar + if newChar < 'a' { + newChar = 'z' - ('a' - newChar) + 1 + } + if newChar > 'z' { + newChar = 'a' + (newChar - 'z') - 1 + } + ret = ret + string(newChar) + } + return ret +} + +// NewVigenere returns a new Vigenere Cipher +func NewVigenere(key string) *VigenereCipher { + re := regexp.MustCompile("[^a-z]") + if len(key) < 3 || re.MatchString(key) { + return nil + } + c := new(VigenereCipher) + c.key = key + return c +} diff --git a/go/simple-cipher/simple_cipher_test.go b/go/simple-cipher/simple_cipher_test.go new file mode 100644 index 0000000..05621d3 --- /dev/null +++ b/go/simple-cipher/simple_cipher_test.go @@ -0,0 +1,270 @@ +// For Step 1, implement the Caesar cipher, which seems clear enough except +// maybe for figuring out whether to add or subtract. +// +// For Step 2, implement a shift cipher, like Caesar except the shift amount +// is specified as an int. (Each letter of "ddddddddddddddddd", is 'd'; +// 'd'-'a' == 3, so the corresponding shift amount is 3.) +// +// Steps 2 and 3 seem to be describing the Vigenère cipher (Google it) +// so let's do that too. The random thing, don't worry about. There is +// no test for that. +// +// API: +// +// type Cipher interface { +// Encode(string) string +// Decode(string) string +// } +// NewCaesar() Cipher +// NewShift(int) Cipher +// NewVigenere(string) Cipher +// +// Interface Cipher is in file cipher.go. +// +// Argument for NewShift must be in the range 1 to 25 or -1 to -25. +// Zero is disallowed. For invalid arguments NewShift returns nil. +// +// Argument for NewVigenere must consist of lower case letters a-z only. +// Values consisting entirely of the letter 'a' are disallowed. +// For invalid arguments NewVigenere returns nil. + +package cipher + +import ( + "fmt" + "testing" +) + +// type for testing cipher encoding alone, without requiring any text prep. +type prepped struct { + pt string // prepped text == decoded plain text + ct string // cipher text +} + +// +3? -3? Positions vary. Your code just has to pass the tests. +var caesarPrepped = []prepped{ + {"iamapandabear", "ldpdsdqgdehdu"}, + {"programmingisawesome", "surjudpplqjlvdzhvrph"}, + {"todayisholiday", "wrgdblvkrolgdb"}, + {"venividivici", "yhqlylglylfl"}, +} + +func TestCaesarPrepped(t *testing.T) { + c := NewCaesar() + for _, test := range caesarPrepped { + if enc := c.Encode(test.pt); enc != test.ct { + t.Fatalf("Caesar Encode(%q) = %q, want %q.", test.pt, enc, test.ct) + } + } +} + +// type for testing implementations of the Cipher interface +type cipherTest struct { + source string // source text, any UTF-8 + cipher string // cipher text, result of Encode(st) + plain string // decoded plain text, result of Decode(ct) +} + +var caesarTests = []cipherTest{ + {"Go, go, gophers", "jrjrjrskhuv", "gogogophers"}, + {"I am a panda bear.", "ldpdsdqgdehdu", "iamapandabear"}, + {"Programming is AWESOME!", "surjudpplqjlvdzhvrph", "programmingisawesome"}, + {"today is holiday", "wrgdblvkrolgdb", "todayisholiday"}, + {"Twas the night before Christmas", + "wzdvwkhqljkwehiruhfkulvwpdv", + "twasthenightbeforechristmas"}, + {"venividivici", "yhqlylglylfl", "venividivici"}, + {" -- @#!", "", ""}, + {"", "", ""}, +} + +func TestCaesar(t *testing.T) { + testCipher("Caesar", NewCaesar(), caesarTests, t) +} + +func testCipher(name string, c Cipher, tests []cipherTest, t *testing.T) { + for _, test := range tests { + if enc := c.Encode(test.source); enc != test.cipher { + t.Fatalf("%s Encode(%q) = %q, want %q.", + name, test.plain, enc, test.cipher) + } + if dec := c.Decode(test.cipher); dec != test.plain { + t.Fatalf("%s Decode(%q) = %q, want %q.", + name, test.cipher, dec, test.plain) + } + } +} + +var NSATests = []cipherTest{ + {"THE ENEMY IS NEAR", "qebbkbjvfpkbxo", "theenemyisnear"}, + {"SPIES SEND SECRET MESSAGES", + "pmfbppbkapbzobqjbppxdbp", + "spiessendsecretmessages"}, + {"THOMAS JEFFERSON DESIGNED A SUBSTITUTION CIPHER", + "qeljxpgbccboplkabpfdkbaxprypqfqrqflkzfmebo", + "thomasjeffersondesignedasubstitutioncipher"}, + {"the quick brown fox jumps over the lazy dog", + "qebnrfzhyoltkclugrjmplsboqebixwvald", + "thequickbrownfoxjumpsoverthelazydog"}, +} + +func TestShift(t *testing.T) { + // test shift(3) against Caesar cases. + c := NewShift(3) + if c == nil { + t.Fatal("NewShift(3) returned nil, want non-nil Cipher") + } + testCipher("Shift(3)", c, caesarTests, t) + + // NSA and WP say Caesar uses shift of -3 + c = NewShift(-3) + if c == nil { + t.Fatal("NewShift(-3) returned nil, want non-nil Cipher") + } + testCipher("Shift(-3)", c, NSATests, t) + + // invalid shifts + for _, s := range []int{-27, -26, 0, 26, 27} { + if NewShift(s) != nil { + t.Fatal("NewShift(%d) returned non-nil, "+ + "Want nil return for invalid argument.", s) + } + } +} + +var vtests = []struct { + key string + tests []cipherTest +}{ + {"lemon", []cipherTest{{"ATTACKATDAWN", "lxfopvefrnhr", "attackatdawn"}}}, + {"abcdefghij", []cipherTest{ + {"aaaaaaaaaa", "abcdefghij", "aaaaaaaaaa"}, + {"zzzzzzzzzz", "zabcdefghi", "zzzzzzzzzz"}, + }}, + {"iamapandabear", []cipherTest{ + {"I am a panda bear.", "qayaeaagaciai", "iamapandabear"}, + }}, + {"duxrceqyaimciuucnelkeoxjhdyduu", []cipherTest{ + {"Diffie Hellman", "gccwkixcltycv", "diffiehellman"}, + }}, + {"qgbvno", []cipherTest{ + {"cof-FEE, 123!", "sugars", "coffee"}, + }}, +} + +func TestVigenere(t *testing.T) { + for _, test := range vtests { + v := NewVigenere(test.key) + if v == nil { + t.Fatalf("NewVigenere(%q) returned nil, want non-nil Cipher", + test.key) + } + testCipher(fmt.Sprintf("Vigenere(%q)", test.key), v, test.tests, t) + } + + // invalid keys + for _, k := range []string{"", "a", "aa", "no way", "CAT", "3", "and,"} { + if NewVigenere(k) != nil { + t.Fatalf("NewVigenere(%q) returned non-nil, "+ + "Want nil return for invalid argument.", k) + } + } +} + +// Benchmark combined time to run all tests. +// Note other ciphers test different data; times cannot be compared. +func BenchmarkEncodeCaesar(b *testing.B) { + c := NewCaesar() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, test := range caesarTests { + c.Encode(test.source) + } + } +} + +func BenchmarkDecodeCaesar(b *testing.B) { + c := NewCaesar() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, test := range caesarTests { + c.Decode(test.cipher) + } + } +} + +func BenchmarkNewShift(b *testing.B) { + for i := 0; i < b.N; i++ { + for s := -27; s <= 27; s++ { + NewShift(s) + } + } +} + +func BenchmarkEncodeShift(b *testing.B) { + s := NewShift(5) + all := append(caesarTests, NSATests...) + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, test := range all { + s.Encode(test.source) + } + } +} + +func BenchmarkDecodeShift(b *testing.B) { + s := NewShift(5) + all := append(caesarTests, NSATests...) + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, test := range all { + s.Decode(test.cipher) + } + } +} + +func BenchmarkNewVigenere(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, test := range vtests { + NewVigenere(test.key) + } + } +} + +func BenchmarkEncVigenere(b *testing.B) { + v := make([]Cipher, len(vtests)) + for i, test := range vtests { + v[i] = NewVigenere(test.key) + if v[i] == nil { + b.Skip("Benchmark requires valid Vigenere test cases") + } + } + b.ResetTimer() + for j := 0; j < b.N; j++ { + for i, test := range vtests { + vi := v[i] + for _, test := range test.tests { + vi.Encode(test.source) + } + } + } +} + +func BenchmarkDecVigenere(b *testing.B) { + v := make([]Cipher, len(vtests)) + for i, test := range vtests { + v[i] = NewVigenere(test.key) + if v[i] == nil { + b.Skip("Benchmark requires valid Vigenere test cases") + } + } + b.ResetTimer() + for j := 0; j < b.N; j++ { + for i, test := range vtests { + vi := v[i] + for _, test := range test.tests { + vi.Decode(test.cipher) + } + } + } +}