Resync
This commit is contained in:
parent
2f99878b09
commit
1415b3ea84
@ -1 +1 @@
|
||||
hexadecimal
|
||||
binary-search-tree
|
77
go/kindergarten-garden/README.md
Normal file
77
go/kindergarten-garden/README.md
Normal file
@ -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.
|
||||
|
93
go/kindergarten-garden/kindergarten.go
Normal file
93
go/kindergarten-garden/kindergarten.go
Normal file
@ -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].
|
211
go/kindergarten-garden/kindergarten_garden_test.go
Normal file
211
go/kindergarten-garden/kindergarten_garden_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
32
go/paasio/README.md
Normal file
32
go/paasio/README.md
Normal file
@ -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.
|
||||
|
40
go/paasio/interface.go
Normal file
40
go/paasio/interface.go
Normal file
@ -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
|
||||
}
|
223
go/paasio/paasio_test.go
Normal file
223
go/paasio/paasio_test.go
Normal file
@ -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
|
||||
}
|
100
go/simple-cipher/README.md
Normal file
100
go/simple-cipher/README.md
Normal file
@ -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.
|
||||
|
143
go/simple-cipher/cipher.go
Normal file
143
go/simple-cipher/cipher.go
Normal file
@ -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
|
||||
}
|
270
go/simple-cipher/simple_cipher_test.go
Normal file
270
go/simple-cipher/simple_cipher_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user