From 721c6f8a62b5e0ea5cc630700bf7aae05960c30c Mon Sep 17 00:00:00 2001 From: TehSphinX Date: Sat, 17 Nov 2018 11:22:29 +0100 Subject: [PATCH 1/5] grains: add solutions and tests --- README.md | 1 + go.sum | 1 + suggestion.go | 10 +- track/grains/grains.go | 25 ++ track/grains/grains_test.go | 39 +++ track/grains/solutions/1/solution.go | 23 ++ track/grains/solutions/2/solution.go | 21 ++ track/grains/tpl/bindata.go | 367 ++++++++++++++++++++++++++ track/grains/tpl/bits-rotate.md | 1 + track/grains/tpl/bits-rotate64.md | 1 + track/grains/tpl/math-pow.md | 1 + track/grains/tpl/reg.go | 14 + track/grains/tpl/static-total-nice.md | 1 + track/grains/tpl/static-total.md | 1 + 14 files changed, 502 insertions(+), 4 deletions(-) create mode 100644 track/grains/grains.go create mode 100644 track/grains/grains_test.go create mode 100644 track/grains/solutions/1/solution.go create mode 100644 track/grains/solutions/2/solution.go create mode 100644 track/grains/tpl/bindata.go create mode 100644 track/grains/tpl/bits-rotate.md create mode 100644 track/grains/tpl/bits-rotate64.md create mode 100644 track/grains/tpl/math-pow.md create mode 100644 track/grains/tpl/reg.go create mode 100644 track/grains/tpl/static-total-nice.md create mode 100644 track/grains/tpl/static-total.md diff --git a/README.md b/README.md index 0abdfa0..1d00be6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ This is a proof of concept. I use it to mentor, and enhance/extend it as I go. - Difference of Squares - Luhn - Parallel Letter Frequency +- Grains Exalysis will do its format, lint, and test checks on any solution, but specific suggestions have so far been implemented for only the exercises above. If you'd like to add support for a new exercise, please submit a PR! (See 'Contributions' below.) diff --git a/go.sum b/go.sum index fc89c8a..ef8cea1 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ github.com/atotto/clipboard v0.1.1 h1:WSoEbAS70E5gw8FbiqFlp69MGsB6dUb4l+0AGGLiVG github.com/atotto/clipboard v0.1.1/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/kevinburke/go-bindata v3.13.0+incompatible h1:hThDhUBH4KjTyhfXfOgacEPfFBNjltnzl/xzfLfrPoQ= github.com/kevinburke/go-bindata v3.13.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= diff --git a/suggestion.go b/suggestion.go index 63de8eb..d2c7821 100644 --- a/suggestion.go +++ b/suggestion.go @@ -13,6 +13,7 @@ import ( "github.com/tehsphinx/exalysis/extypes" "github.com/tehsphinx/exalysis/gtpl" "github.com/tehsphinx/exalysis/track/diffsquares" + "github.com/tehsphinx/exalysis/track/grains" "github.com/tehsphinx/exalysis/track/hamming" "github.com/tehsphinx/exalysis/track/isogram" "github.com/tehsphinx/exalysis/track/luhn" @@ -23,14 +24,15 @@ import ( ) var exercisePkgs = map[string]extypes.SuggestionFunc{ - "twofer": twofer.Suggest, + "diffsquares": diffsquares.Suggest, + "grains": grains.Suggest, "hamming": hamming.Suggest, - "raindrops": raindrops.Suggest, - "scrabble": scrabble.Suggest, "isogram": isogram.Suggest, - "diffsquares": diffsquares.Suggest, "luhn": luhn.Suggest, "letter": paraletterfreq.Suggest, + "raindrops": raindrops.Suggest, + "scrabble": scrabble.Suggest, + "twofer": twofer.Suggest, } //GetSuggestions selects the package suggestion routine and returns the suggestions diff --git a/track/grains/grains.go b/track/grains/grains.go new file mode 100644 index 0000000..1f85792 --- /dev/null +++ b/track/grains/grains.go @@ -0,0 +1,25 @@ +package grains + +import ( + "github.com/tehsphinx/astrav" + "github.com/tehsphinx/exalysis/extypes" + "github.com/tehsphinx/exalysis/track/twofer/tpl" +) + +//Suggest builds suggestions for the exercise solution +func Suggest(pkg *astrav.Package, r *extypes.Response) { + for _, tf := range exFuncs { + tf(pkg, r) + } +} + +var exFuncs = []extypes.SuggestionFunc{ + examStringsJoin, +} + +func examStringsJoin(pkg *astrav.Package, r *extypes.Response) { + node := pkg.FindFirstByName("Join") + if node != nil { + r.AppendImprovement(tpl.StringsJoin) + } +} diff --git a/track/grains/grains_test.go b/track/grains/grains_test.go new file mode 100644 index 0000000..6d26520 --- /dev/null +++ b/track/grains/grains_test.go @@ -0,0 +1,39 @@ +package grains + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tehsphinx/exalysis/extypes" + "github.com/tehsphinx/exalysis/gtpl" + "github.com/tehsphinx/exalysis/testhelper" + "github.com/tehsphinx/exalysis/track/grains/tpl" +) + +var suggestTests = []struct { + path string + suggestion gtpl.Template + expected bool +}{ + {path: "./solutions/1", suggestion: tpl.BitsRotate64, expected: true}, + {path: "./solutions/1", suggestion: tpl.BitsRotate, expected: true}, + {path: "./solutions/1", suggestion: tpl.StaticTotalNice, expected: true}, + {path: "./solutions/2", suggestion: tpl.MathPow, expected: true}, + {path: "./solutions/2", suggestion: tpl.StaticTotal, expected: true}, +} + +func Test_Suggest(t *testing.T) { + for _, test := range suggestTests { + _, pkg, err := testhelper.LoadExample(test.path, "grains") + if err != nil { + t.Fatal(err) + } + + r := extypes.NewResponse() + Suggest(pkg, r) + + assert.Equal(t, test.expected, r.HasSuggestion(test.suggestion), + fmt.Sprintf("test failed: %+v", test)) + } +} diff --git a/track/grains/solutions/1/solution.go b/track/grains/solutions/1/solution.go new file mode 100644 index 0000000..996ecd0 --- /dev/null +++ b/track/grains/solutions/1/solution.go @@ -0,0 +1,23 @@ +/* +Package grains calculate the number of grains of wheat on a chessboard given that the number on each square doubles. +*/ +package grains + +import ( + "errors" + "math" + "math/bits" +) + +// Square returns number of grains on the specified square +func Square(number int) (uint64, error) { + if number < 1 || number > 64 { + return 0, errors.New("invalid number of squares") + } + return uint64(bits.RotateLeft(1, number-1)), nil +} + +// Total returns total number of grains from 64 squares (2^64 - 1) +func Total() uint64 { + return math.MaxUint64 +} diff --git a/track/grains/solutions/2/solution.go b/track/grains/solutions/2/solution.go new file mode 100644 index 0000000..19dbf7b --- /dev/null +++ b/track/grains/solutions/2/solution.go @@ -0,0 +1,21 @@ +package grains + +import ( + "fmt" + "math" +) + +// Square returns the number of grains on a square on a chess board where the +// first square has 1 and every subsequent square doubles the number. +func Square(num int) (uint64, error) { + if num < 1 || num > 64 { + return 0, fmt.Errorf("Num [%d] is invalid.", num) + } + return uint64(math.Pow(2, float64(num-1))), nil +} + +// Total returns the total number of grains of the chessboard. +func Total() uint64 { + // uint64's range: 0-18446744073709551615. + return uint64(math.Pow(2, 63))*2 - 1 +} diff --git a/track/grains/tpl/bindata.go b/track/grains/tpl/bindata.go new file mode 100644 index 0000000..74eb363 --- /dev/null +++ b/track/grains/tpl/bindata.go @@ -0,0 +1,367 @@ +// Code generated by go-bindata. DO NOT EDIT. +// sources: +// bits-rotate.md (99B) +// bits-rotate64.md (86B) +// math-pow.md (238B) +// static-total-nice.md (66B) +// static-total.md (304B) + +package tpl + +import ( + "bytes" + "compress/gzip" + "crypto/sha256" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo + digest [sha256.Size]byte +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _bitsRotateMd = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x1c\xcb\x31\x0a\x42\x31\x0c\x06\xe0\xdd\x53\xfc\x9b\x93\x6f\x12\x57\x17\x47\x27\xf1\x00\x8d\x36\xd2\x20\x24\xd0\xfe\xb5\xf4\xf6\xd2\x77\x80\xef\x84\x9b\x65\xcc\xe8\xf8\x7a\x0c\xb0\x68\x55\x58\x83\x20\xbd\x8c\x6d\x7b\x04\x85\x7a\xd7\x0f\x2f\xe7\x74\xc5\xb3\x08\x31\x64\xee\x22\x87\x1f\x09\x57\xcd\xcb\xe1\x1d\xfe\xd3\xda\x2c\x1c\x0c\xa4\x6e\xbe\xcc\x76\xf8\x07\x00\x00\xff\xff\x4b\x69\x29\x39\x63\x00\x00\x00") + +func bitsRotateMdBytes() ([]byte, error) { + return bindataRead( + _bitsRotateMd, + "bits-rotate.md", + ) +} + +func bitsRotateMd() (*asset, error) { + bytes, err := bitsRotateMdBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "bits-rotate.md", size: 99, mode: os.FileMode(420), modTime: time.Unix(1542448678, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe0, 0xbd, 0x49, 0x27, 0xea, 0x25, 0xc9, 0x45, 0x20, 0x2d, 0x6d, 0x92, 0x30, 0x56, 0xf1, 0x6a, 0x2e, 0x2c, 0xf2, 0x1c, 0x74, 0xda, 0x5b, 0xb1, 0xa1, 0x1b, 0x3d, 0xa1, 0x79, 0xdc, 0xb4, 0x2}} + return a, nil +} + +var _bitsRotate64Md = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x1c\xc6\x31\x0e\xc3\x20\x0c\x05\xd0\xbd\xa7\xf8\x63\x3b\x50\x89\xb5\xf2\xd8\x83\x98\x16\x12\xac\x44\x76\x04\x46\x28\xb7\x8f\x94\xed\x05\x7c\x25\xe3\xb4\x81\x4d\x6d\xde\xf8\x27\x45\xda\xbb\x21\x1b\x7e\xe2\xe8\x55\x16\x17\x5d\x31\xc5\x2b\xbc\x16\x30\x11\xc3\x8e\xd2\x92\x5b\xfb\x80\x23\x88\x30\x44\xfd\x29\x21\xbe\xf8\xfd\xb8\x02\x00\x00\xff\xff\xe9\x57\xbd\x36\x56\x00\x00\x00") + +func bitsRotate64MdBytes() ([]byte, error) { + return bindataRead( + _bitsRotate64Md, + "bits-rotate64.md", + ) +} + +func bitsRotate64Md() (*asset, error) { + bytes, err := bitsRotate64MdBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "bits-rotate64.md", size: 86, mode: os.FileMode(420), modTime: time.Unix(1542448709, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x56, 0xc5, 0x55, 0xa8, 0x86, 0xba, 0x69, 0xed, 0x8d, 0xa, 0xd8, 0x6b, 0x27, 0x60, 0x5c, 0x32, 0xd8, 0x30, 0xe9, 0x73, 0x20, 0x91, 0xb8, 0x86, 0x89, 0x5, 0xe4, 0xd9, 0x0, 0x25, 0x1b, 0x66}} + return a, nil +} + +var _mathPowMd = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4c\x8e\xb1\x4e\xc4\x30\x10\x44\x7b\xbe\x62\x3e\x00\x5c\x21\xaa\xfb\x01\x24\x0a\x84\x68\xe8\xbc\x09\x6b\x7b\x75\xc1\x1b\xd9\x13\x59\xf7\xf7\xc8\xb9\xe6\xca\xdd\x37\xf3\x34\x2f\xf8\xf1\x03\xd2\x14\xc3\xdb\xd5\x6a\xc6\x30\x16\xc4\x3f\x61\x09\x9f\x3e\x22\x8a\x36\x0d\x78\x4f\xb8\xf9\x81\x21\x95\xa0\x23\x3b\x92\x37\xf4\x5d\xf5\xf7\x04\xab\x54\x64\xb1\x0a\xc1\xe6\xc4\x72\x43\x1f\xc6\xb5\x4c\x23\x1d\x8b\x11\xbd\x58\xe2\xbc\xef\xc6\xef\xa2\xf0\x5d\x9b\xd0\xdb\x29\xdb\x34\x3d\x84\xac\x23\x5e\x2e\x11\xde\xc0\x59\x38\x37\xca\xd6\x1d\xe9\xa8\x2b\xcd\x6b\x87\xd5\xc9\x10\x17\x63\x8f\xd8\x65\xbd\x4a\xd6\x67\x68\xc8\xe1\xfe\x0c\x5f\x4e\xa1\x7e\x68\xe2\xdb\x6b\x0c\x4f\xff\x01\x00\x00\xff\xff\xed\xcb\xe7\x04\xee\x00\x00\x00") + +func mathPowMdBytes() ([]byte, error) { + return bindataRead( + _mathPowMd, + "math-pow.md", + ) +} + +func mathPowMd() (*asset, error) { + bytes, err := mathPowMdBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "math-pow.md", size: 238, mode: os.FileMode(420), modTime: time.Unix(1542449680, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc6, 0x9a, 0xe1, 0x36, 0x7a, 0x3d, 0xc9, 0x12, 0x1, 0x38, 0x67, 0x73, 0x4a, 0x84, 0x0, 0x21, 0x31, 0xfc, 0x1a, 0x68, 0x51, 0x2d, 0xe0, 0x68, 0x7c, 0xfb, 0x9c, 0x7e, 0xaa, 0x16, 0xc4, 0xb2}} + return a, nil +} + +var _staticTotalNiceMd = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x04\xc0\xb1\x0d\x85\x30\x0c\x04\xd0\xfe\x4f\x71\x7f\x00\x56\xa1\x62\x80\x84\xe0\x80\xa5\x70\x96\xcc\xa5\x60\x7b\xde\x82\xd5\x9b\x8d\x17\x47\xd0\x90\xa6\x99\x74\x9e\xa8\x78\x54\xe5\x0d\x9c\xf7\x6e\x89\x1e\x09\x5d\x86\xb2\x85\xea\x28\xe8\x93\x4d\x1e\xfc\xff\xbe\x00\x00\x00\xff\xff\xba\xdb\x04\x73\x42\x00\x00\x00") + +func staticTotalNiceMdBytes() ([]byte, error) { + return bindataRead( + _staticTotalNiceMd, + "static-total-nice.md", + ) +} + +func staticTotalNiceMd() (*asset, error) { + bytes, err := staticTotalNiceMdBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "static-total-nice.md", size: 66, mode: os.FileMode(420), modTime: time.Unix(1542448842, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xcf, 0xf2, 0xcc, 0x2e, 0x28, 0x95, 0xc2, 0x6c, 0x88, 0x41, 0x3, 0xa3, 0x63, 0x9c, 0x3f, 0x7d, 0x10, 0x61, 0x7b, 0x68, 0x40, 0x35, 0x74, 0x26, 0x4, 0xb9, 0x95, 0x49, 0xc7, 0xe0, 0xb7, 0x51}} + return a, nil +} + +var _staticTotalMd = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x3c\x8f\x3b\x52\xc4\x30\x10\x44\x73\x4e\xd1\x1c\x00\x47\x14\x01\x27\x80\x80\x90\x80\xcc\x8d\x3c\x6b\x4d\xa1\x0f\x68\x46\xcb\xfa\xf6\x94\xe5\x65\xd3\x99\xd7\xaf\xab\x1f\xf0\x51\x3b\xd8\x04\x81\x29\xf4\x44\xd7\xb2\xc2\xa3\xc0\xab\x33\xa1\xf4\xfc\x29\x0d\xf5\x84\xb5\x51\x8b\xe1\x37\x6a\x88\x50\x3b\xfe\x69\x43\x92\x55\xfd\x7e\x68\x42\xed\x69\x41\x13\xef\xad\x80\x30\xa7\x6b\xf8\x57\xd0\xa0\x3e\x82\x51\x60\xcc\x02\x39\x4b\xdb\xe0\x9a\x65\xc2\x6b\x81\x47\x35\x04\x9a\x0c\xe2\xcc\xd4\x65\xc7\xe5\xa7\x33\xc1\xeb\xb8\x66\x5e\x34\xf7\x0c\x62\xee\x5a\xfc\xe9\x71\x46\x60\x41\xac\x69\x01\xcb\x82\x53\x6d\x3b\x67\xd7\xfc\x28\x6b\x72\xec\xab\xc5\x9c\xc5\x0d\x5a\x86\x6b\xce\xf4\x38\xe3\x9b\xe1\x8b\xab\x3c\xe3\x65\x27\xb7\xdb\x8c\x6e\x57\x64\x7a\xe3\xe5\xfd\x28\x9b\xee\xfe\x02\x00\x00\xff\xff\xd1\x7d\x06\x59\x30\x01\x00\x00") + +func staticTotalMdBytes() ([]byte, error) { + return bindataRead( + _staticTotalMd, + "static-total.md", + ) +} + +func staticTotalMd() (*asset, error) { + bytes, err := staticTotalMdBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "static-total.md", size: 304, mode: os.FileMode(420), modTime: time.Unix(1542449726, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x7b, 0x56, 0x72, 0x8d, 0xbf, 0xa5, 0xeb, 0xb1, 0x7f, 0x38, 0x8e, 0x23, 0x41, 0x5, 0xe1, 0xba, 0x1f, 0xbd, 0x61, 0x15, 0xa4, 0xa5, 0xd0, 0xb, 0x86, 0xd6, 0x3a, 0x28, 0x64, 0x75, 0x9b, 0x7d}} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// AssetString returns the asset contents as a string (instead of a []byte). +func AssetString(name string) (string, error) { + data, err := Asset(name) + return string(data), err +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// MustAssetString is like AssetString but panics when Asset would return an +// error. It simplifies safe initialization of global variables. +func MustAssetString(name string) string { + return string(MustAsset(name)) +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetDigest returns the digest of the file with the given name. It returns an +// error if the asset could not be found or the digest could not be loaded. +func AssetDigest(name string) ([sha256.Size]byte, error) { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { + a, err := f() + if err != nil { + return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err) + } + return a.digest, nil + } + return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name) +} + +// Digests returns a map of all known files and their checksums. +func Digests() (map[string][sha256.Size]byte, error) { + mp := make(map[string][sha256.Size]byte, len(_bindata)) + for name := range _bindata { + a, err := _bindata[name]() + if err != nil { + return nil, err + } + mp[name] = a.digest + } + return mp, nil +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "bits-rotate.md": bitsRotateMd, + + "bits-rotate64.md": bitsRotate64Md, + + "math-pow.md": mathPowMd, + + "static-total-nice.md": staticTotalNiceMd, + + "static-total.md": staticTotalMd, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"}, +// AssetDir("data/img") would return []string{"a.png", "b.png"}, +// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + canonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(canonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "bits-rotate.md": &bintree{bitsRotateMd, map[string]*bintree{}}, + "bits-rotate64.md": &bintree{bitsRotate64Md, map[string]*bintree{}}, + "math-pow.md": &bintree{mathPowMd, map[string]*bintree{}}, + "static-total-nice.md": &bintree{staticTotalNiceMd, map[string]*bintree{}}, + "static-total.md": &bintree{staticTotalMd, map[string]*bintree{}}, +}} + +// RestoreAsset restores an asset under the given directory. +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) +} + +// RestoreAssets restores an asset under the given directory recursively. +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + canonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) +} diff --git a/track/grains/tpl/bits-rotate.md b/track/grains/tpl/bits-rotate.md new file mode 100644 index 0000000..3cdf457 --- /dev/null +++ b/track/grains/tpl/bits-rotate.md @@ -0,0 +1 @@ +- Did you know there is a `bits.RotateLeft64`? That way you don't need the conversion to `uint64`. diff --git a/track/grains/tpl/bits-rotate64.md b/track/grains/tpl/bits-rotate64.md new file mode 100644 index 0000000..abc8538 --- /dev/null +++ b/track/grains/tpl/bits-rotate64.md @@ -0,0 +1 @@ +- Did you know you can also do bit shifting with the `<<` operator: `1 << uint(i-1)`. diff --git a/track/grains/tpl/math-pow.md b/track/grains/tpl/math-pow.md new file mode 100644 index 0000000..b033efd --- /dev/null +++ b/track/grains/tpl/math-pow.md @@ -0,0 +1 @@ +- You are working with `math.Pow` here. If you want to go for speed you can gain a lot by switching to bit shifting here. The operator for left shifting is `<<` or there are also functions in the `bits` package, e.g. `bits.RotateLeft64`. diff --git a/track/grains/tpl/reg.go b/track/grains/tpl/reg.go new file mode 100644 index 0000000..c223bbf --- /dev/null +++ b/track/grains/tpl/reg.go @@ -0,0 +1,14 @@ +//go:generate go-bindata -ignore=\.go -pkg=tpl -o=bindata.go ./... + +package tpl + +import "github.com/tehsphinx/exalysis/gtpl" + +//Templates to be used in the response of suggester +var ( + BitsRotate64 = gtpl.NewStringTemplate("bits-rotate64.md", MustAsset) + BitsRotate = gtpl.NewStringTemplate("bits-rotate.md", MustAsset) + StaticTotalNice = gtpl.NewStringTemplate("static-total-nice.md", MustAsset) + MathPow = gtpl.NewStringTemplate("math-pow.md", MustAsset) + StaticTotal = gtpl.NewStringTemplate("static-total.md", MustAsset) +) diff --git a/track/grains/tpl/static-total-nice.md b/track/grains/tpl/static-total-nice.md new file mode 100644 index 0000000..2e3ec1e --- /dev/null +++ b/track/grains/tpl/static-total-nice.md @@ -0,0 +1 @@ +- Nicely done returning a static number for the `Total` function! diff --git a/track/grains/tpl/static-total.md b/track/grains/tpl/static-total.md new file mode 100644 index 0000000..4ab67df --- /dev/null +++ b/track/grains/tpl/static-total.md @@ -0,0 +1 @@ +- You are calculating the total number of grains which is totally legit! You could return a static number as it is the same every time. In this case the value is equal to the maximum a `uint64` can hold and for these values there are constants in the `math` package: Here you could use `math.MaxUint64`. From db160cbb5365620c3e995ec95770d069779f95b6 Mon Sep 17 00:00:00 2001 From: TehSphinX Date: Sat, 17 Nov 2018 14:14:05 +0100 Subject: [PATCH 2/5] grains: mention cost of formatted error --- track/grains/grains_test.go | 2 ++ track/grains/tpl/error-formatted.md | 1 + track/grains/tpl/reg.go | 1 + 3 files changed, 4 insertions(+) create mode 100644 track/grains/tpl/error-formatted.md diff --git a/track/grains/grains_test.go b/track/grains/grains_test.go index 6d26520..dcdd07f 100644 --- a/track/grains/grains_test.go +++ b/track/grains/grains_test.go @@ -19,8 +19,10 @@ var suggestTests = []struct { {path: "./solutions/1", suggestion: tpl.BitsRotate64, expected: true}, {path: "./solutions/1", suggestion: tpl.BitsRotate, expected: true}, {path: "./solutions/1", suggestion: tpl.StaticTotalNice, expected: true}, + {path: "./solutions/1", suggestion: tpl.ErrorFormatted, expected: false}, {path: "./solutions/2", suggestion: tpl.MathPow, expected: true}, {path: "./solutions/2", suggestion: tpl.StaticTotal, expected: true}, + {path: "./solutions/2", suggestion: tpl.ErrorFormatted, expected: true}, } func Test_Suggest(t *testing.T) { diff --git a/track/grains/tpl/error-formatted.md b/track/grains/tpl/error-formatted.md new file mode 100644 index 0000000..a2e5720 --- /dev/null +++ b/track/grains/tpl/error-formatted.md @@ -0,0 +1 @@ +- If you return a less intelligent error message (meaning not formatted message like `errors.New("illegal square")`) the `BenchmarkSquare` will be a lot faster. Crazy, huh? diff --git a/track/grains/tpl/reg.go b/track/grains/tpl/reg.go index c223bbf..37be867 100644 --- a/track/grains/tpl/reg.go +++ b/track/grains/tpl/reg.go @@ -11,4 +11,5 @@ var ( StaticTotalNice = gtpl.NewStringTemplate("static-total-nice.md", MustAsset) MathPow = gtpl.NewStringTemplate("math-pow.md", MustAsset) StaticTotal = gtpl.NewStringTemplate("static-total.md", MustAsset) + ErrorFormatted = gtpl.NewStringTemplate("error-formatted.md", MustAsset) ) From 9a91c03f5295d0d7bbf0eeb9d51689589e2e5cec Mon Sep 17 00:00:00 2001 From: TehSphinX Date: Mon, 3 Dec 2018 07:48:50 +0100 Subject: [PATCH 3/5] grains: add solution and tests --- track/grains/grains.go | 2 +- track/grains/grains_test.go | 2 ++ track/grains/solutions/3/solution.go | 25 +++++++++++++++++++++++++ track/grains/tpl/reg.go | 2 +- 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 track/grains/solutions/3/solution.go diff --git a/track/grains/grains.go b/track/grains/grains.go index 1f85792..cb93e6a 100644 --- a/track/grains/grains.go +++ b/track/grains/grains.go @@ -6,7 +6,7 @@ import ( "github.com/tehsphinx/exalysis/track/twofer/tpl" ) -//Suggest builds suggestions for the exercise solution +// Suggest builds suggestions for the exercise solution func Suggest(pkg *astrav.Package, r *extypes.Response) { for _, tf := range exFuncs { tf(pkg, r) diff --git a/track/grains/grains_test.go b/track/grains/grains_test.go index dcdd07f..9ea07b6 100644 --- a/track/grains/grains_test.go +++ b/track/grains/grains_test.go @@ -23,6 +23,8 @@ var suggestTests = []struct { {path: "./solutions/2", suggestion: tpl.MathPow, expected: true}, {path: "./solutions/2", suggestion: tpl.StaticTotal, expected: true}, {path: "./solutions/2", suggestion: tpl.ErrorFormatted, expected: true}, + {path: "./solutions/3", suggestion: tpl.StaticTotal, expected: true}, + {path: "./solutions/3", suggestion: tpl.MathPow, expected: true}, } func Test_Suggest(t *testing.T) { diff --git a/track/grains/solutions/3/solution.go b/track/grains/solutions/3/solution.go new file mode 100644 index 0000000..bc41595 --- /dev/null +++ b/track/grains/solutions/3/solution.go @@ -0,0 +1,25 @@ +package grains + +import ( + "errors" + "math" +) + +//Square returns the number of grains on the input square +func Square(input int) (uint64, error) { + if input <= 0 || input > 64 { + return 0, errors.New("Invalid input") + } + result := uint64(math.Exp2(float64(input - 1))) + return result, nil +} + +//Total returns all the grains on the board. +func Total() uint64 { + sum := uint64(0) + for i := 1; i <= 64; i++ { + sum1, _ := Square(i) + sum += sum1 + } + return sum +} diff --git a/track/grains/tpl/reg.go b/track/grains/tpl/reg.go index 37be867..04e4ac1 100644 --- a/track/grains/tpl/reg.go +++ b/track/grains/tpl/reg.go @@ -4,7 +4,7 @@ package tpl import "github.com/tehsphinx/exalysis/gtpl" -//Templates to be used in the response of suggester +// Templates to be used in the response of suggester var ( BitsRotate64 = gtpl.NewStringTemplate("bits-rotate64.md", MustAsset) BitsRotate = gtpl.NewStringTemplate("bits-rotate.md", MustAsset) From 6347bdd02e034894d4d1046325c0cd6ab9bd7f2e Mon Sep 17 00:00:00 2001 From: John Arundel Date: Sun, 23 Dec 2018 13:39:34 +0000 Subject: [PATCH 4/5] grains: copyedit templates --- track/grains/tpl/bits-rotate64.md | 2 +- track/grains/tpl/error-formatted.md | 2 +- track/grains/tpl/math-pow.md | 2 +- track/grains/tpl/static-total-nice.md | 2 +- track/grains/tpl/static-total.md | 19 ++++++++++++++++++- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/track/grains/tpl/bits-rotate64.md b/track/grains/tpl/bits-rotate64.md index abc8538..701edf7 100644 --- a/track/grains/tpl/bits-rotate64.md +++ b/track/grains/tpl/bits-rotate64.md @@ -1 +1 @@ -- Did you know you can also do bit shifting with the `<<` operator: `1 << uint(i-1)`. +- Did you know you can also do bit-shifting with the `<<` operator? For example, `1 << uint(i-1)`. diff --git a/track/grains/tpl/error-formatted.md b/track/grains/tpl/error-formatted.md index a2e5720..9cf825f 100644 --- a/track/grains/tpl/error-formatted.md +++ b/track/grains/tpl/error-formatted.md @@ -1 +1 @@ -- If you return a less intelligent error message (meaning not formatted message like `errors.New("illegal square")`) the `BenchmarkSquare` will be a lot faster. Crazy, huh? +- It's always a good idea to return as much information in an error message as you can. If you're testing for invalid arguments, it's helpful to return the actual arguments that caused the problem, using `fmt.Errorf`. However, it's worth knowing that this is a good deal slower than returning a string literal with `errors.New`. Usually, that price is well worth paying, but in a performance-critical situation, if this code is in the 'hot path', you may want to return `errors.New` instead. diff --git a/track/grains/tpl/math-pow.md b/track/grains/tpl/math-pow.md index b033efd..e672215 100644 --- a/track/grains/tpl/math-pow.md +++ b/track/grains/tpl/math-pow.md @@ -1 +1 @@ -- You are working with `math.Pow` here. If you want to go for speed you can gain a lot by switching to bit shifting here. The operator for left shifting is `<<` or there are also functions in the `bits` package, e.g. `bits.RotateLeft64`. +- You are calling `math.Pow` here, which is a useful and flexible tool for computing powers generally. However, there's a special optimization you can do if you're just computing integer powers of 2: you can _bit-shift_ (move all the bits in the binary representation of the number left one place, which is equivalent to multiplying by 2). The operator for left shifting is `<<` or there are also functions in the `bits` package, e.g. `bits.RotateLeft64`. You can shift more than one place at once with `x << N`, which is equivalent to multiplying `x` by 2^N. diff --git a/track/grains/tpl/static-total-nice.md b/track/grains/tpl/static-total-nice.md index 2e3ec1e..35a3369 100644 --- a/track/grains/tpl/static-total-nice.md +++ b/track/grains/tpl/static-total-nice.md @@ -1 +1 @@ -- Nicely done returning a static number for the `Total` function! +- Nicely done returning a constant expression for the `Total` function! Although you could calculate it by calling `Square` in a loop, that's unnecessarily slow, since you know in advance what the answer will be. diff --git a/track/grains/tpl/static-total.md b/track/grains/tpl/static-total.md index 4ab67df..0e38e94 100644 --- a/track/grains/tpl/static-total.md +++ b/track/grains/tpl/static-total.md @@ -1 +1,18 @@ -- You are calculating the total number of grains which is totally legit! You could return a static number as it is the same every time. In this case the value is equal to the maximum a `uint64` can hold and for these values there are constants in the `math` package: Here you could use `math.MaxUint64`. +- You are calculating the total number of grains by summing the values for each square, which is straightforward, but there's a short-cut. Because the answer is always the same, assuming the chessboard has 64 squares (which most do), you can return a constant expression for the value of `Total`. The mathematical formula for the sum of N consecutive powers of 2 is 2^(N+1) - 1. Since square 1 contains 2^0 grains, square 2 contains 2^1 grains, and so on, we are summing the series 2^0 + 2^1 + ... + 2^63. The formula for this sum is 2^64 - 1. Can you see how to use the bit-shift operator (`<<`) to implement this formula? + +(If you're wondering why this is the case, consider the following binary numbers, which represent the series 2^0, 2^1, etc: + + 0001 + 0010 + 0100 + 1000 + +What's the sum of this series? That's easy: + + 1111 + +If you add one to this, you get: + + 10000 + +So the sum of the first N powers of 2 is one less than the next highest power of 2. Does that help?) From 9313e26969cc1bde14dca334ee9ad085ff4cf450 Mon Sep 17 00:00:00 2001 From: John Arundel Date: Sun, 23 Dec 2018 13:54:09 +0000 Subject: [PATCH 5/5] grains: check for ignored error --- track/grains/grains.go | 12 ++++++------ track/grains/grains_test.go | 1 + track/grains/tpl/check-error.md | 3 +++ track/grains/tpl/reg.go | 1 + 4 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 track/grains/tpl/check-error.md diff --git a/track/grains/grains.go b/track/grains/grains.go index cb93e6a..357a500 100644 --- a/track/grains/grains.go +++ b/track/grains/grains.go @@ -3,7 +3,7 @@ package grains import ( "github.com/tehsphinx/astrav" "github.com/tehsphinx/exalysis/extypes" - "github.com/tehsphinx/exalysis/track/twofer/tpl" + "github.com/tehsphinx/exalysis/track/grains/tpl" ) // Suggest builds suggestions for the exercise solution @@ -14,12 +14,12 @@ func Suggest(pkg *astrav.Package, r *extypes.Response) { } var exFuncs = []extypes.SuggestionFunc{ - examStringsJoin, + examIgnoreError, } -func examStringsJoin(pkg *astrav.Package, r *extypes.Response) { - node := pkg.FindFirstByName("Join") - if node != nil { - r.AppendImprovement(tpl.StringsJoin) +func examIgnoreError(pkg *astrav.Package, r *extypes.Response) { + blank := pkg.FindFirstIdentByName("_") + if blank != nil { + r.AppendImprovement(tpl.CheckError) } } diff --git a/track/grains/grains_test.go b/track/grains/grains_test.go index 9ea07b6..c2c630e 100644 --- a/track/grains/grains_test.go +++ b/track/grains/grains_test.go @@ -25,6 +25,7 @@ var suggestTests = []struct { {path: "./solutions/2", suggestion: tpl.ErrorFormatted, expected: true}, {path: "./solutions/3", suggestion: tpl.StaticTotal, expected: true}, {path: "./solutions/3", suggestion: tpl.MathPow, expected: true}, + {path: "./solutions/3", suggestion: tpl.CheckError, expected: true}, } func Test_Suggest(t *testing.T) { diff --git a/track/grains/tpl/check-error.md b/track/grains/tpl/check-error.md new file mode 100644 index 0000000..fe07727 --- /dev/null +++ b/track/grains/tpl/check-error.md @@ -0,0 +1,3 @@ +- You are using the blank identifier (`_`) to ignore the error value returned from `Square`. That's a code smell in Go. If a function returns an error value, you must check it. Right now, it may be impossible for `Square` to be called in a way that would return an error. But real code lives a long time, and undergoes many changes. At some point in the future, the implementation for `Square` could change to return different errors, or the loop code that calls `Square` could have a bug introduced which causes an error. To protect against such situations, it's wise to get into the habit of always checking your error returns. + +(What should you do if the error is not `nil`? It depends on the situation. Sometimes you should pass the error back to the caller, other times you may be able to recover from the error and continue. In this case, there must be an internal error in your package, and so it's probably best to call `panic()`, which generally indicates an unrecoverable internal error.) diff --git a/track/grains/tpl/reg.go b/track/grains/tpl/reg.go index 04e4ac1..9b2b1b1 100644 --- a/track/grains/tpl/reg.go +++ b/track/grains/tpl/reg.go @@ -12,4 +12,5 @@ var ( MathPow = gtpl.NewStringTemplate("math-pow.md", MustAsset) StaticTotal = gtpl.NewStringTemplate("static-total.md", MustAsset) ErrorFormatted = gtpl.NewStringTemplate("error-formatted.md", MustAsset) + CheckError = gtpl.NewStringTemplate("check-error.md", MustAsset) )