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..357a500 --- /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/grains/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{ + examIgnoreError, +} + +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 new file mode 100644 index 0000000..c2c630e --- /dev/null +++ b/track/grains/grains_test.go @@ -0,0 +1,44 @@ +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/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}, + {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) { + 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/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/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..701edf7 --- /dev/null +++ b/track/grains/tpl/bits-rotate64.md @@ -0,0 +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/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/error-formatted.md b/track/grains/tpl/error-formatted.md new file mode 100644 index 0000000..9cf825f --- /dev/null +++ b/track/grains/tpl/error-formatted.md @@ -0,0 +1 @@ +- 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 new file mode 100644 index 0000000..e672215 --- /dev/null +++ b/track/grains/tpl/math-pow.md @@ -0,0 +1 @@ +- 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/reg.go b/track/grains/tpl/reg.go new file mode 100644 index 0000000..9b2b1b1 --- /dev/null +++ b/track/grains/tpl/reg.go @@ -0,0 +1,16 @@ +//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) + ErrorFormatted = gtpl.NewStringTemplate("error-formatted.md", MustAsset) + CheckError = gtpl.NewStringTemplate("check-error.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..35a3369 --- /dev/null +++ b/track/grains/tpl/static-total-nice.md @@ -0,0 +1 @@ +- 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 new file mode 100644 index 0000000..0e38e94 --- /dev/null +++ b/track/grains/tpl/static-total.md @@ -0,0 +1,18 @@ +- 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?)