├── .appends └── .github │ └── labels.yml ├── .github ├── labels.yml └── workflows │ ├── pause-community-contributions.yml │ └── sync-labels.yml ├── .gitignore ├── .golangci.yml ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── README.md ├── cmd └── exalysis │ ├── main.go │ ├── main_test.go │ ├── run.sh │ └── testdata │ ├── compile_error │ └── solution.go │ ├── compile_error2 │ ├── cases_test.go │ ├── solution.go │ └── space_age_test.go │ ├── happy_twofer │ └── solution.go │ ├── happypath │ └── solution.go │ └── vet_error │ └── solution.go ├── exam ├── exam.go ├── gobench.go ├── gobench_test.go ├── gofmt.go ├── golangci-lint.go ├── golangci-lint_test.go ├── golint.go ├── gotest.go ├── govet.go ├── govet_test.go └── solutions │ ├── 0 │ └── readme.md │ ├── 1 │ ├── two_fer.go │ └── two_fer_test.go │ ├── 2 │ ├── cases_test.go │ ├── hamming.go │ └── hamming_test.go │ ├── 3 │ └── solution.go │ └── 4 │ └── solution.go ├── extypes ├── response.go └── suggestion.go ├── go.mod ├── go.sum ├── gtpl ├── bindata.go ├── reg.go ├── response │ ├── comment.md │ ├── greeting.md │ ├── improvement.md │ ├── newcomer_greeting.md │ ├── praise.md │ ├── questions.md │ ├── tip.md │ └── todo.md ├── template.go ├── tips │ ├── advanced_tests.md │ ├── append.md │ ├── awesome_go.md │ ├── building.md │ ├── channels.md │ ├── channels2.md │ ├── community.md │ ├── concurrency.md │ ├── concurrencypatterns.md │ ├── concurrencypatterns2.md │ ├── conduct.md │ ├── context.md │ ├── debugger.md │ ├── effective.md │ ├── errors.md │ ├── faq.md │ ├── font.md │ ├── go-perfbook.md │ ├── gophercises.md │ ├── graceful_errors.md │ ├── idiomatic.md │ ├── interface.md │ ├── magic.md │ ├── modules.md │ ├── mutexes.md │ ├── nil.md │ ├── performance.md │ ├── pointers.md │ ├── practical.md │ ├── profiling.md │ ├── proverbs.md │ ├── review.md │ ├── rubyists.md │ ├── servers.md │ ├── shades.md │ ├── simplicity.md │ ├── solid.md │ ├── tdd.md │ ├── tests.md │ ├── tooling.md │ └── tour.md ├── tools │ ├── compile.md │ ├── not_formatted.md │ ├── not_linted.md │ ├── not_vetted.md │ ├── pass_tests.md │ └── race_condition.md └── topic │ ├── benchmarking.md │ ├── hints.md │ ├── pprof-allocations.md │ └── regex.md ├── sample.md ├── suggestion.go ├── testhelper └── helper.go ├── tools.go └── track ├── diffsquares ├── diffsquares.go ├── diffsquares_test.go ├── solutions │ ├── 1 │ │ └── solution.go │ ├── 2 │ │ └── solution.go │ ├── 3 │ │ └── solution.go │ ├── 4 │ │ └── solution.go │ ├── 5 │ │ └── solution.go │ └── 6 │ │ └── solution.go └── tpl │ ├── basic-float64.md │ ├── bindata.go │ ├── calc-range-condition.md │ ├── dry.md │ ├── math-pow.md │ ├── reg.go │ ├── square-sum-loop.md │ └── sum-square-loop.md ├── isogram ├── isogram.go ├── isogram_test.go ├── solutions │ ├── 1 │ │ └── solution.go │ ├── 2 │ │ └── solution.go │ ├── 3 │ │ └── solution.go │ ├── 4 │ │ └── solution.go │ ├── 5 │ │ └── solution.go │ ├── 6 │ │ └── solution.go │ ├── 7 │ │ └── solution.go │ └── 8 │ │ └── solution.go └── tpl │ ├── bindata.go │ ├── if-continue.md │ ├── isletter.md │ ├── just-return.md │ ├── mustcompile.md │ ├── nonexisting-map-value.md │ ├── reg.go │ ├── regex-in-func.md │ ├── two-loops.md │ ├── unicode-loop.md │ ├── unicode.md │ ├── universal-isletter.md │ └── zero-value-assign.md ├── luhn ├── luhn.go ├── luhn_test.go ├── solutions │ ├── 1 │ │ └── solution.go │ ├── 2 │ │ └── solution.go │ ├── 3 │ │ └── solution.go │ ├── 4 │ │ └── solution.go │ ├── 5 │ │ └── solution.go │ ├── 6 │ │ └── solution.go │ └── 7 │ │ └── solution.go └── tpl │ ├── bindata.go │ ├── mustcompile.md │ ├── one-loop.md │ ├── reg.go │ ├── regex-in-func.md │ └── regex-to-fast.md ├── paraletterfreq ├── paraletterfreq.go ├── paraletterfreq_test.go ├── solutions │ ├── 1 │ │ └── solution.go │ ├── 2 │ │ └── solution.go │ ├── 3 │ │ └── solution.go │ ├── 4 │ │ └── solution.go │ ├── 5 │ │ └── solution.go │ ├── 6 │ │ └── solution.go │ ├── 7 │ │ └── solution.go │ ├── 8 │ │ └── solution.go │ ├── 9 │ │ └── solution.go │ └── 10 │ │ └── solution.go └── tpl │ ├── bindata.go │ ├── buffer-size-len.md │ ├── combine-maps-while-waiting.md │ ├── concurrency-not-faster.md │ ├── for-range-novars.md │ ├── goroutine-leak.md │ ├── mutex.md │ ├── range-chan.md │ ├── reg.go │ ├── select-not-needed.md │ ├── waitgroup-add-one.md │ ├── waitgroup-not-needed.md │ └── waitgroup.md ├── raindrops ├── raindrops.go ├── raindrops_test.go ├── solutions │ ├── 1 │ │ └── solution.go │ ├── 2 │ │ └── solution.go │ ├── 3 │ │ └── solution.go │ ├── 4 │ │ └── solution.go │ ├── 5 │ │ └── solution.go │ ├── 6 │ │ └── solution.go │ ├── 7 │ │ └── solution.go │ ├── 8 │ │ └── solution.go │ ├── 9 │ │ └── solution.go │ ├── 10 │ │ └── solution.go │ ├── 11 │ │ └── solution.go │ ├── 12 │ │ └── solution.go │ ├── 13 │ │ └── solution.go │ ├── 14 │ │ └── solution.go │ ├── 15 │ │ └── solution.go │ ├── 16 │ │ └── solution.go │ └── 17 │ │ └── solution.go └── tpl │ ├── all-cases.md │ ├── bindata.go │ ├── extensive-for-loop.md │ ├── fmt-print.md │ ├── itoa.md │ ├── loop-map.md │ ├── many-loops.md │ ├── plus-equal.md │ ├── reg.go │ ├── remove-extra-bool.md │ └── strings-builder.md └── scrabble ├── scrabble.go ├── scrabble_test.go ├── solutions ├── 1 │ └── solution.go ├── 2 │ └── solution.go ├── 3 │ └── solution.go ├── 4 │ └── solution.go ├── 5 │ └── solution.go ├── 6 │ └── solution.go ├── 7 │ └── solution.go ├── 8 │ └── solution.go └── 9 │ └── solution.go └── tpl ├── bindata.go ├── challenge.md ├── flatten-map.md ├── go-routines.md ├── ifs-to-switch.md ├── loop-rune-not-byte.md ├── maprune.md ├── move-map.md ├── reg.go ├── regex.md ├── slice-rune-conversion.md ├── try-switch.md ├── type-conversion.md ├── unicode-loop.md └── unicode.md /.appends/.github/labels.yml: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------------------- # 2 | # These are the repository-specific labels that augment the Exercise-wide labels defined in # 3 | # https://github.com/exercism/org-wide-files/blob/main/global-files/.github/labels.yml. # 4 | # ----------------------------------------------------------------------------------------- # 5 | 6 | - name: "24 Pull Requests" 7 | description: "" 8 | color: "e59492" 9 | 10 | - name: "bug" 11 | description: "Something isn't working" 12 | color: "d73a4a" 13 | 14 | - name: "duplicate" 15 | description: "This issue or pull request already exists" 16 | color: "cfd3d7" 17 | 18 | - name: "enhancement" 19 | description: "New feature or request" 20 | color: "a2eeef" 21 | 22 | - name: "good first issue" 23 | description: "Good for newcomers" 24 | color: "7057ff" 25 | 26 | - name: "help wanted" 27 | description: "Extra attention is needed" 28 | color: "008672" 29 | 30 | - name: "invalid" 31 | description: "This doesn't seem right" 32 | color: "e4e669" 33 | 34 | - name: "question" 35 | description: "Further information is requested" 36 | color: "d876e3" 37 | 38 | - name: "wontfix" 39 | description: "This will not be worked on" 40 | color: "ffffff" 41 | -------------------------------------------------------------------------------- /.github/workflows/pause-community-contributions.yml: -------------------------------------------------------------------------------- 1 | name: Pause Community Contributions 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | pull_request_target: 8 | types: 9 | - opened 10 | 11 | permissions: 12 | issues: write 13 | pull-requests: write 14 | 15 | jobs: 16 | pause: 17 | if: github.repository_owner == 'exercism' # Stops this job from running on forks 18 | uses: exercism/github-actions/.github/workflows/community-contributions.yml@main 19 | with: 20 | forum_category: support 21 | secrets: 22 | github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | name: Tools 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - .github/labels.yml 9 | - .github/workflows/sync-labels.yml 10 | workflow_dispatch: 11 | schedule: 12 | - cron: 0 0 1 * * # First day of each month 13 | 14 | permissions: 15 | issues: write 16 | 17 | jobs: 18 | sync-labels: 19 | uses: exercism/github-actions/.github/workflows/labels.yml@main 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /exalysis 2 | /cmd/exalysis/exalysis 3 | .vscode/spellright.dict 4 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | skip-dirs: 3 | - solutions 4 | 5 | linters-settings: 6 | govet: 7 | check-shadowing: true 8 | golint: 9 | min-confidence: 0.8 10 | gocyclo: 11 | min-complexity: 20 12 | maligned: 13 | suggest-new: true 14 | dupl: 15 | threshold: 100 16 | depguard: 17 | list-type: blacklist 18 | include-go-root: false 19 | packages: 20 | # protect from pushing debug package to production 21 | - github.com/tehsphinx/dbg 22 | lll: 23 | line-length: 150 24 | nakedret: 25 | max-func-lines: 10 26 | 27 | linters: 28 | enable: 29 | - golint 30 | - unconvert 31 | - dupl 32 | - gocyclo 33 | - goimports 34 | - maligned 35 | - depguard 36 | - lll 37 | - nakedret 38 | - scopelint 39 | - gocritic 40 | 41 | issues: 42 | exclude-use-default: false 43 | 44 | exclude-rules: 45 | # Exclude some linters from running on tests files. 46 | - path: _test\.go 47 | linters: 48 | - dupl 49 | 50 | exclude: 51 | # errcheck: Almost all programs ignore errors on these functions and in most cases it's ok 52 | - Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked 53 | 54 | # golint: False positive when tests are defined in package 'test' 55 | - func name will be used as test\.Test.* by other packages, and that stutters; consider calling this 56 | 57 | # govet: shadowing of err variable 58 | - declaration of "err" shadows declaration at 59 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | sudo: false 4 | 5 | go: 6 | - "1.16.x" 7 | 8 | git: 9 | depth: 1 10 | 11 | install: 12 | - go mod download 13 | - go get github.com/golangci/golangci-lint/cmd/golangci-lint 14 | 15 | script: 16 | - go build -o ./exalysis cmd/exalysis/*.go 17 | - go test -cover -tags=testing ./... 18 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Introduction 4 | 5 | Exercism is a platform centered around empathetic conversation. 6 | We have a low tolerance for communication that makes anyone feel unwelcome, unsupported, insulted or discriminated against. 7 | 8 | ## Seen or experienced something uncomfortable? 9 | 10 | If you see or experience abuse, harassment, discrimination, or feel unsafe or upset, please email [abuse@exercism.org](mailto:abuse@exercism.org?subject=%5BCoC%5D) and include \[CoC\] in the subject line. 11 | We will follow up with you as a priority. 12 | 13 | ## Enforcement 14 | 15 | We actively monitor for Code of Conduct (CoC) violations and take any reports of violations extremely seriously. 16 | We have banned contributors, mentors and users due to violations. 17 | 18 | After we receive a report of a CoC violation, we view that person's conversation history on Exercism and related communication channels and attempt to understand whether someone has deliberately broken the CoC, or accidentally crossed a line. 19 | We generally reach out to the person who has been reported to discuss any concerns we have and warn them that repeated violations will result in a ban. 20 | Sometimes we decide that no violation has occurred and that no action is required and sometimes we will also ban people on a first offense. 21 | We strive to be fair, but will err on the side of protecting the culture of our community. 22 | 23 | Exercism's leadership reserve the right to take whatever action they feel appropriate with regards to CoC violations. 24 | 25 | ## The simple version 26 | 27 | - Be empathetic 28 | - Be welcoming 29 | - Be kind 30 | - Be honest 31 | - Be supportive 32 | - Be polite 33 | 34 | ## The details 35 | 36 | Exercism should be a safe place for everybody regardless of 37 | 38 | - Gender, gender identity or gender expression 39 | - Sexual orientation 40 | - Disability 41 | - Physical appearance (including but not limited to body size) 42 | - Race 43 | - Age 44 | - Religion 45 | - Anything else you can think of 46 | 47 | As someone who is part of this community, you agree that: 48 | 49 | - We are collectively and individually committed to safety and inclusivity 50 | - We have zero tolerance for abuse, harassment, or discrimination 51 | - We respect people’s boundaries and identities 52 | - We refrain from using language that can be considered offensive or oppressive (systemically or otherwise), eg. sexist, racist, homophobic, transphobic, ableist, classist, etc. 53 | - this includes (but is not limited to) various slurs. 54 | - We avoid using offensive topics as a form of humor 55 | 56 | We actively work towards: 57 | 58 | - Being a safe community 59 | - Cultivating a network of support & encouragement for each other 60 | - Encouraging responsible and varied forms of expression 61 | 62 | We condemn: 63 | 64 | - Stalking, doxxing, or publishing private information 65 | - Violence, threats of violence or violent language 66 | - Anything that compromises people’s safety 67 | - Conduct or speech which might be considered sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory or offensive in nature 68 | - The use of unwelcome, suggestive, derogatory or inappropriate nicknames or terms 69 | - Disrespect towards others (jokes, innuendo, dismissive attitudes) and towards differences of opinion 70 | - Intimidation or harassment (online or in-person). 71 | Please read the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md) for how we interpret harassment 72 | - Inappropriate attention or contact 73 | - Not understanding the differences between constructive criticism and disparagement 74 | 75 | These things are NOT OK. 76 | 77 | Be aware of how your actions affect others. 78 | If it makes someone uncomfortable, stop. 79 | 80 | If you say something that is found offensive, and you are called out on it, try to: 81 | 82 | - Listen without interruption 83 | - Believe what the person is saying & do not attempt to disqualify what they have to say 84 | - Ask for tips / help with avoiding making the offense in the future 85 | - Apologize and ask forgiveness 86 | 87 | ## History 88 | 89 | This policy was initially adopted from the Front-end London Slack community and has been modified since. 90 | A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md). 91 | 92 | _This policy is a "living" document, and subject to refinement and expansion in the future. 93 | This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Discord, Forum, Twitter, email) and any other Exercism entity or event._ 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exalysis 2 | 3 | [![Build Status](https://travis-ci.org/tehsphinx/exalysis.svg?branch=master)](https://travis-ci.org/tehsphinx/exalysis) 4 | 5 | Exalysis is a tool designed to help mentors of the [Exercism](https://Exercism.io) Go track. It will automatically run the tests on a student's solution, check `gofmt` and `golint`, and make some helpful suggestions for the student based on static analysis of the code for common errors and patterns (see [sample Exalysis output](sample.md)). 6 | 7 | It's not perfect: you should check Exalysis's suggestions before sending them to the student, and add your own notes if necessary, but it can dramatically speed up the process of mentoring by automating these repetitive tasks. 8 | 9 | ## Status 10 | 11 | This is a proof of concept. I use it to mentor, and enhance/extend it as I go. 12 | 13 | ***Exercise implementations:*** 14 | - Two Fer 15 | - Hamming 16 | - Raindrops 17 | - Scrabble Score 18 | - Isogram 19 | - Difference of Squares 20 | - Luhn 21 | - Parallel Letter Frequency 22 | 23 | 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.) 24 | 25 | ## Installation 26 | 27 | Make sure you are in any go module path on your computer. If you have no local modules (or are not sure what that means), choose any empty folder and use `go mod init myModule` to create a module. This will create a `go.mod` file in that folder. 28 | 29 | Then install exalysis with this command: 30 | 31 | ``` 32 | go install github.com/exercism/exalysis/cmd/exalysis 33 | ``` 34 | 35 | This will build and place the executable in your `$GOPATH/bin` directory which is usually `$HOME/go/bin`. Add this directory to the $PATH environment variable to be able to use exalysis (and other go tools) from anywhere. 36 | 37 | ## Usage 38 | 39 | ### Watch mode 40 | 41 | To start Exalysis in watch mode, run this command: 42 | 43 | ``` 44 | exalysis -watch 45 | ``` 46 | 47 | When you copy an Exercism download command to the clipboard, Exalysis will detect this and do the following: 48 | 49 | - Download exercise and run tests, lint checks, and so on 50 | - Analyse the code and make suggestions for improvement 51 | 52 | The output of Exalysis is automatically copied to the clipboard, so all you need to do is paste into the comment box on the Exercism site and review your answer. 53 | 54 | ### Single exercise examination 55 | 56 | With no arguments, the `exalysis` command will analyse the solution in the current directory and write its suggestions to the standard output. 57 | 58 | ``` 59 | exalysis 60 | ``` 61 | 62 | ## Features 63 | 64 | - Auto-detect the exercise from the package name and the student's name from the current path. 65 | - Run a list of tools to check the code and output the result. See supported tools below. 66 | - Check supported exercises for special patterns and create todos, suggestions and comments based on that. 67 | - Sometimes adds an entire block packed with knowledge about a certain topic. 68 | - Outputs a complete answer ready to be pasted into Exercism. The answer is already in the clipboard when the tool finishes. 69 | - Provides a suggestion to the mentor whether to approve or not. 70 | 71 | ## Tools 72 | 73 | Exalysis runs the following tools on each solution: 74 | 75 | - `golint` 76 | - `gofmt` 77 | - `go test` 78 | - `go test -race` (for exercises involving concurrency) 79 | - `go vet` 80 | - `golangci-lint` 81 | - `go test -bench` (when run with the `-bench` flag) 82 | 83 | ## My Typical Workflow 84 | 85 | 1. Start exalysis in `-watch` mode. 86 | 1. Split screen 2/3 Exercism, 1/3 terminal. 87 | 1. Filter Exercism by a certain exercise. For me mentoring the same exercise a few times is much more effective then constantly switching contexts. 88 | 1. Start mentoring a solution and copy the download command. 89 | 1. Wait for Exalysis to run on in the terminal. It will put the answer to be pasted in the clipboard. 90 | 1. Paste the answer to the student on Exercism and validate what it says against the student's solution. 91 | 1. Adjust the answer and submit/approve. 92 | 93 | **Warning!** The tool is **not** to be used without examining the code and trying to figure out how to best help the student. The output should not be used without checking it! There might be false positives or missing suggestions or simply too much information for the student to handle at once. 94 | 95 | ## Contribution 96 | Any contribution is welcome as long as it complies with the code of conduct of [Exercism](https://Exercism.io) and the mentoring guidelines. 97 | 98 | - You can open an issue if you find a false positive that you think will happen more than once and should be fixed. The same with suggestions you think are missing. 99 | 100 | - One very simple way to contribute is to add samples for a case that is not covered yet, add the comment that should be shown to the student and add a test for it. That could be done for an already supported exercise or one that is not supported yet. The existing test structure is a good sample. 101 | 102 | - There are still many exercises to be implemented. If you're up for implementing a new exercise, that would be much appreciated. 103 | 104 | ## Discussion 105 | 106 | There's a channel on the Exercism Team Slack for discussion, development, and questions about Exalysis: [#track-go-exalysis](https://exercism-team.slack.com/messages/CE6EMAFEZ). 107 | 108 | If you're not already on this Slack, email mentoring@exercism.io to request an invite. 109 | -------------------------------------------------------------------------------- /cmd/exalysis/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "regexp" 10 | "strings" 11 | "time" 12 | 13 | "github.com/exercism/exalysis" 14 | "github.com/exercism/exalysis/exam" 15 | "github.com/tehsphinx/clipboard" 16 | ) 17 | 18 | var ( 19 | minConfidence = flag.Float64("min_confidence", 0.8, "golint: minimum confidence of a problem to print it") 20 | watch = flag.Bool("watch", false, "starts Exalysis in watch mode, waiting for 'exercism download ...' commands in the clipboard") 21 | outputAnswer = flag.Bool("output", false, "outputs the answer to the student. Only applies to watch mode, where the answer is usually suppressed") 22 | bench = flag.Bool("bench", false, "runs the benchmarks and outputs the result") 23 | ) 24 | 25 | func main() { 26 | flag.Parse() 27 | exam.LintMinConfidence = *minConfidence 28 | exam.Benchmarks = *bench 29 | 30 | if *watch { 31 | watchClipboard() 32 | return 33 | } 34 | 35 | sugg, approval := exalysis.GetSuggestions(".") 36 | 37 | fmt.Println("\n" + sugg) 38 | fmt.Print("\n\n" + approval) 39 | if err := clipboard.WriteAll(sugg); err != nil { 40 | log.Println(err) 41 | } 42 | } 43 | 44 | func watchClipboard() { 45 | ch, cancel := clipboard.Watch(100 * time.Millisecond) 46 | defer cancel() 47 | 48 | fmt.Println("Watching for Exercism download links on the clipboard...") 49 | 50 | for clip := range ch { 51 | cmdText, ok := checkExercismDownload(clip) 52 | if !ok { 53 | continue 54 | } 55 | 56 | path, err := downloadExercise(cmdText) 57 | if err != nil { 58 | log.Println(err) 59 | continue 60 | } 61 | 62 | fmt.Printf("<-- Exalysis: %s -->\n", path) 63 | 64 | sugg, approval := exalysis.GetSuggestions(path) 65 | 66 | if *outputAnswer { 67 | fmt.Println("\n" + sugg) 68 | } 69 | fmt.Print("\n\n" + approval) 70 | if err := clipboard.WriteAll(sugg); err != nil { 71 | log.Println(err) 72 | } 73 | } 74 | } 75 | 76 | var downloadRegex = regexp.MustCompile("^exercism download --uuid=[[:xdigit:]]+$") 77 | 78 | func checkExercismDownload(clip string) (string, bool) { 79 | if downloadRegex.MatchString(clip) { 80 | return clip, true 81 | } 82 | return "", false 83 | } 84 | 85 | func downloadExercise(cmdString string) (string, error) { 86 | parts := strings.Split(cmdString, " ") 87 | cmd := exec.Command(parts[0], parts[1:]...) 88 | b, err := cmd.Output() 89 | if err != nil { 90 | return "", fmt.Errorf("error executing: %s: %s", strings.Join(parts, " "), err) 91 | } 92 | 93 | path := strings.TrimSpace(string(b)) 94 | if _, err := os.Stat(path); err != nil { 95 | return "", err 96 | } 97 | return path, err 98 | } 99 | -------------------------------------------------------------------------------- /cmd/exalysis/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | const exePath = "./exalysis" 12 | 13 | func build() error { 14 | out, err := exec.Command("go", "build", "-o", exePath, "main.go").CombinedOutput() 15 | if err != nil { 16 | return fmt.Errorf("failed to build: %s\n%s", err, out) 17 | } 18 | return nil 19 | } 20 | 21 | func TestHappyPath(t *testing.T) { 22 | if err := build(); err != nil { 23 | t.Fatal(err) 24 | } 25 | cmd := exec.Command("../../" + exePath) 26 | cmd.Dir = "./testdata/happypath" 27 | output, err := cmd.CombinedOutput() 28 | if err != nil { 29 | t.Fatalf("%s: %s", err, output) 30 | } 31 | assert.Contains(t, string(output), "This looks excellent!") 32 | } 33 | 34 | func TestCompileError(t *testing.T) { 35 | if err := build(); err != nil { 36 | t.Fatal(err) 37 | } 38 | cmd := exec.Command("../../" + exePath) 39 | cmd.Dir = "./testdata/compile_error" 40 | output, err := cmd.CombinedOutput() 41 | if err != nil { 42 | t.Fatalf("%s: %s", err, output) 43 | } 44 | assert.Contains(t, string(output), "does not compile") 45 | } 46 | 47 | func TestCompileError2(t *testing.T) { 48 | if err := build(); err != nil { 49 | t.Fatal(err) 50 | } 51 | cmd := exec.Command("../../" + exePath) 52 | cmd.Dir = "./testdata/compile_error2" 53 | output, err := cmd.CombinedOutput() 54 | if err != nil { 55 | t.Fatalf("%s: %s", err, output) 56 | } 57 | assert.Contains(t, string(output), "does not pass the tests") 58 | assert.NotContains(t, string(output), "race conditions") 59 | assert.NotContains(t, string(output), "`go vet`") 60 | } 61 | 62 | func TestVetError(t *testing.T) { 63 | if err := build(); err != nil { 64 | t.Fatal(err) 65 | } 66 | cmd := exec.Command("../../" + exePath) 67 | cmd.Dir = "./testdata/vet_error" 68 | output, err := cmd.CombinedOutput() 69 | if err != nil { 70 | t.Fatalf("%s: %s", err, output) 71 | } 72 | assert.NotContains(t, string(output), "`golint`") 73 | assert.Contains(t, string(output), "`go vet`") 74 | } 75 | 76 | func TestTip(t *testing.T) { 77 | if err := build(); err != nil { 78 | t.Fatal(err) 79 | } 80 | cmd := exec.Command("../../" + exePath) 81 | cmd.Dir = "./testdata/happypath" 82 | output, err := cmd.CombinedOutput() 83 | if err != nil { 84 | t.Fatalf("%s: %s", err, output) 85 | } 86 | assert.Contains(t, string(output), "might find interesting") 87 | } 88 | -------------------------------------------------------------------------------- /cmd/exalysis/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is targeted at development and manual testing needs. 4 | # To run in production, build an executable and use that. 5 | # This file can serve as a guideline for building. See below. 6 | 7 | pushd $GOPATH/src/github.com/exercism/exalysis >/dev/null 8 | 9 | # building starts here 10 | GO111MODULE=on go generate ./... 11 | GO111MODULE=on go build -o ./exalysis ./cmd/exalysis 12 | # building ends here. Move exalysis executable to a folder contained in PATH variable. 13 | 14 | popd >/dev/null 15 | mv $GOPATH/src/github.com/exercism/exalysis/exalysis . 16 | ./exalysis 17 | -------------------------------------------------------------------------------- /cmd/exalysis/testdata/compile_error/solution.go: -------------------------------------------------------------------------------- 1 | // Package twofer implements sharing functionality 2 | package twofer 3 | 4 | import "fmt" 5 | 6 | // ShareWith function 7 | func ShareWith(name string) string { 8 | if oops != "" { 9 | name = "you" 10 | } 11 | return fmt.Sprintf("One for %v, one for me.", name) 12 | 13 | } 14 | -------------------------------------------------------------------------------- /cmd/exalysis/testdata/compile_error2/cases_test.go: -------------------------------------------------------------------------------- 1 | package space 2 | 3 | // Source: exercism/problem-specifications 4 | // Commit: 8d4df79 space-age: Apply new "input" policy 5 | // Problem Specifications Version: 1.1.0 6 | 7 | var testCases = []struct { 8 | description string 9 | planet Planet 10 | seconds float64 11 | expected float64 12 | }{ 13 | { 14 | description: "age on Earth", 15 | planet: "Earth", 16 | seconds: 1000000000, 17 | expected: 31.69, 18 | }, 19 | { 20 | description: "age on Mercury", 21 | planet: "Mercury", 22 | seconds: 2134835688, 23 | expected: 280.88, 24 | }, 25 | { 26 | description: "age on Venus", 27 | planet: "Venus", 28 | seconds: 189839836, 29 | expected: 9.78, 30 | }, 31 | { 32 | description: "age on Mars", 33 | planet: "Mars", 34 | seconds: 2329871239, 35 | expected: 39.25, 36 | }, 37 | { 38 | description: "age on Jupiter", 39 | planet: "Jupiter", 40 | seconds: 901876382, 41 | expected: 2.41, 42 | }, 43 | { 44 | description: "age on Saturn", 45 | planet: "Saturn", 46 | seconds: 3000000000, 47 | expected: 3.23, 48 | }, 49 | { 50 | description: "age on Uranus", 51 | planet: "Uranus", 52 | seconds: 3210123456, 53 | expected: 1.21, 54 | }, 55 | { 56 | description: "age on Neptune", 57 | planet: "Neptune", 58 | seconds: 8210123456, 59 | expected: 1.58, 60 | }, 61 | } 62 | -------------------------------------------------------------------------------- /cmd/exalysis/testdata/compile_error2/solution.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package space has a single function that converts the given seconds in the age of the given planet. 3 | */ 4 | package space 5 | 6 | // Converts the given seconds in the age of the given planet 7 | func Age(seconds float64, planet string) float64 { 8 | // A map to store the proportions between the planets age and the planet Earth 9 | proportions := make(map[string]float64) 10 | proportions["Earth"] = 1 11 | proportions["Mercury"] = 0.2408467 12 | proportions["Venus"] = 0.61519726 13 | proportions["Mars"] = 1.8808158 14 | proportions["Jupiter"] = 11.862615 15 | proportions["Saturn"] = 29.447498 16 | proportions["Uranus"] = 84.016846 17 | proportions["Neptune"] = 164.79132 18 | 19 | // Calculates the age based on the proportions 20 | // 31557600 is equivalent as 365.25 days on earth (one year) 21 | return seconds / 31557600 / proportions[planet] 22 | } 23 | -------------------------------------------------------------------------------- /cmd/exalysis/testdata/compile_error2/space_age_test.go: -------------------------------------------------------------------------------- 1 | package space 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | ) 7 | 8 | func TestAge(t *testing.T) { 9 | const precision = 0.01 10 | for _, tc := range testCases { 11 | actual := Age(tc.seconds, tc.planet) 12 | if math.IsNaN(actual) || math.Abs(actual-tc.expected) > precision { 13 | t.Fatalf("FAIL: %s\nExpected: %#v\nActual: %#v", tc.description, tc.expected, actual) 14 | } 15 | t.Logf("PASS: %s", tc.description) 16 | } 17 | } 18 | 19 | func BenchmarkAge(b *testing.B) { 20 | for i := 0; i < b.N; i++ { 21 | for _, tc := range testCases { 22 | Age(tc.seconds, tc.planet) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cmd/exalysis/testdata/happy_twofer/solution.go: -------------------------------------------------------------------------------- 1 | // Package twofer implements sharing functionality 2 | package twofer 3 | 4 | import "fmt" 5 | 6 | // ShareWith function 7 | func ShareWith(name string) string { 8 | if name != "" { 9 | name = "you" 10 | } 11 | return fmt.Sprintf("One for %v, one for me.", name) 12 | 13 | } 14 | -------------------------------------------------------------------------------- /cmd/exalysis/testdata/happypath/solution.go: -------------------------------------------------------------------------------- 1 | package collatzconjecture 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // CollatzConjecture - Given a number n, returns the number of steps required to reach 1. 8 | func CollatzConjecture(number int) (int, error) { 9 | if number < 1 { 10 | return 0, errors.New("`n` can not be less than 1") 11 | } 12 | 13 | var steps int 14 | 15 | for number != 1 { 16 | steps++ 17 | 18 | if number%2 == 0 { 19 | number /= 2 20 | continue 21 | } 22 | 23 | number *= 3 24 | number++ 25 | } 26 | 27 | return steps, nil 28 | } 29 | -------------------------------------------------------------------------------- /cmd/exalysis/testdata/vet_error/solution.go: -------------------------------------------------------------------------------- 1 | // Package tournament keeps tab on USPL 2 | package tournament 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "io" 8 | "sort" 9 | "strings" 10 | ) 11 | 12 | const ( 13 | firstTeamName = iota 14 | secondTeamName 15 | matchResult 16 | ) 17 | 18 | const poolSize = 4 19 | 20 | var poolA = [poolSize]string{ 21 | "Allegoric Alaskians", 22 | "Blithering Badgers", 23 | "Courageous Californians", 24 | "Devastating Donkeys", 25 | } 26 | 27 | type score struct { 28 | teamName string 29 | matchesPlayed int 30 | won int 31 | drawn int 32 | lost int 33 | points int 34 | } 35 | 36 | // Tally reads raw data from input and outpts tallied results 37 | func Tally(input io.Reader, output io.Writer) error { 38 | 39 | var scoreCard [poolSize]*score 40 | var nameToIndex = map[string]int{} 41 | 42 | // Create score card to keep track of result 43 | for i, name := range poolA { 44 | card := new(score) 45 | card.teamName = name 46 | scoreCard[i] = card 47 | nameToIndex[name] = i 48 | } 49 | 50 | // INPUT 51 | 52 | var resultsTable strings.Builder 53 | 54 | p := make([]byte, 100) 55 | for { 56 | n, err := input.Read(p) 57 | resultsTable.Write(p[:n]) 58 | if io.EOF == err { 59 | break 60 | } 61 | } 62 | 63 | results := strings.Split(resultsTable.String(), "\n") 64 | 65 | for _, resultLine := range results { 66 | details := strings.Split(resultLine, ";") 67 | 68 | // VALIDATE 69 | 70 | switch { 71 | // Ignore commented lines 72 | case strings.HasPrefix(resultLine, "#"): 73 | continue 74 | // Ignore emplty lines 75 | case len(details) == 1 && len(details[0]) == 0: 76 | continue 77 | // Not fully formed 78 | case len(details) < 3: 79 | return errors.New("Invalid input") 80 | continue 81 | } 82 | 83 | // PROCESS 84 | 85 | var firstTeam, secondTeam *score 86 | var index int 87 | var teamValid bool 88 | 89 | // Get first team stats 90 | teamName := details[firstTeamName] 91 | index, teamValid = nameToIndex[teamName] 92 | if !teamValid { 93 | return errors.New("Invalid team found") 94 | } 95 | firstTeam = scoreCard[index] 96 | 97 | // Get second team stats 98 | teamName = details[secondTeamName] 99 | index, teamValid = nameToIndex[teamName] 100 | if !teamValid { 101 | return errors.New("Invalid team found") 102 | } 103 | secondTeam = scoreCard[index] 104 | 105 | // Update scores. 106 | firstTeam.matchesPlayed++ 107 | secondTeam.matchesPlayed++ 108 | 109 | switch details[matchResult] { 110 | case "win": 111 | firstTeam.won++ 112 | secondTeam.lost++ 113 | firstTeam.points += 3 114 | 115 | case "loss": 116 | firstTeam.lost++ 117 | secondTeam.won++ 118 | secondTeam.points += 3 119 | case "draw": 120 | firstTeam.drawn++ 121 | secondTeam.drawn++ 122 | firstTeam.points++ 123 | secondTeam.points++ 124 | default: 125 | return errors.New("Invalid result found") 126 | } 127 | } 128 | 129 | // SORT RESULTS 130 | 131 | sortedCard := scoreCard[:] 132 | sort.Slice(sortedCard, func(i, j int) bool { 133 | if sortedCard[i].points == sortedCard[j].points { 134 | return sortedCard[i].teamName < sortedCard[j].teamName 135 | } 136 | return sortedCard[i].points > sortedCard[j].points 137 | }) 138 | 139 | // OUTPUT 140 | 141 | fmt.Fprintf(output, "%-31s|%3s |%3s |%3s |%3s |%3s\n", "Team", "MP", "W", "D", "L", "P") 142 | 143 | for _, v := range sortedCard { 144 | fmt.Fprintf(output, "%-31s|%3d |%3d |%3d |%3d |%3d\n", 145 | v.teamName, v.matchesPlayed, v.won, v.drawn, v.lost, v.points) 146 | } 147 | 148 | return nil 149 | } 150 | -------------------------------------------------------------------------------- /exam/exam.go: -------------------------------------------------------------------------------- 1 | package exam 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/exercism/exalysis/extypes" 7 | "github.com/tehsphinx/astrav" 8 | ) 9 | 10 | // Result contains the result if running all examinations 11 | type Result struct { 12 | GoLint bool 13 | GoFmt bool 14 | GoTest bool 15 | GoBench bool 16 | GoVet bool 17 | GolangCILint bool 18 | } 19 | 20 | // All runs all examinations contained in this package and returns the result 21 | func All(folder *astrav.Folder, r *extypes.Response, pkgName string, analyze bool) (res *Result, err error) { 22 | dir, err := os.Getwd() 23 | if err != nil { 24 | return nil, err 25 | } 26 | if err := os.Chdir(folder.GetPath()); err != nil { 27 | return nil, err 28 | } 29 | defer func() { 30 | if r := os.Chdir(dir); r != nil { 31 | err = r 32 | } 33 | }() 34 | 35 | test, errTest := GoTest(folder, r, pkgName) 36 | res = &Result{ 37 | GoTest: test, 38 | } 39 | 40 | resp := r 41 | if analyze { 42 | resp = &extypes.Response{} 43 | } 44 | res.GoLint = GoLint(folder, resp) 45 | res.GoFmt = GoFmt(folder, resp) 46 | res.GoVet = GoVet(folder, r, pkgName, errTest != nil) 47 | res.GolangCILint = GolangCILint(folder, r, pkgName) 48 | res.GoBench = GoBench(folder, r, pkgName, errTest != nil) 49 | 50 | return res, err 51 | } 52 | -------------------------------------------------------------------------------- /exam/gobench.go: -------------------------------------------------------------------------------- 1 | package exam 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | 9 | "github.com/exercism/exalysis/extypes" 10 | "github.com/exercism/exalysis/gtpl" 11 | "github.com/logrusorgru/aurora" 12 | "github.com/tehsphinx/astrav" 13 | ) 14 | 15 | var ( 16 | // Benchmarks setting whether to run the benchmarks or just the tests 17 | Benchmarks bool 18 | ) 19 | 20 | // GoBench runs `go test` on provided path and adds suggestions to the response 21 | func GoBench(_ *astrav.Folder, r *extypes.Response, _ string, skip bool) bool { 22 | if !Benchmarks || skip { 23 | fmt.Println(aurora.Gray("benchmarks:\t"), aurora.Brown("SKIPPED")) 24 | return true 25 | } 26 | 27 | res, state := bench() 28 | 29 | if state.Success() { 30 | fmt.Println(aurora.Gray("benchmarks:\t"), aurora.Green("OK")) 31 | } else { 32 | fmt.Println(aurora.Gray("benchmarks:\t"), aurora.Red("FAIL")) 33 | } 34 | 35 | fmt.Println(res) 36 | 37 | if state.Success() { 38 | return true 39 | } 40 | r.AppendTodoTpl(gtpl.PassTests) 41 | return false 42 | } 43 | 44 | func bench() (string, *os.ProcessState) { 45 | cmd := exec.Command("go", "test", "-v", "--bench", ".", "--benchmem", "-test.run=^$") 46 | 47 | b, err := cmd.CombinedOutput() 48 | if err != nil { 49 | log.Println("error running go test: ", err) 50 | } 51 | 52 | return string(b), cmd.ProcessState 53 | } 54 | -------------------------------------------------------------------------------- /exam/gobench_test.go: -------------------------------------------------------------------------------- 1 | package exam 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "testing" 8 | 9 | "github.com/exercism/exalysis/extypes" 10 | "github.com/exercism/exalysis/gtpl" 11 | "github.com/exercism/exalysis/testhelper" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | var benchTests = []struct { 16 | path string 17 | expected bool 18 | expectSugg bool 19 | suggestion gtpl.Template 20 | pkgName string 21 | }{ 22 | {path: "./solutions/0", expected: false}, 23 | {path: "./solutions/1", expected: true, pkgName: "twofer"}, 24 | {path: "./solutions/2", expected: true, pkgName: "hamming"}, 25 | } 26 | 27 | func TestGoBench(t *testing.T) { 28 | Benchmarks = true 29 | dir, err := os.Getwd() 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | defer func() { 34 | if r := os.Chdir(dir); r != nil { 35 | err = r 36 | } 37 | }() 38 | 39 | for _, test := range benchTests { 40 | folder, _, err := testhelper.LoadFolder(path.Join(dir, test.path)) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | if err := os.Chdir(folder.GetPath()); err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | r := extypes.NewResponse() 49 | ok := GoBench(folder, r, test.pkgName, false) 50 | 51 | failMsg := fmt.Sprintf("test failed: %+v", test) 52 | assert.Equal(t, test.expected, ok, failMsg) 53 | if test.suggestion != nil { 54 | assert.Equal(t, test.expectSugg, r.HasSuggestion(test.suggestion), failMsg) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /exam/gofmt.go: -------------------------------------------------------------------------------- 1 | package exam 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/format" 7 | 8 | "github.com/exercism/exalysis/extypes" 9 | "github.com/exercism/exalysis/gtpl" 10 | "github.com/logrusorgru/aurora" 11 | "github.com/pmezard/go-difflib/difflib" 12 | "github.com/tehsphinx/astrav" 13 | ) 14 | 15 | // GoFmt runs gofmt on provided go files and adds suggestions to the response 16 | func GoFmt(folder *astrav.Folder, r *extypes.Response) bool { 17 | files := folder.GetRawFiles() 18 | 19 | resFmt := fmtCode(files) 20 | if resFmt == "" { 21 | fmt.Println(aurora.Gray("gofmt:\t\t"), aurora.Green("OK")) 22 | return true 23 | } 24 | 25 | fmtOuput("gofmt", resFmt) 26 | r.AppendTodoTpl(gtpl.NotFormatted) 27 | return false 28 | } 29 | 30 | func fmtCode(files map[string][]byte) string { 31 | for _, file := range files { 32 | file = bytes.Replace(file, []byte{'\r', '\n'}, []byte{'\n'}, -1) 33 | f, err := format.Source(file) 34 | if err != nil { 35 | return fmt.Sprintf("code fails to format with error: %s\n", err) 36 | } 37 | if string(f) != string(file) && string(f) != string(append(file, '\n')) { 38 | return getDiff(file, f) 39 | } 40 | } 41 | return "" 42 | } 43 | 44 | func getDiff(current, formatted []byte) string { 45 | diff := difflib.UnifiedDiff{ 46 | A: difflib.SplitLines(string(current)), 47 | B: difflib.SplitLines(string(formatted)), 48 | FromFile: "Current", 49 | ToFile: "Formatted", 50 | Context: 0, 51 | } 52 | text, err := difflib.GetUnifiedDiffString(diff) 53 | if err != nil { 54 | return fmt.Sprintf("error while diffing strings: %s", err) 55 | } 56 | return text 57 | } 58 | -------------------------------------------------------------------------------- /exam/golangci-lint.go: -------------------------------------------------------------------------------- 1 | package exam 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | 9 | "github.com/exercism/exalysis/extypes" 10 | "github.com/logrusorgru/aurora" 11 | "github.com/tehsphinx/astrav" 12 | ) 13 | 14 | // GolangCILint runs GolangCI-Lint on provided go files and adds suggestions to the response. 15 | // This runs a lot of linters. Goal is to cover a lot for now. Later individual linters can be run 16 | // directly and disabled in GolangCI-Linter. 17 | func GolangCILint(_ *astrav.Folder, _ *extypes.Response, _ string) bool { 18 | res, state, err := golangCILint() 19 | if err != nil { 20 | fmt.Println(aurora.Gray("golangci-lint:\t"), aurora.Brown("SKIPPED"), aurora.Gray("\t(is GolangCI-Lint installed?)")) 21 | fmt.Println(err) 22 | return true 23 | } 24 | 25 | if state.Success() { 26 | fmt.Println(aurora.Gray("golangci-lint:\t"), aurora.Green("OK")) 27 | return true 28 | } 29 | 30 | fmt.Println(aurora.Gray("golangci-lint:\t"), aurora.Red("FAIL")) 31 | fmt.Println(res) 32 | return false 33 | } 34 | 35 | func golangCILint() (string, *os.ProcessState, error) { 36 | cmd := exec.Command("golangci-lint", "run", "--disable", "govet,megacheck", "--skip-files=_test.go$") 37 | 38 | b, err := cmd.CombinedOutput() 39 | if err != nil && !strings.HasPrefix(err.Error(), "exit status") { 40 | return "", nil, err 41 | } 42 | 43 | return string(b), cmd.ProcessState, nil 44 | } 45 | -------------------------------------------------------------------------------- /exam/golangci-lint_test.go: -------------------------------------------------------------------------------- 1 | package exam 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "testing" 8 | 9 | "github.com/exercism/exalysis/extypes" 10 | "github.com/exercism/exalysis/gtpl" 11 | "github.com/exercism/exalysis/testhelper" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | var golangCILintTests = []struct { 16 | path string 17 | expected bool 18 | expectSugg bool 19 | suggestion gtpl.Template 20 | pkgName string 21 | }{ 22 | {path: "./solutions/0", expected: false}, 23 | {path: "./solutions/1", expected: true, pkgName: "twofer"}, 24 | {path: "./solutions/2", expected: true, pkgName: "hamming"}, 25 | {path: "./solutions/3", expected: true}, 26 | {path: "./solutions/4", expected: false}, 27 | } 28 | 29 | func TestGolangCILint(t *testing.T) { 30 | dir, err := os.Getwd() 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | defer func() { 35 | if r := os.Chdir(dir); r != nil { 36 | err = r 37 | } 38 | }() 39 | 40 | for _, test := range golangCILintTests { 41 | folder, _, err := testhelper.LoadFolder(path.Join(dir, test.path)) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | if err := os.Chdir(folder.GetPath()); err != nil { 46 | t.Fatal(err) 47 | } 48 | 49 | r := extypes.NewResponse() 50 | ok := GolangCILint(folder, r, test.pkgName) 51 | 52 | failMsg := fmt.Sprintf("test failed: %+v", test) 53 | assert.Equal(t, test.expected, ok, failMsg) 54 | if test.suggestion != nil { 55 | assert.Equal(t, test.expectSugg, r.HasSuggestion(test.suggestion), failMsg) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /exam/golint.go: -------------------------------------------------------------------------------- 1 | package exam 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/exercism/exalysis/extypes" 8 | "github.com/exercism/exalysis/gtpl" 9 | "github.com/logrusorgru/aurora" 10 | "github.com/tehsphinx/astrav" 11 | "golang.org/x/lint" 12 | ) 13 | 14 | var ( 15 | // LintMinConfidence sets the min confidence for linting 16 | LintMinConfidence float64 17 | ) 18 | 19 | // GoLint runs golint on provided go files and adds suggestions to the response 20 | func GoLint(folder *astrav.Folder, r *extypes.Response) bool { 21 | files := folder.GetRawFiles() 22 | resLint := lintCode(files) 23 | if resLint == "" { 24 | fmt.Println(aurora.Gray("golint:\t\t"), aurora.Green("OK")) 25 | return true 26 | } 27 | 28 | fmtOuput("golint", resLint) 29 | r.AppendTodoTpl(gtpl.NotLinted) 30 | return false 31 | } 32 | 33 | func fmtOuput(tool, result string) { 34 | fmt.Println(aurora.Gray(fmt.Sprintf("%s:\t\t", tool)), aurora.Red("FAIL")) 35 | fmt.Println(result) 36 | } 37 | 38 | func lintCode(files map[string][]byte) string { 39 | l := lint.Linter{} 40 | ps, err := l.LintFiles(files) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | var lintRes string 46 | for _, p := range ps { 47 | if p.Confidence < LintMinConfidence { 48 | continue 49 | } 50 | lintRes += fmt.Sprintf("%s: %s\n\t%s\n\tdoc: %s\n", p.Category, p.Text, p.Position.String(), p.Link) 51 | } 52 | return lintRes 53 | } 54 | -------------------------------------------------------------------------------- /exam/gotest.go: -------------------------------------------------------------------------------- 1 | package exam 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | 9 | "github.com/exercism/exalysis/extypes" 10 | "github.com/exercism/exalysis/gtpl" 11 | "github.com/logrusorgru/aurora" 12 | "github.com/tehsphinx/astrav" 13 | ) 14 | 15 | var skipRaceExercises = []string{ 16 | "twofer", 17 | "hamming", 18 | "raindrops", 19 | "scrabble", 20 | "isogram", 21 | "diffsquares", 22 | "luhn", 23 | "grains", 24 | "clock", 25 | "erratum", 26 | } 27 | 28 | // GoTest runs `go test` on provided path and adds suggestions to the response 29 | func GoTest(_ *astrav.Folder, r *extypes.Response, pkgName string) (bool, error) { 30 | succTest, err := goTest(r) 31 | succTestRace := goTestRace(r, pkgName, err != nil) 32 | 33 | return succTest && succTestRace, err 34 | } 35 | 36 | func goTest(r *extypes.Response) (bool, error) { 37 | res, state, err := test() 38 | 39 | if state.Success() { 40 | fmt.Println(aurora.Gray("go test:\t"), aurora.Green("OK")) 41 | return true, nil 42 | } 43 | 44 | fmt.Println(aurora.Gray("go test:\t"), aurora.Red("FAIL")) 45 | fmt.Println(res) 46 | r.AppendTodoTpl(gtpl.PassTests) 47 | return false, err 48 | } 49 | 50 | func test() (string, *os.ProcessState, error) { 51 | cmd := exec.Command("go", "test") 52 | 53 | b, err := cmd.CombinedOutput() 54 | if err != nil { 55 | log.Println("error running go test: ", err) 56 | } 57 | 58 | return string(b), cmd.ProcessState, err 59 | } 60 | 61 | func goTestRace(r *extypes.Response, pkgName string, skip bool) bool { 62 | if skipRace(pkgName) || skip { 63 | fmt.Println(aurora.Gray("go test -race:\t"), aurora.Brown("SKIPPED")) 64 | return true 65 | } 66 | res, state := testRace() 67 | 68 | if state.Success() { 69 | fmt.Println(aurora.Gray("go test -race:\t"), aurora.Green("OK")) 70 | return true 71 | } 72 | 73 | fmt.Println(aurora.Gray("go test -race:\t"), aurora.Red("FAIL")) 74 | fmt.Println(res) 75 | r.AppendTodoTpl(gtpl.RaceCondition) 76 | return false 77 | } 78 | 79 | func testRace() (string, *os.ProcessState) { 80 | cmd := exec.Command("go", "test", "-race") 81 | 82 | b, err := cmd.CombinedOutput() 83 | if err != nil { 84 | log.Println("error running go test -race: ", err) 85 | } 86 | 87 | return string(b), cmd.ProcessState 88 | } 89 | 90 | func skipRace(pkgName string) bool { 91 | for _, n := range skipRaceExercises { 92 | if n == pkgName { 93 | return true 94 | } 95 | } 96 | return false 97 | } 98 | -------------------------------------------------------------------------------- /exam/govet.go: -------------------------------------------------------------------------------- 1 | package exam 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | 9 | "github.com/exercism/exalysis/extypes" 10 | "github.com/exercism/exalysis/gtpl" 11 | "github.com/logrusorgru/aurora" 12 | "github.com/tehsphinx/astrav" 13 | ) 14 | 15 | // GoVet runs go vet on provided go files and adds suggestions to the response 16 | func GoVet(_ *astrav.Folder, r *extypes.Response, pkgName string, skip bool) bool { 17 | if skip { 18 | fmt.Println(aurora.Gray("go vet:\t\t"), aurora.Brown("SKIPPED")) 19 | return true 20 | } 21 | 22 | res, state := goVet() 23 | 24 | if state.Success() { 25 | fmt.Println(aurora.Gray("go vet:\t\t"), aurora.Green("OK")) 26 | return true 27 | } 28 | 29 | fmt.Println(aurora.Gray("go vet:\t\t"), aurora.Red("FAIL")) 30 | fmt.Println(res) 31 | 32 | if pkgName == "twofer" || pkgName == "hamming" { 33 | r.AppendImprovementTpl(gtpl.NotVetted) 34 | } else { 35 | r.AppendTodoTpl(gtpl.NotVetted) 36 | } 37 | 38 | return false 39 | } 40 | 41 | func goVet() (string, *os.ProcessState) { 42 | cmd := exec.Command("go", "vet") 43 | 44 | b, err := cmd.CombinedOutput() 45 | if err != nil { 46 | log.Println("error running go vet: ", err) 47 | } 48 | 49 | return string(b), cmd.ProcessState 50 | } 51 | -------------------------------------------------------------------------------- /exam/govet_test.go: -------------------------------------------------------------------------------- 1 | package exam 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "testing" 8 | 9 | "github.com/exercism/exalysis/extypes" 10 | "github.com/exercism/exalysis/gtpl" 11 | "github.com/exercism/exalysis/testhelper" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | var vetTests = []struct { 16 | path string 17 | expected bool 18 | expectSugg bool 19 | suggestion gtpl.Template 20 | pkgName string 21 | }{ 22 | {path: "./solutions/0", expected: false}, 23 | {path: "./solutions/1", expected: true, pkgName: "twofer"}, 24 | {path: "./solutions/2", expected: true, pkgName: "hamming"}, 25 | {path: "./solutions/3", expected: false}, 26 | } 27 | 28 | func TestGoVet(t *testing.T) { 29 | dir, err := os.Getwd() 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | defer func() { 34 | if r := os.Chdir(dir); r != nil { 35 | err = r 36 | } 37 | }() 38 | 39 | for _, test := range vetTests { 40 | folder, _, err := testhelper.LoadFolder(path.Join(dir, test.path)) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | if err := os.Chdir(folder.GetPath()); err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | r := extypes.NewResponse() 49 | ok := GoVet(folder, r, test.pkgName, false) 50 | 51 | failMsg := fmt.Sprintf("test failed: %+v", test) 52 | assert.Equal(t, test.expected, ok, failMsg) 53 | if test.suggestion != nil { 54 | assert.Equal(t, test.expectSugg, r.HasSuggestion(test.suggestion), failMsg) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /exam/solutions/0/readme.md: -------------------------------------------------------------------------------- 1 | Used for tests with non-existing source code. 2 | -------------------------------------------------------------------------------- /exam/solutions/1/two_fer.go: -------------------------------------------------------------------------------- 1 | // Package twofer is about sharing 2 | package twofer 3 | 4 | import "fmt" 5 | 6 | // ShareWith returns sharing directions 7 | func ShareWith(s string) string { 8 | if s == "" { 9 | s = "you" 10 | } 11 | return fmt.Sprintf("One for %v, one for me.", s) 12 | } 13 | -------------------------------------------------------------------------------- /exam/solutions/1/two_fer_test.go: -------------------------------------------------------------------------------- 1 | package twofer 2 | 3 | import "testing" 4 | 5 | // Define a function ShareWith(string) string. 6 | 7 | func TestShareWith(t *testing.T) { 8 | tests := []struct { 9 | name, expected string 10 | }{ 11 | {"", "One for you, one for me."}, 12 | {"Alice", "One for Alice, one for me."}, 13 | {"Bob", "One for Bob, one for me."}, 14 | } 15 | for _, test := range tests { 16 | if observed := ShareWith(test.name); observed != test.expected { 17 | t.Fatalf("ShareWith(%s) = %v, want %v", test.name, observed, test.expected) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /exam/solutions/2/cases_test.go: -------------------------------------------------------------------------------- 1 | package hamming 2 | 3 | // Source: exercism/problem-specifications 4 | // Commit: f79dfd7 Simplify two tests so the difference is easily spotted. 5 | // Problem Specifications Version: 2.0.1 6 | 7 | var testCases = []struct { 8 | s1 string 9 | s2 string 10 | want int 11 | }{ 12 | { // empty strands 13 | "", 14 | "", 15 | 0, 16 | }, 17 | { // identical strands 18 | "A", 19 | "A", 20 | 0, 21 | }, 22 | { // long identical strands 23 | "GGACTGA", 24 | "GGACTGA", 25 | 0, 26 | }, 27 | { // complete distance in single nucleotide strands 28 | "A", 29 | "G", 30 | 1, 31 | }, 32 | { // complete distance in small strands 33 | "AG", 34 | "CT", 35 | 2, 36 | }, 37 | { // small distance in small strands 38 | "AT", 39 | "CT", 40 | 1, 41 | }, 42 | { // small distance 43 | "GGACG", 44 | "GGTCG", 45 | 1, 46 | }, 47 | { // small distance in long strands 48 | "ACCAGGG", 49 | "ACTATGG", 50 | 2, 51 | }, 52 | { // non-unique character in first strand 53 | "AAG", 54 | "AAA", 55 | 1, 56 | }, 57 | { // non-unique character in second strand 58 | "AAA", 59 | "AAG", 60 | 1, 61 | }, 62 | { // same nucleotides in different positions 63 | "TAG", 64 | "GAT", 65 | 2, 66 | }, 67 | { // large distance 68 | "GATACA", 69 | "GCATAA", 70 | 4, 71 | }, 72 | { // large distance in off-by-one strand 73 | "GGACGGATTCTG", 74 | "AGGACGGATTCT", 75 | 9, 76 | }, 77 | { // disallow first strand longer 78 | "AATG", 79 | "AAA", 80 | -1, 81 | }, 82 | { // disallow second strand longer 83 | "ATA", 84 | "AGTG", 85 | -1, 86 | }, 87 | } 88 | -------------------------------------------------------------------------------- /exam/solutions/2/hamming.go: -------------------------------------------------------------------------------- 1 | // Package hamming provides the hamming algorithm 2 | package hamming 3 | 4 | import "errors" 5 | 6 | // Distance calculates the hamming distance between two strings 7 | func Distance(a, b string) (int, error) { 8 | if len(a) != len(b) { 9 | return 0, errors.New("length is not equal") 10 | } 11 | 12 | var count int 13 | for i, s := range []byte(a) { 14 | if s != b[i] { 15 | count++ 16 | } 17 | } 18 | 19 | return count, nil 20 | } 21 | -------------------------------------------------------------------------------- /exam/solutions/2/hamming_test.go: -------------------------------------------------------------------------------- 1 | package hamming 2 | 3 | import "testing" 4 | 5 | func TestHamming(t *testing.T) { 6 | for _, tc := range testCases { 7 | got, err := Distance(tc.s1, tc.s2) 8 | if tc.want < 0 { 9 | // check if err is of error type 10 | var _ error = err 11 | 12 | // we expect error 13 | if err == nil { 14 | t.Fatalf("Distance(%q, %q). error is nil.", 15 | tc.s1, tc.s2) 16 | } 17 | } else { 18 | if got != tc.want { 19 | t.Fatalf("Distance(%q, %q) = %d, want %d.", 20 | tc.s1, tc.s2, got, tc.want) 21 | } 22 | 23 | // we do not expect error 24 | if err != nil { 25 | t.Fatalf("Distance(%q, %q) returned error: %v when expecting none.", 26 | tc.s1, tc.s2, err) 27 | } 28 | } 29 | } 30 | } 31 | 32 | func BenchmarkHamming(b *testing.B) { 33 | // bench combined time to run through all test cases 34 | for i := 0; i < b.N; i++ { 35 | for _, tc := range testCases { 36 | Distance(tc.s1, tc.s2) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /exam/solutions/3/solution.go: -------------------------------------------------------------------------------- 1 | // +build !testing 2 | 3 | package main 4 | 5 | import "fmt" 6 | 7 | func main() { 8 | a := 0 9 | if a != 1 || a != 2 { 10 | a++ 11 | } 12 | 13 | fmt.Printf("a = %s\n", a) 14 | } 15 | -------------------------------------------------------------------------------- /exam/solutions/4/solution.go: -------------------------------------------------------------------------------- 1 | // +build !testing 2 | 3 | package main 4 | 5 | func main() { 6 | retErr() 7 | } 8 | 9 | func retErr() error { 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /extypes/suggestion.go: -------------------------------------------------------------------------------- 1 | package extypes 2 | 3 | import ( 4 | "github.com/tehsphinx/astrav" 5 | ) 6 | 7 | //SuggestionFunc defines a function checking a solution for a specific problem 8 | type SuggestionFunc func(pkg *astrav.Package, r *Response) 9 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/exercism/exalysis 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/atotto/clipboard v0.1.1 // indirect 7 | github.com/exercism/go-analyzer v0.2.1 8 | github.com/kevinburke/go-bindata v3.13.0+incompatible 9 | github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e 10 | github.com/pmezard/go-difflib v1.0.0 11 | github.com/stretchr/testify v1.3.0 12 | github.com/tehsphinx/astrav v0.4.1 13 | github.com/tehsphinx/clipboard v0.1.3 14 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b 15 | golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/atotto/clipboard v0.1.1 h1:WSoEbAS70E5gw8FbiqFlp69MGsB6dUb4l+0AGGLiVGw= 2 | github.com/atotto/clipboard v0.1.1/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/exercism/go-analyzer v0.2.1 h1:LKi7rxSd0QzSeyfJJ5ffrexr7NtisW2mSya5+XDWwMw= 7 | github.com/exercism/go-analyzer v0.2.1/go.mod h1:xkbJqmqiHSD3GSiikxiWQ2PW0kaiarQskimJM2omcPM= 8 | github.com/kevinburke/go-bindata v3.13.0+incompatible h1:hThDhUBH4KjTyhfXfOgacEPfFBNjltnzl/xzfLfrPoQ= 9 | github.com/kevinburke/go-bindata v3.13.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= 10 | github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k= 11 | github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 12 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 13 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 14 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 15 | github.com/namsral/flag v1.7.4-pre/go.mod h1:OXldTctbM6SWH1K899kPZcf65KxJiD7MsceFUpB5yDo= 16 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 17 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/shurcooL/httpfs v0.0.0-20181222201310-74dc9339e414/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= 21 | github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= 22 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 23 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 24 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 25 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 26 | github.com/tehsphinx/astpatt v0.2.1 h1:ToNB0mviTE+Y2PY5NnB3HD1xgNKaHU30Gsjokwr0gbE= 27 | github.com/tehsphinx/astpatt v0.2.1/go.mod h1:iZdDB0J0Zhz9sW+Q8tzpR19L9b8fcUoSSaao+6s9Pho= 28 | github.com/tehsphinx/astrav v0.4.0/go.mod h1:zEFhb8ClsLyrt/CT5cGip7dRM7POzPQPUZAHlJkb/3I= 29 | github.com/tehsphinx/astrav v0.4.1 h1:nmWhVgZBRV9ZBG6s5XdPRIs78LghrLEopaF2DZnQHnY= 30 | github.com/tehsphinx/astrav v0.4.1/go.mod h1:zEFhb8ClsLyrt/CT5cGip7dRM7POzPQPUZAHlJkb/3I= 31 | github.com/tehsphinx/clipboard v0.1.3 h1:yvpiEuRmCzuCHscbKKveUjaSGIpsgIayMoUmQxaFr8Y= 32 | github.com/tehsphinx/clipboard v0.1.3/go.mod h1:mHVLI8BgutQPdEYIx/saoBHBqoZIYSu/oTydcL5VfBQ= 33 | github.com/tehsphinx/dbg v0.0.0-20180912080624-6ae3be1fde4a/go.mod h1:0lc75gUc5PIEHuvMfdbFQqjEGKFkP+V7XBa75j6s91c= 34 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 35 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 36 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 37 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 38 | golang.org/x/lint v0.0.0-20181212231659-93c0bb5c8393/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 39 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= 40 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 41 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 42 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 43 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 44 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 45 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 46 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 47 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 48 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 49 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 50 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 51 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 52 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 53 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 54 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 55 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 56 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 57 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 58 | golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346 h1:hzJjkvxUIF3bSt+v8N5tBQNx/605vszZJ+3XsIamzZo= 59 | golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= 60 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 61 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 62 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 63 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 64 | -------------------------------------------------------------------------------- /gtpl/reg.go: -------------------------------------------------------------------------------- 1 | //go:generate go-bindata -ignore=\.go -pkg=gtpl -o=bindata.go ./... 2 | 3 | package gtpl 4 | 5 | // Templates to be used in the response of suggester 6 | var ( 7 | Greeting = NewFormatTemplate("response/greeting.md", MustAsset) 8 | NewcomerGreeting = NewStringTemplate("response/newcomer_greeting.md", MustAsset) 9 | Praise = NewFormatTemplate("response/praise.md", MustAsset) 10 | Todo = NewFormatTemplate("response/todo.md", MustAsset) 11 | Improvement = NewFormatTemplate("response/improvement.md", MustAsset) 12 | Comment = NewFormatTemplate("response/comment.md", MustAsset) 13 | Tip = NewFormatTemplate("response/tip.md", MustAsset) 14 | Questions = NewStringTemplate("response/questions.md", MustAsset) 15 | 16 | Compile = NewStringTemplate("tools/compile.md", MustAsset) 17 | PassTests = NewStringTemplate("tools/pass_tests.md", MustAsset) 18 | RaceCondition = NewStringTemplate("tools/race_condition.md", MustAsset) 19 | NotLinted = NewStringTemplate("tools/not_linted.md", MustAsset) 20 | NotVetted = NewStringTemplate("tools/not_vetted.md", MustAsset) 21 | NotFormatted = NewStringTemplate("tools/not_formatted.md", MustAsset) 22 | 23 | Benchmarking = NewStringTemplate("topic/benchmarking.md", MustAsset) 24 | Regex = NewStringTemplate("topic/regex.md", MustAsset) 25 | Hints = NewStringTemplate("topic/hints.md", MustAsset) 26 | Tips = NewStringTemplateSlice("tips/", MustAsset) 27 | ) 28 | -------------------------------------------------------------------------------- /gtpl/response/comment.md: -------------------------------------------------------------------------------- 1 | 2 | %s on this exercise: -------------------------------------------------------------------------------- /gtpl/response/greeting.md: -------------------------------------------------------------------------------- 1 | Hi %s! -------------------------------------------------------------------------------- /gtpl/response/improvement.md: -------------------------------------------------------------------------------- 1 | 2 | Here %s for further improvement: -------------------------------------------------------------------------------- /gtpl/response/newcomer_greeting.md: -------------------------------------------------------------------------------- 1 | 2 | Welcome to Exercism and welcome to learning Go! Nice to meet you! -------------------------------------------------------------------------------- /gtpl/response/praise.md: -------------------------------------------------------------------------------- 1 | 2 | This looks %s! -------------------------------------------------------------------------------- /gtpl/response/questions.md: -------------------------------------------------------------------------------- 1 | 2 | Feel free to ask if you have questions or want to know more. I'd love to help! -------------------------------------------------------------------------------- /gtpl/response/tip.md: -------------------------------------------------------------------------------- 1 | 2 | Here's something you might find interesting. It's not necessarily related to this exercise, but it will help you develop your general Go knowledge: 3 | -------------------------------------------------------------------------------- /gtpl/response/todo.md: -------------------------------------------------------------------------------- 1 | 2 | Could you have a look at the following %s so I can approve the solution? -------------------------------------------------------------------------------- /gtpl/template.go: -------------------------------------------------------------------------------- 1 | package gtpl 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Template defines a snippet template 9 | type Template interface { 10 | ID() string 11 | TplString() string 12 | } 13 | 14 | // NewTemplate returns a new template 15 | func NewTemplate(id string, text string) StringTemplate { 16 | return StringTemplate{ 17 | id: id, 18 | content: text, 19 | } 20 | } 21 | 22 | // NewStringTemplate returns a new template 23 | func NewStringTemplate(id string, assetFunc func(string) []byte) StringTemplate { 24 | return StringTemplate{ 25 | id: id, 26 | content: string(assetFunc(id)), 27 | } 28 | } 29 | 30 | // NewStringTemplateSlice returns a slice of templates generated from all assets 31 | // matching a given prefix (for example "tips/" to get all tips). 32 | func NewStringTemplateSlice(prefix string, assetFunc func(string) []byte) []StringTemplate { 33 | var tpls []StringTemplate 34 | for _, n := range AssetNames() { 35 | if !strings.HasPrefix(n, prefix) { 36 | continue 37 | } 38 | tpls = append(tpls, NewStringTemplate(n, assetFunc)) 39 | } 40 | return tpls 41 | } 42 | 43 | // StringTemplate is the standard template implementation 44 | type StringTemplate struct { 45 | id string 46 | content string 47 | } 48 | 49 | // ID returns the templates identifier 50 | func (s StringTemplate) ID() string { 51 | return s.id 52 | } 53 | 54 | // TplString returns the templates result string 55 | func (s StringTemplate) TplString() string { 56 | return s.content + "\n" 57 | } 58 | 59 | // NewFormatTemplate returns a new formattable template 60 | func NewFormatTemplate(id string, assetFunc func(string) []byte) FormatTemplate { 61 | return FormatTemplate{ 62 | id: id, 63 | content: string(assetFunc(id)), 64 | } 65 | } 66 | 67 | // FormatTemplate is the standard template implementation 68 | type FormatTemplate struct { 69 | id string 70 | content string 71 | params []interface{} 72 | } 73 | 74 | // ID returns the templates identifier 75 | func (s FormatTemplate) ID() string { 76 | return s.id 77 | } 78 | 79 | // Format adds formatting parameters to the template 80 | func (s FormatTemplate) Format(params ...interface{}) FormatTemplate { 81 | s.params = params 82 | return s 83 | } 84 | 85 | // TplString returns the templates result string 86 | func (s FormatTemplate) TplString() string { 87 | return fmt.Sprintf(s.content, s.params...) + "\n" 88 | } 89 | -------------------------------------------------------------------------------- /gtpl/tips/advanced_tests.md: -------------------------------------------------------------------------------- 1 | - On Exercism, we're always writing code to make tests pass, but what about writing the tests themselves? When you work on your own programs, you should write tests for them, and Hashicorp's Mitchell Hashimoto has given a great talk on [Advanced Testing with Go](https://youtu.be/yszygk1cpEc). You'll learn some powerful techniques not only for writing tests, but for writing testable programs. 2 | -------------------------------------------------------------------------------- /gtpl/tips/append.md: -------------------------------------------------------------------------------- 1 | - The official [Go blog](https://blog.golang.org) is full of fascinating and useful essays on features of Go. [Arrays, slices (and strings): The mechanics of 'append'](https://blog.golang.org/slices) is a particularly good one, explaining how arrays and slices work in Go, and how `append()` is implemented. 2 | -------------------------------------------------------------------------------- /gtpl/tips/awesome_go.md: -------------------------------------------------------------------------------- 1 | - With millions of Go programmers around the world, Go has a vast ecosystem of tools and packages; so much so that it can be difficult to choose the right one for your task. [awesome-go](https://github.com/avelino/awesome-go) is a curated collection of awesome Go frameworks, libraries, and software. Everything is high-quality and up to date, making it easy to find exactly what you need. 2 | -------------------------------------------------------------------------------- /gtpl/tips/building.md: -------------------------------------------------------------------------------- 1 | - Go didn't just spring into existence; it descends from a long history of computer languages and concepts. Steve Francia's talk [Go: building on the shoulders of giants](https://www.youtube.com/watch?v=sX8r6zATHGU) outlines the history of Go, the people behind it, and the design decisions which make it what it is. 2 | -------------------------------------------------------------------------------- /gtpl/tips/channels.md: -------------------------------------------------------------------------------- 1 | - Channels and goroutines are a really neat feature of Go. They let us build surprisingly powerful, simple abstractions without using a special library, or even the standard library: they're built into the language. This talk by Bill Kennedy explores [The Behavior of Channels](https://youtu.be/zDCKZn4-dck) 2 | -------------------------------------------------------------------------------- /gtpl/tips/channels2.md: -------------------------------------------------------------------------------- 1 | - Channels are a simple idea, with complex and powerful implications. What kind of things can channels do, and how should you use them? Bill Kennedy's lucid and engaging essay on [The Behavior of Channels](https://www.ardanlabs.com/blog/2017/10/the-behavior-of-channels.html) explains everything you need to know about channels as a programmer, and shows you how to use them in different scenarios. 2 | -------------------------------------------------------------------------------- /gtpl/tips/community.md: -------------------------------------------------------------------------------- 1 | - Go has a very active user community, and there are lots of ways for you to be part of it! There's the [`golang-nuts` mailing list](https://groups.google.com/d/forum/golang-nuts), a [Go Reddit](https://www.reddit.com/r/golang/), a [Gophers Slack organization](https://invite.slack.golangbridge.org/), a [Go discussion forum](https://forum.golangbridge.org/), and hundreds of [Go meetups](https://go-meetups.appspot.com/). Why not find one in your local area and give it a try? Gophers are very friendly people! 2 | -------------------------------------------------------------------------------- /gtpl/tips/concurrency.md: -------------------------------------------------------------------------------- 1 | - Concurrency is often mentioned as one of the things Go does really well. But what actually is it, how does it relate to parallelism, and how do we use Go to solve problems in a concurrent way? Rob Pike's talk entitled [Concurrency is not Parallelism](https://www.youtube.com/watch?v=oV9rvDllKEg) explains. 2 | -------------------------------------------------------------------------------- /gtpl/tips/concurrencypatterns.md: -------------------------------------------------------------------------------- 1 | - Rob Pike's talk [Go Concurrency Patterns](https://www.youtube.com/watch?v=f6kdp27TYZs) explains one of the most exciting features of Go: concurrency! What does it mean? What can it do? How do you use it? You'll find all the answers in this talk. 2 | -------------------------------------------------------------------------------- /gtpl/tips/concurrencypatterns2.md: -------------------------------------------------------------------------------- 1 | - [Advanced Go Concurrency Patterns](https://www.youtube.com/watch?v=QDDwwePbDtw) is a detailed exploration of Go's concurrency features and how to use them in real programs, by Go team lead Sameer Ajmani. Watch this after you've absorbed Rob Pike's [Go Concurrency Patterns](https://www.youtube.com/watch?v=f6kdp27TYZs). 2 | -------------------------------------------------------------------------------- /gtpl/tips/conduct.md: -------------------------------------------------------------------------------- 1 | - Did you know the Go community has a code of conduct? It outlines a set of values to which Gophers should aspire, of which the first is "Be friendly and welcoming". That's a great start, and you can read the rest here: [Go Community Code of Conduct](https://golang.org/conduct) 2 | 3 | -------------------------------------------------------------------------------- /gtpl/tips/context.md: -------------------------------------------------------------------------------- 1 | - Francesc Campoy's [JustForFunc](https://www.youtube.com/channel/UC_BzFbxG2za3bp5NRRRXJSw) series is a fun and fascinating introduction to Go. In the episode [The Context Package](https://www.youtube.com/watch?v=LSzR0VEraWw), Francesc explains the `context` package from the Go standard library: what it's for, how to use it, and some of the useful things it can do for us. 2 | -------------------------------------------------------------------------------- /gtpl/tips/debugger.md: -------------------------------------------------------------------------------- 1 | - At some point in your Go journey you'll find yourself needing to use a debugger. But what is a debugger and how does it work? Liz Rice's talk [A Debugger From Scratch](https://www.youtube.com/watch?v=ZrpkrMKYvqQ) walks you through the process of writing one in Go! 2 | -------------------------------------------------------------------------------- /gtpl/tips/effective.md: -------------------------------------------------------------------------------- 1 | - We may have mentioned it before, but [Effective Go](https://golang.org/doc/effective_go.html) is really worth reading. It's all about how to write idiomatic Go and make the best use of the various features of the language. When you're first learning, parts of it won't mean much to you yet, but come back and read it again every so often; you'll get something new out of it every time. 2 | -------------------------------------------------------------------------------- /gtpl/tips/errors.md: -------------------------------------------------------------------------------- 1 | - Are you tired of writing `if err != nil` after every operation which can return an error? If so, you might find this blog post enjoyable and enlightening: [Errors are Values](https://blog.golang.org/errors-are-values). It has some great techniques for making your Go code more readable, while still robustly handling errors. 2 | -------------------------------------------------------------------------------- /gtpl/tips/faq.md: -------------------------------------------------------------------------------- 1 | - The [Unofficial Go FAQ](https://go101.org/article/unofficial-faq.html) is a great collection of questions and answers about Go, some of which are not found in the official documentation. It's well worth reading and keeping the link handy! 2 | -------------------------------------------------------------------------------- /gtpl/tips/font.md: -------------------------------------------------------------------------------- 1 | - There's a special font designed for programming, which can make editing Go code a nicer experience. It's called [Fira Code](https://github.com/tonsky/FiraCode), and it has special symbols for some things in Go syntax, like '!=' and '<-'. It's free, and you can use it with any editor or terminal which supports fonts. 2 | -------------------------------------------------------------------------------- /gtpl/tips/go-perfbook.md: -------------------------------------------------------------------------------- 1 | - "In the vast majority of cases, the size and speed of a program is not a concern." But in those rare cases when performance is critical, what techniques can we use to write very efficient Go programs? [Writing and Optimizing Go code](https://github.com/dgryski/go-perfbook/blob/master/performance.md) is a free online book by Damian Gryski with lots of useful tips and tactics. You might like to try some of them yourself. 2 | -------------------------------------------------------------------------------- /gtpl/tips/gophercises.md: -------------------------------------------------------------------------------- 1 | - If you're ready for a more challenging collection of exercises to solve in Go, [Gophercises](https://gophercises.com/) is a great resource. You can choose from a selection of interesting and varied projects, from a simple URL shortener to an AI bot for playing blackjack! 2 | -------------------------------------------------------------------------------- /gtpl/tips/graceful_errors.md: -------------------------------------------------------------------------------- 1 | - [Don't Just Check Errors, Handle Them Gracefully](https://www.youtube.com/watch?v=lsBF58Q-DnY) is a fascinating talk by Dave Cheney about errors in Go, what to do with them, and how to make your errors more useful. 2 | -------------------------------------------------------------------------------- /gtpl/tips/idiomatic.md: -------------------------------------------------------------------------------- 1 | - "Idiomatic" code in Go follows a standard, consistent way of doing things, such as capitalizing acronyms, or naming error variables. [Idiomatic Go](https://dmitri.shuralyov.com/idiomatic-go) is a small collection of tips on idiomatic Go which you may find useful. 2 | -------------------------------------------------------------------------------- /gtpl/tips/interface.md: -------------------------------------------------------------------------------- 1 | - Interfaces are one of the most powerful, useful, and unique features of Go. When you understand interfaces, you understand Go. Francesc Campoy gave a great talk called [Interfaces in Go](https://www.youtube.com/watch?v=PfQFjOwGGks). Enjoy! 2 | -------------------------------------------------------------------------------- /gtpl/tips/magic.md: -------------------------------------------------------------------------------- 1 | - If you're interested in learning some powerful, elegant, and simple techniques of Go programming, watch this great talk by Mat Ryer called [Idiomatic Go Tricks](https://youtu.be/yeetIgNeIkc). Mat says "Go deliberately has no magic built into the language, but magic things happen when it is used correctly." 2 | -------------------------------------------------------------------------------- /gtpl/tips/modules.md: -------------------------------------------------------------------------------- 1 | - You might be aware of "Go modules', but not quite sure what they are or how to use them. Go core team member Russ Cox has written a series of blog posts explaining how modules work, and what problems they solve, which you might find interesting: [Go += Package Versioning](https://research.swtch.com/vgo-intro) 2 | -------------------------------------------------------------------------------- /gtpl/tips/mutexes.md: -------------------------------------------------------------------------------- 1 | - Channels are an incredibly powerful concept in concurrent programming, but they aren't the answer to everything. Sometimes you need to use a mutex, and [Dancing with Go's Mutexes](https://hackernoon.com/dancing-with-go-s-mutexes-92407ae927bf) is a great introduction to how to use mutexes, and when to choose them over channels. 2 | -------------------------------------------------------------------------------- /gtpl/tips/nil.md: -------------------------------------------------------------------------------- 1 | - `nil` is something that puzzles some new Gophers. What does it mean? What's it for? Francesc Campoy's talk [Understanding Nil](https://www.youtube.com/watch?v=ynoY2xz-F8s) explains everything you ever wanted to know about `nil`, and just a little more. 2 | -------------------------------------------------------------------------------- /gtpl/tips/performance.md: -------------------------------------------------------------------------------- 1 | - At Gophercon 2018, Dave Cheney and Francesc Campoy gave a workshop on [Go Performance Tuning](https://github.com/davecheney/gophercon2018-performance-tuning-workshop) which will give you everything you need to diagnose and fix performance problems in your Go code: benchmarking, profiling, execution tracing, the garbage collector, and more. 2 | -------------------------------------------------------------------------------- /gtpl/tips/pointers.md: -------------------------------------------------------------------------------- 1 | - Are you perplexed by pointers? Don't worry, Dave Cheney's blog post [Understand Go pointers in less than 800 words or your money back](https://dave.cheney.net/2017/04/26/understand-go-pointers-in-less-than-800-words-or-your-money-back) is a beautifully concise and helpful explanation. 2 | -------------------------------------------------------------------------------- /gtpl/tips/practical.md: -------------------------------------------------------------------------------- 1 | - [Practical Go: Real world advice for writing maintainable Go programs](https://dave.cheney.net/practical-go/presentations/qcon-china.html) is a transcript of a talk by Dave Cheney about ways to do things in Go. How should you choose good names for variables? When, why, and how should you write comments? How should you organize your packages? Dave has the answers. 2 | -------------------------------------------------------------------------------- /gtpl/tips/profiling.md: -------------------------------------------------------------------------------- 1 | - Go is fast and efficient, but at some point you'll probably encounter a performance bottleneck in your code. How can you find and fix problems like this? [Profiling Go](https://www.integralist.co.uk/posts/profiling-go/) explains some techniques and tools for profiling Go code and figuring out what's going on. 2 | -------------------------------------------------------------------------------- /gtpl/tips/proverbs.md: -------------------------------------------------------------------------------- 1 | - Have you heard of [Go proverbs](https://go-proverbs.github.io/)? These are a collection of sayings which neatly express some important ideas about Go ("Clear is better than clever" is one of my favourites). You can watch Rob Pike explain them in a talk entitled [Go Proverbs](https://youtu.be/PAAkCSZUG1c). 2 | -------------------------------------------------------------------------------- /gtpl/tips/review.md: -------------------------------------------------------------------------------- 1 | - Having other people review your code can be incredibly helpful, and of course one of the great things about Exercism is that you get code reviews from experienced Go programmers. [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) is a great collection of common mistakes and Go style errors which should be picked up in code reviews. It can help you avoid these problems in your own code. 2 | -------------------------------------------------------------------------------- /gtpl/tips/rubyists.md: -------------------------------------------------------------------------------- 1 | - If you're coming to Go from a Ruby background, some things will make sense to you right away and others will be a little more puzzling. Hsing-Hui Hsu's [A Rubyist's (Poignant) Guide to Go](https://speakerdeck.com/somanyhs/go-northwest-2018-a-rubyists-poignant-guide-to-go?slide=13) is a great way to relate what you know from Ruby to what you're learning in Go. 2 | -------------------------------------------------------------------------------- /gtpl/tips/servers.md: -------------------------------------------------------------------------------- 1 | - Go is widely used for writing internet servers, but the internet can be a dangerous place. [So you want to expose Go on the internet](https://blog.gopheracademy.com/advent-2016/exposing-go-on-the-internet/) is an excellent guide to the things you need to think about when writing safe, reliable, and efficient servers in Go. 2 | -------------------------------------------------------------------------------- /gtpl/tips/shades.md: -------------------------------------------------------------------------------- 1 | - [50 Shades of Go](http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html) is a wonderful collection of traps, gotchas, and common mistakes in Go, orgnaised by experience level from beginner upwards. Some of these you may already know (unused variables!), many will be new to you. While you shouldn't necessarily read it from cover to cover, you'll find this a useful list to dip into and refer to from time to time. 2 | -------------------------------------------------------------------------------- /gtpl/tips/simplicity.md: -------------------------------------------------------------------------------- 1 | - Go is often described as a simple language. It's not; it just seems that way. In this talk, Rob Pike explains how Go's simplicity hides a great deal of complexity, and that both the simplicity and complexity are part of the design: [Simplicity is Complicated](https://youtu.be/rFejpH_tAHM) 2 | -------------------------------------------------------------------------------- /gtpl/tips/solid.md: -------------------------------------------------------------------------------- 1 | - Dave Cheney's [SOLID Go Design](https://dave.cheney.net/2016/08/20/solid-go-design) is a thoughtful essay on the principles of good software engineering in general, and how they apply to Go in particular, explaining how to design Go programs that are well engineered, decoupled, reusable, and above all responsive to change. 2 | -------------------------------------------------------------------------------- /gtpl/tips/tdd.md: -------------------------------------------------------------------------------- 1 | - Did you know that when you solve problems on Exercism you're doing "test-driven development" (TDD)? What is TDD anyway, and why does it matter? There's a nice blog post by Pierre Prinetti about the TDD workflow, with a step-by-step example: [Test-Driven Development in Go](https://medium.com/@pierreprinetti/test-driven-development-in-go-baeab5adb468) 2 | -------------------------------------------------------------------------------- /gtpl/tips/tests.md: -------------------------------------------------------------------------------- 1 | - By now you'll be familiar with the idea of tests, since all Exercism exercises use them! But how do you go about writing your own tests? There's a great GitHub repo called [Learn Go With Tests](https://github.com/quii/learn-go-with-tests) which will help you. You'll work through a series of problems which teach you important aspects of Go, and learn a lot about testing in the process! 2 | -------------------------------------------------------------------------------- /gtpl/tips/tooling.md: -------------------------------------------------------------------------------- 1 | - One of the many great things about Go is the modern, powerful tooling that comes with the language. Francesc Campoy's [Go Tooling in Action](https://youtu.be/uBjoTxosSys) is a video guide that introduces the Go tools and how to use them. 2 | -------------------------------------------------------------------------------- /gtpl/tips/tour.md: -------------------------------------------------------------------------------- 1 | - Have you tried the interactive [Tour of Go](https://tour.golang.org/)? It's an introduction to Go which lets you edit and run code right in your browser. It's also available in twenty different languages! It's a great way to learn about every aspect of Go, step by step. 2 | -------------------------------------------------------------------------------- /gtpl/tools/compile.md: -------------------------------------------------------------------------------- 1 | - Unfortunately, this solution does not compile correctly. Did you run the tests? Maybe you accidentally submitted an incomplete solution? If you're having trouble getting your program to compile, you can always ask me for help! 2 | -------------------------------------------------------------------------------- /gtpl/tools/not_formatted.md: -------------------------------------------------------------------------------- 1 | - Code formatting in Go is very important to the community. Take a look at `gofmt` and run it on your code. Your editor or IDE should be able to do this for you automatically: 2 | 3 | - Visual Studio Code has [great Go support](https://code.visualstudio.com/docs/languages/go) 4 | 5 | - Vim also has a [fantastic Go extension](https://github.com/fatih/vim-go) 6 | 7 | - [Jetbrains Goland](https://www.jetbrains.com/go/) is an entire IDE for Go 8 | -------------------------------------------------------------------------------- /gtpl/tools/not_linted.md: -------------------------------------------------------------------------------- 1 | - There's a great tool called [`golint`](https://github.com/golang/lint) which will examine your code for common problems and style issues. Try running `golint` on your code, e.g.: `golint two_fer.go`; it will make some useful suggestions for you. 2 | 3 | It's a good idea to always check your code with `golint` (or configure your editor to do it for you). When you're writing Go software for production, many build pipelines will automatically fail if the code doesn't pass `golint` (and `gofmt`). You can avoid this by linting code yourself prior to submitting it. 4 | -------------------------------------------------------------------------------- /gtpl/tools/not_vetted.md: -------------------------------------------------------------------------------- 1 | - Did you know Go can automatically check your code for some common problems? It's a great idea to run [`go vet`](https://golang.org/cmd/vet/) every so often. Try it on your code now: for example, `go vet two_fer.go`; it will make some useful suggestions for you. Some editors (for example, Visual Studio Code) can run `go vet` for you automatically every time you save; check the documentation for your editor to see how to do this. 2 | -------------------------------------------------------------------------------- /gtpl/tools/pass_tests.md: -------------------------------------------------------------------------------- 1 | - Unfortunately, this solution does not pass the tests. Did you run the tests? Or did you accidentally submit an incomplete solution? 2 | If you are not sure how to solve this exercise, feel free to ask me for hints. 3 | 4 | If the tests passed when you submitted this solution, you may have what's known as a 'flickering test': one that passes sometimes, and fails other times. Try running the tests two or three times in a row and see if one fails. You may have a race condition or some other problem. 5 | -------------------------------------------------------------------------------- /gtpl/tools/race_condition.md: -------------------------------------------------------------------------------- 1 | - Unfortunately, this solution has race conditions when running the tests. Did you run the tests with the `-race` parameter? 2 | If you are not sure how to solve the race conditions, feel free to ask me for hints. 3 | 4 | `go test -race` 5 | -------------------------------------------------------------------------------- /gtpl/topic/benchmarking.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | ***Benchmarking*** 4 | 5 | I have made some suggestions that involve benchmarking (testing the performance and efficiency of your code). If you're already familiar with benchmarking you can skip this. 6 | 7 | Benchmarks already exist for this exercise. You can execute them with: 8 | ``` 9 | go test -v --bench . --benchmem 10 | ``` 11 | 12 | This will first run the tests, and then the benchmarks, producing something like this: 13 | 14 | ``` 15 | goos: darwin 16 | goarch: amd64 17 | pkg: path/to/exercise 18 | BenchmarkName-8 100000 1859 ns/op 57 B/op 7 allocs/op 19 | PASS 20 | ok path/to/exercise 2.042s 21 | ``` 22 | 23 | For each benchmark there will be a line starting with the `BenchmarkName` followed by the number of cores (`8`) available. The next number (`100000`) indicates how many times the benchmark was run. Go will automatically work out how many times to run the benchmark to get a statistically useful result. 24 | 25 | The next three numbers indicate: 26 | 27 | 1. the time it took to execute the benchmark, in `ns/op` (nanoseconds per operation) 28 | 29 | 1. the memory usage, in `B/op` (bytes of memory allocated per operation) 30 | 31 | 1. the number of memory allocations, in `alloc/op` 32 | 33 | For all these numbers, lower is better. 34 | 35 | The last number `2.042s` indicates the `total execution time` for all tests and benchmarks. Ignore this. This is *not* significant for measuring speed! The benchmarking tool in Go executes faster benchmarks more often to produce more reliable results, possibly increasing the total execution time. 36 | 37 | Dave Cheney has written a good blog post on Go benchmarking which you may find interesting: [How to write benchmarks in Go](https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go) 38 | 39 | --- -------------------------------------------------------------------------------- /gtpl/topic/hints.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | ***Welcome to the Go track!*** 4 | 5 | As this is likely your first Go exercise on Exercism, I thought I'd share a few tips that may be helpful. 6 | 7 | 1. **Run the tests** one last time before submitting. Usually running just `go test` in the exercise directory is all you need to do. If you can't get all the tests to pass, feel free to submit the solution anyway and ask for help. Mentors will be happy to give you hints. 8 | 9 | 2. Ensure your **code is formatted** with `gofmt`. Most editors that support Go can be configured to do this automatically. If you're coming to Go from other languages, you may not be used to the idea that there's one, and only one, accepted way to format Go code, and it's the `gofmt` way. At first this may seem overly restrictive, but there are great advantages to standard formatting, not least that it avoids a lot of futile arguments about which is the best way to format Go code. 10 | 11 | 3. Make sure your **code passes `golint`**. `golint` is a tool that analyses your code for common errors and problems, and also enforces things like documentation comments for your functions and identifiers. Again, your editor can usually lint your code automatically, and it's a good idea to set this up. Run `go get -u golang.org/x/lint/golint` to install `golint`, and check your code with it before submitting. 12 | 13 | 4. **The goal of exercism is to teach fluency**. Among other things this includes guiding you to a **simple, readable and idiomatic** solution. While these are good software engineering principles in general, they're especially important in Go, where the whole language is designed for maximum simplicity and clarity. 14 | 15 | When you have something which works, try to simplify it as much as possible by eliminating all redundant or duplicated code, and rewriting everything in its simplest form. If there are parts of the code which seem awkward or complicated to you, trust your instincts and refactor the code until you feel good about it. In Go, 'clear is better than clever'. Keep that in mind and you won't go far wrong. 16 | 17 | 5. **Resist the temptation to optimize everything for performance**. Go programs are fast; astonishingly fast, if you're used to interpreted languages. Go also has great performance analysis tools: the benchmarker, the profiler, and so on. These are all great fun to play with, and as engineers we love trying to find the absolutely optimal way to do something. Feel free to do so but always consider simplicity and readability first. If you find a more efficient method which doesn't compromise readability, that is perfect! 18 | 19 | 6. This may be one of the few occasions in your career when you can get personal, one-to-one help and advice from an experienced software engineer. **Make the most of it, and above all, have fun!** 20 | 21 | -------------------------------------------------------------------------------- /gtpl/topic/pprof-allocations.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | ***pprof: Allocations*** 4 | 5 | The basic tool for profiling Go programs is `pprof`. There are three different version of the `pprof` tool: 6 | 7 | - [runtime version](https://golang.org/pkg/runtime/pprof/) 8 | - [net/http version](https://golang.org/pkg/net/http/pprof/) 9 | - [command-line version](https://blog.golang.org/profiling-go-programs) 10 | 11 | The command-line version of `pprof` is part of the Go toolchain, and you can run it with `go tool pprof` or just `pprof` in the terminal. We will use this now to look deeper into the allocations of our code. 12 | 13 | First we need to create a memory profile. We can do this while running benchmarks: 14 | 15 | ``` 16 | go test -v --bench . --benchmem --memprofile mem.out --memprofilerate=1 17 | ``` 18 | 19 | We now have a `mem.out` file containing a memory profile. We can investigate it with the `pprof` command from the terminal. 20 | 21 | ``` 22 | pprof mem.out 23 | ``` 24 | 25 | Now we are in the `pprof` console. Here we can type specific `pprof` commands. Start with `help` to show a list of all available commands. We can then see what our top memory consumers are with `top5`, or `top5 -cum` to sort them by the cumulative column. 26 | 27 | We can also check which code allocates memory, and how much. The command `list` takes a regular expression to search in your code. Try giving it a function name to investigate: 28 | 29 | ``` 30 | list functionName 31 | ``` 32 | 33 | This will show all code where `functionName` was found that involves memory allocation. Before the lines that allocate memory you will see how much memory was allocated. 34 | 35 | Remember that we created the profile by running a benchmark, so don't be alarmed to see several megabytes being allocated at once; the code was executed a few thousand times. 36 | 37 | Finally, it's easy to generate a nice visualization of the top memory consumers. The `pdf` command will create a diagram and save it as a PDF file. Alternatively, the `web` command will open your browser and show the diagram there. 38 | 39 | --- -------------------------------------------------------------------------------- /gtpl/topic/regex.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | ***Regular Expressions*** 4 | 5 | According to the [documentation of the regexp package](https://golang.org/pkg/regexp/), Go's implementation is guaranteed to run in _linear time_; that is, it doesn't get slower as the size of the input gets bigger. Because of this, regular expressions in Go have two separate stages: `compilation` and `usage`. 6 | 7 | **Compilation** 8 | 9 | When compiling a `static` regex it is advisable to use `regexp.MustCompile()`, and move the compilation to `package` level. If the regex is invalid, the program will panic on startup, making it very obvious to the developer that there's a problem. 10 | 11 | ```go 12 | var ( 13 | someRegex = regexp.MustCompile(`someregex`) 14 | ) 15 | 16 | func SomeFunc(s string) { 17 | result := someRegex.FindAllString(s, -1) 18 | //... 19 | } 20 | ``` 21 | 22 | If the regex is not static, it still makes sense to compile it only when necessary. In this case `rexexp.Compile()` is better, as it will return an error for an invalid regex, rather than panicking. 23 | 24 | ```go 25 | func SomeFunc(s, param string) error { 26 | regexStr := fmt.Sprintf("regexWithParam%s", param) 27 | someRegex, err := regexp.Compile(regexStr) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | result := someRegex.FindAllString(s, -1) 33 | ... 34 | } 35 | ``` 36 | 37 | --- -------------------------------------------------------------------------------- /sample.md: -------------------------------------------------------------------------------- 1 | Hi GoLover123! 2 | 3 | Welcome to Exercism and welcome to learning Go! Nice to meet you! 4 | 5 | This looks good! 6 | 7 | Here are some thoughts for further improvement: 8 | - I see you referenced the names `Alice` and `Bob` directly in your code. This is OK, but it would be better if the `ShareWith` function worked with any name supplied to it. See if you can figure out how to change your code to make this work. 9 | 10 | - The comment describing the package `twofer` isn't quite in the standard format. The section on comments below may help you fix this. 11 | 12 | - Currently you are repeating most of the output string. This is OK, but in general, Go programmers try to avoid duplicating code where they can avoid it. Can you work out how to restructure your code so that you don't repeat yourself? 13 | 14 | 15 | --- 16 | Go has [great guidelines](https://golang.org/doc/effective_go.html) about how to write comments. There's a standard format you can use for documentation comments, which can then be turned into beautiful documentation automatically by the `godoc` tool. 17 | 18 | The package comment should start with the word `Package` followed by the package name, like this: 19 | 20 | ```go 21 | // Package cook provides handy conversion methods for units typically used in recipes. 22 | package cook 23 | ``` 24 | 25 | For exported functions, methods, constants, and package variables, the comment should start with the name, and continue with a verb phrase: 26 | 27 | ```go 28 | // TbsToMl converts tablespoons to milliliters. 29 | func TbsToMl(tbs int) int { 30 | // ... 31 | } 32 | ``` 33 | 34 | The [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments#comment-sentences) wiki further specifies that doc comments should be full sentences, ending with a period. 35 | 36 | In addition to optimizing for readability, following the recommendations improves how your code looks in [Go's documentation tools](http://whipperstacker.com/2015/09/30/go-documentation-godoc-godoc-godoc-org-and-go-doc/) such as [godoc.org](http://godoc.org), `godoc`, and `go doc`. 37 | 38 | Take a moment to read the section on doc comments in the official style guide, [Effective Go](https://golang.org/doc/effective_go.html) to see the reasoning behind these choices. 39 | 40 | --- 41 | 42 | Feel free to ask if you have questions or want to know more. I'd love to help! 43 | -------------------------------------------------------------------------------- /suggestion.go: -------------------------------------------------------------------------------- 1 | package exalysis 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "net/http" 8 | "path/filepath" 9 | "regexp" 10 | "time" 11 | 12 | "github.com/exercism/exalysis/exam" 13 | "github.com/exercism/exalysis/extypes" 14 | "github.com/exercism/exalysis/gtpl" 15 | "github.com/exercism/exalysis/track/diffsquares" 16 | "github.com/exercism/exalysis/track/isogram" 17 | "github.com/exercism/exalysis/track/luhn" 18 | "github.com/exercism/exalysis/track/paraletterfreq" 19 | "github.com/exercism/exalysis/track/raindrops" 20 | "github.com/exercism/exalysis/track/scrabble" 21 | "github.com/exercism/go-analyzer/analyzer" 22 | "github.com/exercism/go-analyzer/suggester/sugg" 23 | "github.com/logrusorgru/aurora" 24 | "github.com/tehsphinx/astrav" 25 | ) 26 | 27 | var exercisePkgs = map[string]extypes.SuggestionFunc{ 28 | "twofer": nil, 29 | "hamming": nil, 30 | "raindrops": raindrops.Suggest, 31 | "scrabble": scrabble.Suggest, 32 | "isogram": isogram.Suggest, 33 | "diffsquares": diffsquares.Suggest, 34 | "luhn": luhn.Suggest, 35 | "letter": paraletterfreq.Suggest, 36 | } 37 | 38 | // GetSuggestions selects the package suggestion routine and returns the suggestions 39 | func GetSuggestions(codePath string) (string, string) { 40 | var r = extypes.NewResponse() 41 | folder := astrav.NewFolder(http.Dir(codePath), "") 42 | _, err := folder.ParseFolder() 43 | if err != nil { 44 | addGreeting(r, "", "there") 45 | r.AppendTodoTpl(gtpl.Compile) 46 | return r.GetAnswerString(), rating(r, nil, "") 47 | } 48 | 49 | // TODO: clean this up after all exercises are switched to the analyzer 50 | 51 | var pkgName = folder.Pkg.Name() 52 | analyze := pkgName == "twofer" || pkgName == "hamming" 53 | 54 | pkg, suggFunc := getExercisePkg(folder) 55 | var msg string 56 | if pkg == nil && !analyze { 57 | msg = "I don't have any specific knowledge on the %q exercise, but I'll still run my general code checks on it:" 58 | } else { 59 | msg = "I know the %q exercise, so I'll try to make some specific suggestions about it, as well as the general code checks:" 60 | } 61 | fmt.Println(aurora.Sprintf(aurora.Gray(msg), aurora.Green(pkgName))) 62 | 63 | addGreeting(r, pkgName, getStudentName(codePath)) 64 | examRes, err := exam.All(folder, r, pkgName, analyze) 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | 69 | if analyze { 70 | getSuggsAnalyzer(r, codePath) 71 | } else { 72 | getSuggestions(pkg, r, suggFunc) 73 | } 74 | addTip(r, pkgName) 75 | return r.GetAnswerString(), rating(r, examRes, pkgName) 76 | } 77 | 78 | func getSuggsAnalyzer(r *extypes.Response, codePath string) { 79 | res := analyzer.Analyze("", codePath) 80 | for _, err := range res.Errors { 81 | log.Printf("ERROR on %s:\n", codePath) 82 | log.Println(err) 83 | } 84 | 85 | for _, cmt := range res.Comments { 86 | switch cmt.Category() { 87 | case sugg.CtgTodo: 88 | r.AppendTodo(cmt) 89 | case sugg.CtgImprovement: 90 | r.AppendImprovement(cmt) 91 | case sugg.CtgThought: 92 | r.AppendComment(cmt) 93 | case sugg.CtgBlock: 94 | r.AppendBlock(cmt) 95 | default: 96 | log.Println("unknown comment category encountered") 97 | r.AppendImprovement(cmt) 98 | } 99 | } 100 | } 101 | 102 | func getSuggestions(pkg *astrav.Package, r *extypes.Response, suggFunc extypes.SuggestionFunc) { 103 | if suggFunc != nil { 104 | suggFunc(pkg, r) 105 | } 106 | } 107 | 108 | var student = regexp.MustCompile("users/([^/]*)/go/") 109 | 110 | func getStudentName(codePath string) string { 111 | absPath, err := filepath.Abs(codePath) 112 | if err != nil { 113 | return "" 114 | } 115 | 116 | submatch := student.FindStringSubmatch(absPath) 117 | if 1 < len(submatch) { 118 | return submatch[1] 119 | } 120 | return "" 121 | } 122 | 123 | func getExercisePkg(folder *astrav.Folder) (*astrav.Package, extypes.SuggestionFunc) { 124 | for name, pkg := range folder.Pkgs { 125 | if sg, ok := exercisePkgs[name]; ok { 126 | return pkg, sg 127 | } 128 | } 129 | return nil, nil 130 | } 131 | 132 | func addGreeting(r *extypes.Response, pkg, student string) { 133 | r.SetGreeting(gtpl.Greeting.Format(student)) 134 | if pkg == "twofer" { 135 | r.AppendGreeting(gtpl.NewcomerGreeting) 136 | } 137 | } 138 | 139 | func addTip(r *extypes.Response, pkgName string) { 140 | if pkgName == "twofer" { 141 | // For the first exercise, give some useful hints about Exercism and the Go track. 142 | r.AppendOutro(gtpl.Hints) 143 | return 144 | } 145 | // For other exercises, give a randomly-selected tip. 146 | if r.LenSuggestions() < 3 { 147 | rand.Seed(time.Now().UnixNano()) 148 | t := rand.Intn(len(gtpl.Tips)) 149 | r.AppendTip(gtpl.Tips[t]) 150 | } 151 | } 152 | 153 | func rating(r *extypes.Response, examRes *exam.Result, pkgName string) string { 154 | rating := aurora.Gray("Rating Suggestion\n").String() 155 | rating += fmt.Sprintf("Todos:\t\t%d\n", aurora.Red(r.LenTodos())) 156 | rating += fmt.Sprintf("Suggestions:\t%d\n", aurora.Brown(r.LenImprovements())) 157 | rating += fmt.Sprintf("Comments:\t%d\n", aurora.Green(r.LenComments())) 158 | 159 | approve := approval(r, examRes, pkgName) 160 | rating += fmt.Sprintf("Suggestion:\t%s\n", approve) 161 | return rating 162 | } 163 | 164 | func approval(r *extypes.Response, examRes *exam.Result, pkgName string) aurora.Value { 165 | var gofmt, golint bool 166 | if examRes != nil { 167 | gofmt = examRes.GoFmt 168 | golint = examRes.GoLint 169 | } 170 | 171 | // don't be so strict on the first exercises 172 | switch pkgName { 173 | case "twofer": 174 | gofmt = true 175 | golint = true 176 | case "hamming": 177 | golint = true 178 | } 179 | 180 | if !gofmt { 181 | return aurora.Red("NO APPROVAL") 182 | } 183 | if !golint { 184 | return aurora.Red("NO APPROVAL") 185 | } 186 | if r.LenTodos() != 0 { 187 | return aurora.Red("NO APPROVAL") 188 | } 189 | 190 | l := r.LenImprovements() 191 | switch { 192 | case 5 < l: 193 | return aurora.Red("NO APPROVAL") 194 | case 2 < l: 195 | return aurora.Magenta("MAYBE APPROVE") 196 | case 1 < l: 197 | return aurora.Brown("LIKELY APPROVE") 198 | } 199 | 200 | return aurora.Green("APPROVE") 201 | } 202 | -------------------------------------------------------------------------------- /testhelper/helper.go: -------------------------------------------------------------------------------- 1 | package testhelper 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | 7 | "github.com/tehsphinx/astrav" 8 | ) 9 | 10 | // LoadFolder loads an example from given example folder 11 | func LoadFolder(exampleFolder string) (*astrav.Folder, map[string]*astrav.Package, error) { 12 | folder := astrav.NewFolder(http.Dir(exampleFolder), "") 13 | pkgs, err := folder.ParseFolder() 14 | return folder, pkgs, err 15 | } 16 | 17 | // LoadExample loads an example from given example folder and package name 18 | func LoadExample(exampleFolder string, pkgName string) (*astrav.Folder, *astrav.Package, error) { 19 | folder, pkgs, err := LoadFolder(exampleFolder) 20 | if err != nil { 21 | return nil, nil, err 22 | } 23 | 24 | // TODO: get pkgName automatically without a parameter 25 | pkg, ok := pkgs[pkgName] 26 | if !ok { 27 | return nil, nil, errors.New("given package name not found in given folder") 28 | } 29 | 30 | return folder, pkg, nil 31 | } 32 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | package exalysis 4 | 5 | import ( 6 | // import so it is vendored and available for go generate 7 | _ "github.com/kevinburke/go-bindata" 8 | ) 9 | -------------------------------------------------------------------------------- /track/diffsquares/diffsquares.go: -------------------------------------------------------------------------------- 1 | package diffsquares 2 | 3 | import ( 4 | "github.com/exercism/exalysis/extypes" 5 | "github.com/exercism/exalysis/track/diffsquares/tpl" 6 | "github.com/tehsphinx/astrav" 7 | ) 8 | 9 | //Suggest builds suggestions for the exercise solution 10 | func Suggest(pkg *astrav.Package, r *extypes.Response) { 11 | for _, tf := range exFuncs { 12 | tf(pkg, r) 13 | } 14 | } 15 | 16 | var exFuncs = []extypes.SuggestionFunc{ 17 | examLoops, 18 | examMathPow, 19 | examCalcRangeCondition, 20 | examDry, 21 | examBasicFloat, 22 | } 23 | 24 | func examBasicFloat(pkg *astrav.Package, r *extypes.Response) { 25 | nodes := pkg.FindByName("float64") 26 | for _, node := range nodes { 27 | if !node.IsNodeType(astrav.NodeTypeCallExpr) { 28 | continue 29 | } 30 | if len(node.Children()) != 2 { 31 | continue 32 | } 33 | 34 | child := node.ChildByNodeType(astrav.NodeTypeBasicLit) 35 | if child == nil { 36 | continue 37 | } 38 | if child.IsValueType("float64") { 39 | r.AppendImprovementTpl(tpl.BasicFloat64) 40 | } 41 | } 42 | } 43 | 44 | func examDry(pkg *astrav.Package, r *extypes.Response) { 45 | nodes := pkg.FindByName("Difference") 46 | for _, node := range nodes { 47 | if !node.IsNodeType(astrav.NodeTypeFuncDecl) { 48 | continue 49 | } 50 | 51 | nssq := node.FindFirstByName("SumOfSquares") 52 | if nssq == nil { 53 | r.AppendImprovementTpl(tpl.Dry) 54 | } 55 | 56 | nsqs := node.FindFirstByName("SquareOfSum") 57 | if nsqs == nil { 58 | r.AppendImprovementTpl(tpl.Dry) 59 | } 60 | } 61 | } 62 | 63 | func examMathPow(pkg *astrav.Package, r *extypes.Response) { 64 | node := pkg.FindFirstByName("Pow") 65 | if node != nil { 66 | r.AppendImprovementTpl(tpl.MathPow) 67 | } 68 | } 69 | 70 | func examLoops(pkg *astrav.Package, r *extypes.Response) { 71 | funcs := pkg.FindByNodeType(astrav.NodeTypeFuncDecl) 72 | for _, f := range funcs { 73 | nodes := f.FindNodeTypeInCallTree(astrav.NodeTypeForStmt) 74 | 75 | switch f.(*astrav.FuncDecl).Name.Name { 76 | case "SquareOfSum": 77 | if len(nodes) != 0 { 78 | r.AppendImprovementTpl(tpl.SquareSumLoop) 79 | } 80 | case "SumOfSquares": 81 | if len(nodes) != 0 { 82 | r.AppendImprovementTpl(tpl.SumSquareLoop) 83 | } 84 | } 85 | } 86 | } 87 | 88 | func examCalcRangeCondition(pkg *astrav.Package, r *extypes.Response) { 89 | nodes := pkg.FindByNodeType(astrav.NodeTypeForStmt) 90 | for _, node := range nodes { 91 | cond := node.(*astrav.ForStmt).Cond() 92 | if cond == nil { 93 | continue 94 | } 95 | binExpr := cond.FindByNodeType(astrav.NodeTypeBinaryExpr) 96 | if len(binExpr) != 0 { 97 | r.AppendImprovementTpl(tpl.CalcRangeCondition.Format(binExpr[0].GetSourceString())) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /track/diffsquares/diffsquares_test.go: -------------------------------------------------------------------------------- 1 | package diffsquares 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/exercism/exalysis/extypes" 8 | "github.com/exercism/exalysis/gtpl" 9 | "github.com/exercism/exalysis/testhelper" 10 | "github.com/exercism/exalysis/track/diffsquares/tpl" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var suggestTests = []struct { 15 | path string 16 | suggestion gtpl.Template 17 | expected bool 18 | }{ 19 | {path: "./solutions/1", suggestion: tpl.SquareSumLoop, expected: true}, 20 | {path: "./solutions/1", suggestion: tpl.SumSquareLoop, expected: true}, 21 | {path: "./solutions/1", suggestion: tpl.CalcRangeCondition, expected: true}, 22 | {path: "./solutions/2", suggestion: tpl.SquareSumLoop, expected: true}, 23 | {path: "./solutions/2", suggestion: tpl.SumSquareLoop, expected: true}, 24 | {path: "./solutions/2", suggestion: tpl.CalcRangeCondition, expected: false}, 25 | {path: "./solutions/3", suggestion: tpl.MathPow, expected: true}, 26 | {path: "./solutions/4", suggestion: tpl.SumSquareLoop, expected: true}, 27 | {path: "./solutions/4", suggestion: tpl.SquareSumLoop, expected: true}, 28 | {path: "./solutions/4", suggestion: tpl.MathPow, expected: true}, 29 | {path: "./solutions/5", suggestion: tpl.Dry, expected: true}, 30 | {path: "./solutions/6", suggestion: tpl.SquareSumLoop, expected: true}, 31 | {path: "./solutions/6", suggestion: tpl.SumSquareLoop, expected: true}, 32 | {path: "./solutions/6", suggestion: tpl.MathPow, expected: true}, 33 | {path: "./solutions/6", suggestion: tpl.BasicFloat64, expected: true}, 34 | } 35 | 36 | func Test_Suggest(t *testing.T) { 37 | for _, test := range suggestTests { 38 | _, pkg, err := testhelper.LoadExample(test.path, "diffsquares") 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | r := extypes.NewResponse() 44 | Suggest(pkg, r) 45 | 46 | assert.Equal(t, test.expected, r.HasSuggestion(test.suggestion), 47 | fmt.Sprintf("test failed: %+v", test)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /track/diffsquares/solutions/1/solution.go: -------------------------------------------------------------------------------- 1 | // Package diffsquares provides functions for computing aggregate values on slices 2 | package diffsquares 3 | 4 | // SquareOfSum iteratively computes the square of sum of numbers from 1 to n 5 | func SquareOfSum(n int) (res int) { 6 | res = 0 7 | for i := 1; i < n+1; i++ { 8 | res += i 9 | } 10 | return res * res 11 | } 12 | 13 | // SumOfSquares iteratively computes the sum of squares of numbers from 1 to n 14 | func SumOfSquares(n int) (res int) { 15 | res = 0 16 | for i := 1; i < n+1; i++ { 17 | res += i * i 18 | } 19 | return 20 | } 21 | 22 | // Difference computes the difference between the square of sums and the sum of squares 23 | func Difference(n int) int{ 24 | return SquareOfSum(n) - SumOfSquares(n) 25 | } 26 | -------------------------------------------------------------------------------- /track/diffsquares/solutions/2/solution.go: -------------------------------------------------------------------------------- 1 | package diffsquares 2 | 3 | //SquareOfSum returns the Square of sum of the natural numbers from 1 to input 4 | func SquareOfSum(input int) int { 5 | out := 0 6 | for i := 1; i <= input; i++ { 7 | out += i 8 | } 9 | return out * out 10 | } 11 | 12 | //SquareOfSum returns the sum of the squares of natural numbers from 1 to input 13 | func SumOfSquares(input int) int { 14 | var out int 15 | for i := 1; i <= input; i++ { 16 | out += i * i 17 | } 18 | return out 19 | } 20 | 21 | //Difference returns the difference between sum of squares and Square of sum of input 22 | func Difference(input int) int { 23 | return SquareOfSum(input) - SumOfSquares(input) 24 | } 25 | -------------------------------------------------------------------------------- /track/diffsquares/solutions/3/solution.go: -------------------------------------------------------------------------------- 1 | package diffsquares 2 | 3 | import "math" 4 | 5 | // Difference between the SquareOfSum and SumOfSquares 6 | func Difference(n int) int { 7 | return SquareOfSum(n) - SumOfSquares(n) 8 | } 9 | 10 | // SquareOfSum is the square of the summation of the first N numbers 11 | func SquareOfSum(n int) int { 12 | sumOfFirstN := (n * (n + 1)) / 2 13 | return int(math.Pow(float64(sumOfFirstN), 2)) 14 | } 15 | 16 | // SumOfSquares is the summation of each of the first N numbers squared 17 | func SumOfSquares(n int) int { 18 | return ((n * (n + 1)) * (2*n + 1)) / 6 19 | } 20 | -------------------------------------------------------------------------------- /track/diffsquares/solutions/4/solution.go: -------------------------------------------------------------------------------- 1 | package diffsquares 2 | 3 | import "math" 4 | 5 | // SumOfSquares takes in an int and returns an 6 | // integer number equal to the Sum of all the Squares 7 | // upto that number 8 | func SumOfSquares(num int) int { 9 | var sum float64 10 | for i := 1; i <= num; i++ { 11 | sum += math.Pow(float64(i), 2) 12 | } 13 | return int(sum) 14 | } 15 | 16 | // SquareOfSum takes in an integer number and returns the 17 | // square of the sum of all numbers upto the provided no. 18 | func SquareOfSum(num int) int { 19 | var sum int 20 | for i := 1; i <= num; i++ { 21 | sum += i 22 | } 23 | return int(math.Pow(float64(sum), 2)) 24 | } 25 | 26 | // Difference = SumOfSquares(num) - SquareOfSum(num) 27 | func Difference(num int) int { 28 | return SquareOfSum(num) - SumOfSquares(num) 29 | } 30 | -------------------------------------------------------------------------------- /track/diffsquares/solutions/5/solution.go: -------------------------------------------------------------------------------- 1 | package diffsquares 2 | 3 | // Difference finds the difference between the square of the sum and the sum of the squares of the first N natural 4 | // numbers 5 | func Difference(n int) int { 6 | var square = 0 7 | var squareOfSum = 0 8 | for i := 1; i <= n; i++ { 9 | squareOfSum += i 10 | square += i * i 11 | } 12 | squareOfSum = squareOfSum * squareOfSum 13 | return squareOfSum - square 14 | } 15 | 16 | // SumOfSquares calculates the sum of square of the given n natural numbers 17 | func SumOfSquares(n int) int { 18 | var square = 0 19 | for i := 1; i <= n; i++ { 20 | square += i * i 21 | } 22 | return square 23 | } 24 | 25 | // SquareOfSum calculates the square of the sum of the first n natural numbers 26 | func SquareOfSum(n int) int { 27 | var squareOfTheSum = 0 28 | for i := 1; i <= n; i++ { 29 | squareOfTheSum += i 30 | } 31 | return squareOfTheSum * squareOfTheSum 32 | } 33 | -------------------------------------------------------------------------------- /track/diffsquares/solutions/6/solution.go: -------------------------------------------------------------------------------- 1 | package diffsquares 2 | 3 | import "math" 4 | 5 | //SquareOfSum counts second power of sum of first n natural numbers (1 + 2 + ... + n)² 6 | func SquareOfSum(number int) int { 7 | numberSum := sum(number, false) 8 | return int(math.Pow(float64(numberSum), float64(2))) 9 | } 10 | 11 | //SumOfSquares counts sum of second power of first n natural numbers (1² + 2² + ... + n²) 12 | func SumOfSquares(number int) int { 13 | powSum := sum(number, true) 14 | return powSum 15 | } 16 | 17 | //Difference counts difference between SquareOfSum and SumOfSquares 18 | func Difference(number int) int { 19 | return SquareOfSum(number) - SumOfSquares(number) 20 | } 21 | 22 | func sum(number int, usePow bool) int { 23 | result := 0 24 | for i := 1; i <= number; i++ { 25 | if usePow { 26 | result += int(math.Pow(float64(i), float64(2))) 27 | } else { 28 | result += i 29 | } 30 | } 31 | return result 32 | } 33 | -------------------------------------------------------------------------------- /track/diffsquares/tpl/basic-float64.md: -------------------------------------------------------------------------------- 1 | - To create a floating point value you can use the literal `2.0`, instead of a type conversion `float64(2)`. If it is clear to Go that you are trying to create a float then `2` is enough. When does Go know you want a float? For example, if you use it as a parameter in a function call that expects a `float` or if you assign it to a variable of type `float`. 2 | -------------------------------------------------------------------------------- /track/diffsquares/tpl/calc-range-condition.md: -------------------------------------------------------------------------------- 1 | - Here's one way to improve the speed of the `range` loop: the condition needs to be checked on every loop, and it shouldn't contain calculations that can be avoided. In this case `%s` is calculated every time. 2 | -------------------------------------------------------------------------------- /track/diffsquares/tpl/dry.md: -------------------------------------------------------------------------------- 1 | - You could simplify your `Difference` function by having it call `SumOfSquares` and `SquareOfSums`, instead of repeating that code. 2 | -------------------------------------------------------------------------------- /track/diffsquares/tpl/math-pow.md: -------------------------------------------------------------------------------- 1 | - If you just want to square a number, you can use the expression `n * n` rather than calling `math.Pow`. 2 | -------------------------------------------------------------------------------- /track/diffsquares/tpl/reg.go: -------------------------------------------------------------------------------- 1 | //go:generate go-bindata -ignore=\.go -pkg=tpl -o=bindata.go ./... 2 | 3 | package tpl 4 | 5 | import "github.com/exercism/exalysis/gtpl" 6 | 7 | //Templates to be used in the response of suggester 8 | var ( 9 | SquareSumLoop = gtpl.NewStringTemplate("square-sum-loop.md", MustAsset) 10 | SumSquareLoop = gtpl.NewStringTemplate("sum-square-loop.md", MustAsset) 11 | CalcRangeCondition = gtpl.NewFormatTemplate("calc-range-condition.md", MustAsset) 12 | MathPow = gtpl.NewFormatTemplate("math-pow.md", MustAsset) 13 | Dry = gtpl.NewFormatTemplate("dry.md", MustAsset) 14 | BasicFloat64 = gtpl.NewFormatTemplate("basic-float64.md", MustAsset) 15 | ) 16 | -------------------------------------------------------------------------------- /track/diffsquares/tpl/square-sum-loop.md: -------------------------------------------------------------------------------- 1 | - A loop approach to calculating `SquareOfSum` works, but there's a short-cut. The great mathematician Carl Friedrich Gauss faced the same problem as you when he was at school in the 1700s: to find the sum of the numbers from 1 to 100. The question was assigned as busywork by the teacher, but Gauss found the answer rather quickly by discovering a pattern. Can you find it too? (Unlike Gauss, you can use the Internet to search for answers!) 2 | -------------------------------------------------------------------------------- /track/diffsquares/tpl/sum-square-loop.md: -------------------------------------------------------------------------------- 1 | - Good programmers are both smart and lazy. Can you find a smart, lazy way of computing `SumOfSquares` without a loop? (Good programmers also know how to find the right algorithm for a problem!) 2 | -------------------------------------------------------------------------------- /track/isogram/isogram.go: -------------------------------------------------------------------------------- 1 | package isogram 2 | 3 | import ( 4 | "go/token" 5 | "strings" 6 | 7 | "github.com/exercism/exalysis/extypes" 8 | "github.com/exercism/exalysis/gtpl" 9 | "github.com/exercism/exalysis/track/isogram/tpl" 10 | "github.com/tehsphinx/astrav" 11 | ) 12 | 13 | // Suggest builds suggestions for the exercise solution 14 | func Suggest(pkg *astrav.Package, r *extypes.Response) { 15 | addSpeedComment = getAddSpeedComment() 16 | 17 | for _, tf := range exFuncs { 18 | tf(pkg, r) 19 | } 20 | } 21 | 22 | var exFuncs = []extypes.SuggestionFunc{ 23 | examRegexCompileInFunc, 24 | examToLowerUpper("strings.ToLower", "ToLower"), 25 | examToLowerUpper("strings.ToUpper", "ToUpper"), 26 | examJustReturn, 27 | examNonExistingMapValue, 28 | examUniversalIsLetter, 29 | examIfContinueIsLetter, 30 | examZeroValueAssign, 31 | examTwoLoops, 32 | } 33 | 34 | func examTwoLoops(pkg *astrav.Package, r *extypes.Response) { 35 | loops := pkg.FindByNodeType(astrav.NodeTypeRangeStmt) 36 | loops = append(loops, pkg.FindByNodeType(astrav.NodeTypeForStmt)...) 37 | 38 | if 1 < len(loops) { 39 | r.AppendImprovementTpl(tpl.TwoLoops) 40 | } 41 | } 42 | 43 | func examZeroValueAssign(pkg *astrav.Package, r *extypes.Response) { 44 | nodes := pkg.FindByNodeType(astrav.NodeTypeAssignStmt) 45 | for _, node := range nodes { 46 | ident := node.ChildByNodeType(astrav.NodeTypeIdent) 47 | b := node.ChildByNodeType(astrav.NodeTypeBasicLit) 48 | if b == nil || ident == nil { 49 | continue 50 | } 51 | bLit := b.(*astrav.BasicLit) 52 | 53 | switch ident.ValueType().String() { 54 | case "string": 55 | if bLit.Value == `""` { 56 | r.AppendImprovementTpl(tpl.ZeroValueAssign) 57 | } 58 | case "bool": 59 | if bLit.Value == `false` { 60 | r.AppendImprovementTpl(tpl.ZeroValueAssign) 61 | } 62 | case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64": 63 | if bLit.Value == `0` { 64 | r.AppendImprovementTpl(tpl.ZeroValueAssign) 65 | } 66 | } 67 | } 68 | } 69 | 70 | func examIfContinueIsLetter(pkg *astrav.Package, r *extypes.Response) { 71 | node := pkg.FindFirstByName("unicode.IsLetter") 72 | if node == nil { 73 | return 74 | } 75 | 76 | ifNode := node.NextParentByType(astrav.NodeTypeIfStmt) 77 | if ifNode == nil { 78 | return 79 | } 80 | contNodes := ifNode.FindByToken(token.CONTINUE) 81 | retNodes := ifNode.FindByNodeType(astrav.NodeTypeReturnStmt) 82 | ifNodes := ifNode.FindByNodeType(astrav.NodeTypeIfStmt) 83 | 84 | if len(contNodes)+len(retNodes) == 0 { 85 | r.AppendImprovementTpl(tpl.IfContinue) 86 | } else if len(contNodes) == 0 && len(ifNodes) != 0 { 87 | r.AppendImprovementTpl(tpl.IfContinue) 88 | } 89 | } 90 | 91 | func examUniversalIsLetter(pkg *astrav.Package, r *extypes.Response) { 92 | nodes := pkg.FindByNodeType(astrav.NodeTypeBasicLit) 93 | for _, node := range nodes { 94 | bLit := node.(*astrav.BasicLit) 95 | if strings.Contains(bLit.Value, "-") { 96 | r.AppendImprovementTpl(tpl.UniversalIsLetter) 97 | } 98 | if strings.Contains(bLit.Value, "a") && bLit.IsValueType("rune") { 99 | r.AppendImprovementTpl(tpl.UniversalIsLetter) 100 | } 101 | } 102 | } 103 | 104 | func examNonExistingMapValue(pkg *astrav.Package, r *extypes.Response) { 105 | nodes := pkg.FindByNodeType(astrav.NodeTypeIndexExpr) 106 | for _, node := range nodes { 107 | parent := node.Parent() 108 | if !parent.IsNodeType(astrav.NodeTypeAssignStmt) { 109 | continue 110 | } 111 | 112 | assign := parent.ChildrenByNodeType(astrav.NodeTypeIdent) 113 | if len(assign) < 2 { 114 | continue 115 | } 116 | 117 | if assign[0].IsValueType("bool") { 118 | r.AppendCommentTpl(tpl.NonExistingMapValue) 119 | } 120 | } 121 | } 122 | 123 | func examJustReturn(pkg *astrav.Package, r *extypes.Response) { 124 | nodes := pkg.FindByToken(token.BREAK) 125 | if len(nodes) == 0 { 126 | return 127 | } 128 | 129 | for _, node := range nodes { 130 | ifStmt := node.NextParentByType(astrav.NodeTypeIfStmt) 131 | if ifStmt == nil { 132 | continue 133 | } 134 | boolVar := ifStmt.FindByValueType("bool") 135 | if boolVar == nil { 136 | continue 137 | } 138 | 139 | r.AppendImprovementTpl(tpl.JustReturn) 140 | break 141 | } 142 | } 143 | 144 | func examRegexCompileInFunc(pkg *astrav.Package, r *extypes.Response) { 145 | main := pkg.FindFirstByName("IsIsogram") 146 | regComp := pkg.FindFirstByName("regexp.Compile") 147 | if regComp != nil && main.Contains(regComp) { 148 | r.AppendTodoTpl(tpl.RegexInFunc) 149 | r.AppendTodoTpl(tpl.MustCompile) 150 | } 151 | if regComp != nil { 152 | r.AppendTodoTpl(tpl.IsLetter) 153 | } 154 | 155 | regComp = pkg.FindFirstByName("regexp.MustCompile") 156 | if regComp != nil && main.Contains(regComp) { 157 | r.AppendTodoTpl(tpl.RegexInFunc) 158 | } 159 | if regComp != nil { 160 | r.AppendTodoTpl(tpl.IsLetter) 161 | } 162 | } 163 | 164 | func examToLowerUpper(fullName, fnName string) extypes.SuggestionFunc { 165 | return func(pkg *astrav.Package, r *extypes.Response) { 166 | fns := pkg.FindByName(fullName) 167 | for _, fn := range fns { 168 | if _, ok := fn.(*astrav.SelectorExpr); !ok { 169 | continue 170 | } 171 | addSpeedComment(r) 172 | 173 | if fn.NextParentByType(astrav.NodeTypeBlockStmt).IsContainedByType(astrav.NodeTypeRangeStmt) { 174 | r.AppendImprovementTpl(tpl.UnicodeLoop.Format(fnName)) 175 | } else { 176 | r.AppendImprovementTpl(tpl.Unicode.Format(fnName)) 177 | } 178 | } 179 | } 180 | } 181 | 182 | var addSpeedComment func(r *extypes.Response) 183 | 184 | func getAddSpeedComment() func(r *extypes.Response) { 185 | var speedCommentAdded bool 186 | return func(r *extypes.Response) { 187 | if speedCommentAdded { 188 | return 189 | } 190 | speedCommentAdded = true 191 | r.AppendOutro(gtpl.Benchmarking) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /track/isogram/isogram_test.go: -------------------------------------------------------------------------------- 1 | package isogram 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/exercism/exalysis/extypes" 8 | "github.com/exercism/exalysis/gtpl" 9 | "github.com/exercism/exalysis/testhelper" 10 | "github.com/exercism/exalysis/track/isogram/tpl" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var suggestTests = []struct { 15 | path string 16 | suggestion gtpl.Template 17 | expected bool 18 | }{ 19 | {path: "./solutions/1", suggestion: tpl.Unicode, expected: true}, 20 | {path: "./solutions/1", suggestion: tpl.IfContinue, expected: false}, 21 | {path: "./solutions/2", suggestion: tpl.Unicode, expected: true}, 22 | {path: "./solutions/2", suggestion: tpl.RegexInFunc, expected: true}, 23 | {path: "./solutions/2", suggestion: tpl.MustCompile, expected: true}, 24 | {path: "./solutions/2", suggestion: tpl.JustReturn, expected: true}, 25 | {path: "./solutions/2", suggestion: tpl.NonExistingMapValue, expected: true}, 26 | {path: "./solutions/2", suggestion: tpl.IsLetter, expected: true}, 27 | {path: "./solutions/2", suggestion: tpl.IfContinue, expected: false}, 28 | {path: "./solutions/3", suggestion: tpl.IsLetter, expected: true}, 29 | {path: "./solutions/3", suggestion: tpl.IfContinue, expected: false}, 30 | {path: "./solutions/4", suggestion: tpl.IfContinue, expected: true}, 31 | {path: "./solutions/5", suggestion: tpl.NonExistingMapValue, expected: false}, 32 | {path: "./solutions/5", suggestion: tpl.UniversalIsLetter, expected: true}, 33 | {path: "./solutions/5", suggestion: tpl.IfContinue, expected: false}, 34 | {path: "./solutions/6", suggestion: tpl.UniversalIsLetter, expected: true}, 35 | {path: "./solutions/6", suggestion: tpl.ZeroValueAssign, expected: true}, 36 | {path: "./solutions/6", suggestion: tpl.IfContinue, expected: false}, 37 | {path: "./solutions/7", suggestion: tpl.UniversalIsLetter, expected: true}, 38 | {path: "./solutions/8", suggestion: tpl.TwoLoops, expected: true}, 39 | } 40 | 41 | func Test_Suggest(t *testing.T) { 42 | for _, test := range suggestTests { 43 | _, pkg, err := testhelper.LoadExample(test.path, "isogram") 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | r := extypes.NewResponse() 49 | Suggest(pkg, r) 50 | 51 | assert.Equal(t, test.expected, r.HasSuggestion(test.suggestion), 52 | fmt.Sprintf("test failed: %+v", test)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /track/isogram/solutions/1/solution.go: -------------------------------------------------------------------------------- 1 | package isogram 2 | 3 | import ( 4 | "strings" 5 | "unicode" 6 | ) 7 | 8 | // IsIsogram checks whether a string is an isogram or not 9 | func IsIsogram(word string) bool { 10 | letters := make(map[rune]bool, len(word)) 11 | 12 | for _, r := range strings.ToLower(word) { 13 | if unicode.IsLetter(r) && letters[r] { 14 | return false 15 | } 16 | letters[r] = true 17 | } 18 | return true 19 | } 20 | -------------------------------------------------------------------------------- /track/isogram/solutions/2/solution.go: -------------------------------------------------------------------------------- 1 | package isogram 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | func IsIsogram(s string) bool { 9 | 10 | alphaRX, _ := regexp.Compile("[^a-zA-Z]") 11 | lower := alphaRX.ReplaceAllString(strings.ToLower(s), "") 12 | chars := make(map[rune]bool) 13 | isIso := true 14 | 15 | for _, c := range lower { 16 | _, ok := chars[c] 17 | 18 | if ok { 19 | isIso = false 20 | break 21 | } 22 | 23 | chars[c] = true 24 | } 25 | 26 | return isIso 27 | 28 | } 29 | -------------------------------------------------------------------------------- /track/isogram/solutions/3/solution.go: -------------------------------------------------------------------------------- 1 | //Package isogram implements IsIsogram to check input string is an isogram 2 | package isogram 3 | 4 | import ( 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | var reg = regexp.MustCompile(`[\s-]`) 10 | 11 | //IsIsogram function tell us if the input string is an isogram 12 | func IsIsogram(input string) bool { 13 | input = strings.ToLower(reg.ReplaceAllString(input, "")) 14 | charmap := make(map[rune]bool) 15 | for _, c := range input { 16 | _, present := charmap[c] 17 | if present { 18 | return false 19 | } 20 | charmap[c] = true 21 | } 22 | return true 23 | } 24 | -------------------------------------------------------------------------------- /track/isogram/solutions/4/solution.go: -------------------------------------------------------------------------------- 1 | // Package isogram tests for whether or not a word is an isogram 2 | package isogram 3 | 4 | import ( 5 | "unicode" 6 | ) 7 | 8 | // IsIsogram returns true iff input is an isogram (word without repeating letters). 9 | func IsIsogram(candidate string) bool { 10 | var used = make([]bool, 26) 11 | for _, c := range candidate { 12 | if unicode.IsLetter(c) { 13 | cUpper := unicode.ToUpper(c) - 'A' 14 | if used[cUpper] { 15 | return false 16 | } 17 | used[cUpper] = true 18 | } 19 | } 20 | return true 21 | } 22 | -------------------------------------------------------------------------------- /track/isogram/solutions/5/solution.go: -------------------------------------------------------------------------------- 1 | package isogram 2 | 3 | import "unicode" 4 | 5 | // IsIsogram returns true if the given word contains at most one instance of 6 | // any letter. Spaces and hyphens are ignored. 7 | func IsIsogram(word string) bool { 8 | seen := make(map[rune]struct{}) 9 | for _, letter := range word { 10 | if letter == '-' || letter == ' ' { 11 | continue 12 | } 13 | letter = unicode.ToLower(letter) 14 | if _, found := seen[letter]; found { 15 | return false 16 | } 17 | seen[letter] = struct{}{} 18 | } 19 | return true 20 | } 21 | -------------------------------------------------------------------------------- /track/isogram/solutions/6/solution.go: -------------------------------------------------------------------------------- 1 | package isogram 2 | 3 | import ( 4 | "strings" 5 | "unicode" 6 | ) 7 | 8 | // Score returns scrabble score for given word 9 | func IsIsogram(word string) bool { 10 | foundLetters := "" 11 | punctuation := " -" 12 | for _, letter := range word { 13 | 14 | letter := string(unicode.ToLower(letter)) 15 | 16 | if strings.Index(punctuation, letter) == -1 && strings.Index(foundLetters, letter) > -1 { 17 | return false 18 | } 19 | foundLetters += letter 20 | 21 | } 22 | 23 | return true 24 | } 25 | -------------------------------------------------------------------------------- /track/isogram/solutions/7/solution.go: -------------------------------------------------------------------------------- 1 | package isogram 2 | 3 | import "unicode" 4 | 5 | /* 6 | IsIsogram module checks whether string has repeated characters 7 | */ 8 | func IsIsogram(input string) bool { 9 | isoMap := map[rune]bool{} 10 | 11 | for _, ch := range input { 12 | ch = unicode.ToLower(ch) 13 | if ch >= 'a' && ch <= 'z' { 14 | if _, ok := isoMap[ch]; ok { 15 | return false 16 | } 17 | isoMap[ch] = true 18 | } 19 | } 20 | return true 21 | } 22 | -------------------------------------------------------------------------------- /track/isogram/solutions/8/solution.go: -------------------------------------------------------------------------------- 1 | package isogram 2 | 3 | import "unicode" 4 | 5 | // IsIsogram checks if string given as input is an isogram (contains only unique characters). 6 | func IsIsogram(input string) bool { 7 | for index, letter := range input { 8 | if letter == ' ' || letter == '-' { 9 | continue 10 | } 11 | for _, anotherLetter := range input[index+1:] { 12 | if unicode.ToLower(letter) == unicode.ToLower(anotherLetter) { 13 | return false 14 | } 15 | } 16 | } 17 | return true 18 | } 19 | -------------------------------------------------------------------------------- /track/isogram/tpl/if-continue.md: -------------------------------------------------------------------------------- 1 | - You could make the function more readable by inverting the condition of the `if` checking for letters. You can use the `continue` keyword to jump ahead to the next loop iteration. The rest of the loop can then be outdented. A Go proverb says ["The happy path is left-aligned"](https://medium.com/@matryer/line-of-sight-in-code-186dd7cdea88), meaning the normal flow of control follows the left-hand margin, and only exceptional or error cases are indented. 2 | -------------------------------------------------------------------------------- /track/isogram/tpl/isletter.md: -------------------------------------------------------------------------------- 1 | - I see you're using a regular expression to check whether a character is a letter. Instead, try using `unicode.IsLetter`—it's simpler and faster! 2 | -------------------------------------------------------------------------------- /track/isogram/tpl/just-return.md: -------------------------------------------------------------------------------- 1 | - You are breaking out of the `for` loop only to return a variable you just set. You could `return` immediately instead, and eliminate the variable. 2 | -------------------------------------------------------------------------------- /track/isogram/tpl/mustcompile.md: -------------------------------------------------------------------------------- 1 | - To compile a static regex, it's a good idea to use `MustCompile()`. This will cause the program to panic if the regex is invalid, so you'll know about it right away. 2 | -------------------------------------------------------------------------------- /track/isogram/tpl/nonexisting-map-value.md: -------------------------------------------------------------------------------- 1 | - Did you know that a map in Go never panics if you ask it for a nonexistent key? Instead it returns the [zero value](https://golang.org/ref/spec#The_zero_value) for that type. For example, if you have a `map[X]bool` it will return `false` if the entry does not exist. So you can query the `map` without using the `value, ok` syntax: `if somemap[somekey] {...}`. 2 | -------------------------------------------------------------------------------- /track/isogram/tpl/reg.go: -------------------------------------------------------------------------------- 1 | //go:generate go-bindata -ignore=\.go -pkg=tpl -o=bindata.go ./... 2 | 3 | package tpl 4 | 5 | import "github.com/exercism/exalysis/gtpl" 6 | 7 | // Templates to be used in the response of suggester 8 | var ( 9 | Unicode = gtpl.NewFormatTemplate("unicode.md", MustAsset) 10 | UnicodeLoop = gtpl.NewFormatTemplate("unicode-loop.md", MustAsset) 11 | RegexInFunc = gtpl.NewStringTemplate("regex-in-func.md", MustAsset) 12 | MustCompile = gtpl.NewStringTemplate("mustcompile.md", MustAsset) 13 | JustReturn = gtpl.NewStringTemplate("just-return.md", MustAsset) 14 | NonExistingMapValue = gtpl.NewStringTemplate("nonexisting-map-value.md", MustAsset) 15 | IsLetter = gtpl.NewStringTemplate("isletter.md", MustAsset) 16 | IfContinue = gtpl.NewStringTemplate("if-continue.md", MustAsset) 17 | UniversalIsLetter = gtpl.NewStringTemplate("universal-isletter.md", MustAsset) 18 | ZeroValueAssign = gtpl.NewStringTemplate("zero-value-assign.md", MustAsset) 19 | TwoLoops = gtpl.NewStringTemplate("two-loops.md", MustAsset) 20 | ) 21 | -------------------------------------------------------------------------------- /track/isogram/tpl/regex-in-func.md: -------------------------------------------------------------------------------- 1 | - Regular expressions in Go work a little differently from other languages. For better performance, a regex is compiled before it's used. Right now, you're compiling the regex inside your function, but it only needs to be compiled once. If the regex is static you can move the compilation to package level. -------------------------------------------------------------------------------- /track/isogram/tpl/two-loops.md: -------------------------------------------------------------------------------- 1 | - You are comparing every letter in the string with every other letter. That is expensive. Can you think of a way to do this with just one loop? 2 | -------------------------------------------------------------------------------- /track/isogram/tpl/unicode-loop.md: -------------------------------------------------------------------------------- 1 | - Have a look at using `unicode.%[1]s` to replace `strings.%[1]s` in the `for` loop to increase speed! 2 | -------------------------------------------------------------------------------- /track/isogram/tpl/unicode.md: -------------------------------------------------------------------------------- 1 | - You could look at using `unicode.%[1]s` inside the `for` loop instead of `strings.%[1]s` before the loop, to increase speed. 2 | 3 | (Sometimes it makes the code a little easier to read if you call `strings.%[1]s` beforehand, though. Use your best judgment, and remember readability beats performance!) 4 | -------------------------------------------------------------------------------- /track/isogram/tpl/universal-isletter.md: -------------------------------------------------------------------------------- 1 | - Have a look at `unicode.IsLetter` for a flexible, general way to identify non-letter characters. 2 | -------------------------------------------------------------------------------- /track/isogram/tpl/zero-value-assign.md: -------------------------------------------------------------------------------- 1 | - If you're declaring a variable which can be initialized with its [zero value](https://golang.org/ref/spec#The_zero_value), then `var s string` is more idiomatic than an assignment: `s := ""`. 2 | -------------------------------------------------------------------------------- /track/luhn/luhn.go: -------------------------------------------------------------------------------- 1 | package luhn 2 | 3 | import ( 4 | "github.com/exercism/exalysis/extypes" 5 | "github.com/exercism/exalysis/track/luhn/tpl" 6 | "github.com/tehsphinx/astrav" 7 | ) 8 | 9 | //Suggest builds suggestions for the exercise solution 10 | func Suggest(pkg *astrav.Package, r *extypes.Response) { 11 | for _, tf := range exFuncs { 12 | tf(pkg, r) 13 | } 14 | } 15 | 16 | var exFuncs = []extypes.SuggestionFunc{ 17 | examRegexCompileInFunc, 18 | examOneLoop, 19 | } 20 | 21 | func examOneLoop(pkg *astrav.Package, r *extypes.Response) { 22 | loops := len(pkg.FindByNodeType(astrav.NodeTypeForStmt)) 23 | loops += len(pkg.FindByNodeType(astrav.NodeTypeRangeStmt)) 24 | if 1 < loops { 25 | r.AppendBlockTpl(tpl.OneLoop) 26 | } 27 | } 28 | 29 | func examRegexCompileInFunc(pkg *astrav.Package, r *extypes.Response) { 30 | main := pkg.FindFirstByName("Valid") 31 | regComp := pkg.FindFirstByName("Compile") 32 | if regComp != nil && main.Contains(regComp) { 33 | r.AppendTodoTpl(tpl.RegexInFunc) 34 | r.AppendTodoTpl(tpl.MustCompile) 35 | } 36 | if regComp != nil { 37 | r.AppendBlockTpl(tpl.RegexToFast) 38 | } 39 | 40 | regComp = pkg.FindFirstByName("MustCompile") 41 | if regComp != nil && main.Contains(regComp) { 42 | r.AppendTodoTpl(tpl.RegexInFunc) 43 | } 44 | if regComp != nil { 45 | r.AppendBlockTpl(tpl.RegexToFast) 46 | } 47 | regComp = pkg.FindFirstByName("MatchString") 48 | if regComp != nil { 49 | r.AppendBlockTpl(tpl.RegexToFast) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /track/luhn/luhn_test.go: -------------------------------------------------------------------------------- 1 | package luhn 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/exercism/exalysis/extypes" 8 | "github.com/exercism/exalysis/gtpl" 9 | "github.com/exercism/exalysis/testhelper" 10 | "github.com/exercism/exalysis/track/luhn/tpl" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var suggestTests = []struct { 15 | path string 16 | suggestion gtpl.Template 17 | expected bool 18 | }{ 19 | {path: "./solutions/1", suggestion: tpl.RegexInFunc, expected: true}, 20 | {path: "./solutions/1", suggestion: tpl.RegexToFast, expected: true}, 21 | {path: "./solutions/2", suggestion: tpl.RegexInFunc, expected: false}, 22 | {path: "./solutions/2", suggestion: tpl.RegexToFast, expected: true}, 23 | {path: "./solutions/3", suggestion: tpl.RegexToFast, expected: false}, 24 | {path: "./solutions/4", suggestion: tpl.OneLoop, expected: true}, 25 | {path: "./solutions/4", suggestion: tpl.RegexToFast, expected: false}, 26 | {path: "./solutions/5", suggestion: tpl.RegexInFunc, expected: false}, 27 | {path: "./solutions/5", suggestion: tpl.RegexToFast, expected: true}, 28 | {path: "./solutions/6", suggestion: tpl.OneLoop, expected: false}, 29 | {path: "./solutions/6", suggestion: tpl.RegexToFast, expected: false}, 30 | {path: "./solutions/7", suggestion: tpl.RegexToFast, expected: true}, 31 | } 32 | 33 | func Test_Suggest(t *testing.T) { 34 | for _, test := range suggestTests { 35 | _, pkg, err := testhelper.LoadExample(test.path, "luhn") 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | r := extypes.NewResponse() 41 | Suggest(pkg, r) 42 | 43 | assert.Equal(t, test.expected, r.HasSuggestion(test.suggestion), 44 | fmt.Sprintf("test failed: %+v", test)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /track/luhn/solutions/1/solution.go: -------------------------------------------------------------------------------- 1 | //Package luhn contains a single function Valid 2 | //and a couple of internal functions used for intermediate 3 | //calculations 4 | package luhn 5 | 6 | import ( 7 | "regexp" 8 | ) 9 | 10 | //Valid will take a string as input and if the string is 11 | //of length greater than 2 and contains only digits and spaces 12 | //it will return a boolean value based on its validity per the Luhn 13 | //formula. Any inputs that do not meet the requirements are false 14 | func Valid(str string) bool { 15 | s := regexp.MustCompile("\\s").ReplaceAllString(str, "") 16 | validRegex := regexp.MustCompile("^[0-9]{2,}$") 17 | isInputValid := validRegex.MatchString(s) 18 | total := 0 19 | 20 | if !isInputValid { 21 | return false 22 | } 23 | 24 | lengthOffset := (len(s) - 1) % 2 25 | for i, r := range s { 26 | total += calculateValue(i+lengthOffset, r) 27 | } 28 | return evenlyDivisbleByTen(total) 29 | } 30 | 31 | //calculateValue will return double the int value from 32 | //the input rune for every second digit. If the value is greater than 9, 9 is subtracted. Just the int value is returned otherwise 33 | func calculateValue(i int, r rune) int { 34 | val := int(r) - 48 35 | if i%2 == 0 { 36 | return val 37 | } 38 | val *= 2 39 | if val > 9 { 40 | return val - 9 41 | } 42 | return val 43 | } 44 | 45 | //evenlyDivisbleByTen returns boolean value of true 46 | //if the int passed is evenly divisible by 10. 47 | func evenlyDivisbleByTen(val int) bool { 48 | return val%10 == 0 49 | } 50 | -------------------------------------------------------------------------------- /track/luhn/solutions/2/solution.go: -------------------------------------------------------------------------------- 1 | //Package luhn contains a single function Valid 2 | //and a couple of internal functions used for intermediate 3 | //calculations 4 | package luhn 5 | 6 | import ( 7 | "regexp" 8 | ) 9 | 10 | var containsWhiteSpace = regexp.MustCompile("\\s") 11 | var digitsOnly = regexp.MustCompile("^[0-9]{2,}$") 12 | 13 | //Valid will take a string as input and if the string is 14 | //of length greater than 2 and contains only digits and spaces 15 | //it will return a boolean value based on its validity per the Luhn 16 | //formula. Any inputs that do not meet the requirements are false 17 | func Valid(str string) bool { 18 | s := containsWhiteSpace.ReplaceAllString(str, "") 19 | isInputValid := digitsOnly.MatchString(s) 20 | 21 | if !isInputValid { 22 | return false 23 | } 24 | 25 | total := 0 26 | lengthOffset := (len(s) - 1) % 2 27 | for i, r := range s { 28 | total += calculateValue(i+lengthOffset, r) 29 | } 30 | return evenlyDivisbleByTen(total) 31 | } 32 | 33 | //calculateValue will return double the int value from 34 | //the input rune for every second digit. If the value is greater than 9, 9 is subtracted. Just the int value is returned otherwise 35 | func calculateValue(i int, r rune) int { 36 | val := int(r) - 48 37 | if i%2 == 0 { 38 | return val 39 | } 40 | val *= 2 41 | if val > 9 { 42 | return val - 9 43 | } 44 | return val 45 | } 46 | 47 | //evenlyDivisbleByTen returns boolean value of true 48 | //if the int passed is evenly divisible by 10. 49 | func evenlyDivisbleByTen(val int) bool { 50 | return val%10 == 0 51 | } 52 | -------------------------------------------------------------------------------- /track/luhn/solutions/3/solution.go: -------------------------------------------------------------------------------- 1 | //Package luhn contains a single function Valid 2 | //and a couple of internal functions used for intermediate 3 | //calculations 4 | package luhn 5 | 6 | //Valid will take a string as input and if the string is 7 | //of length greater than 2 and contains only digits and spaces 8 | //it will return a boolean value based on its validity per the Luhn 9 | //formula. Any inputs that do not meet the requirements are false 10 | func Valid(s string) bool { 11 | counter, total := 0, 0 12 | for i := len(s) - 1; i >= 0; i-- { 13 | r := rune(s[i]) 14 | 15 | if r == ' ' { 16 | continue 17 | } else if r < '0' || r > '9' { 18 | return false 19 | } else { 20 | total += calculateValue(counter, r) 21 | counter++ 22 | } 23 | } 24 | 25 | if counter < 2 { 26 | return false 27 | } 28 | return isEvenlyDivisibleByTen(total) 29 | } 30 | 31 | func calculateValue(i int, r rune) int { 32 | val := int(r) - 48 33 | if i%2 == 0 { 34 | return val 35 | } 36 | val *= 2 37 | if val > 9 { 38 | return val - 9 39 | } 40 | return val 41 | } 42 | 43 | func isEvenlyDivisibleByTen(val int) bool { 44 | return val%10 == 0 45 | } 46 | -------------------------------------------------------------------------------- /track/luhn/solutions/4/solution.go: -------------------------------------------------------------------------------- 1 | // Package luhn calculates Luhn checksum 2 | package luhn 3 | 4 | import ( 5 | "strconv" 6 | "strings" 7 | "unicode" 8 | ) 9 | 10 | //Valid check the string is valid number 11 | func Valid(N string) bool { 12 | var a string 13 | for _, v := range N { 14 | if unicode.IsLetter(v) || strings.Contains(N, "-") || unicode.IsSymbol(v) { 15 | return false 16 | } 17 | if !unicode.IsNumber(v) { 18 | continue 19 | } 20 | a += string(v) 21 | } 22 | for i := len(a) - 1; 0 < i; i -= 2 { 23 | b, _ := strconv.Atoi(a[i-1 : i]) 24 | b *= 2 25 | if b > 9 { 26 | b -= 9 27 | } 28 | a = a[:i-1] + strconv.Itoa(b) + a[i:] 29 | } 30 | var sum int 31 | for _, v := range a { 32 | c, _ := strconv.Atoi(string(v)) 33 | sum += c 34 | } 35 | valid := true 36 | if len(a) < 2 || sum%10 != 0 { 37 | valid = false 38 | } 39 | return valid 40 | } 41 | -------------------------------------------------------------------------------- /track/luhn/solutions/5/solution.go: -------------------------------------------------------------------------------- 1 | package luhn 2 | 3 | import ( 4 | "regexp" 5 | "strconv" 6 | ) 7 | 8 | func odd(num int) bool { 9 | return num%2 == 0 10 | } 11 | 12 | func doubleMod9(num int) int { 13 | if num == 9 { 14 | return 9 15 | } 16 | return (num + num) % 9 17 | } 18 | 19 | var whitespace = regexp.MustCompile("\\s") 20 | var notDigits = regexp.MustCompile("\\D") 21 | 22 | // Valid checks if a number is a valid per the Luhn firmula 23 | func Valid(number string) bool { 24 | number = whitespace.ReplaceAllString(number, "") 25 | length := len(number) 26 | if notDigits.MatchString(number) || length <= 1 { 27 | return false 28 | } 29 | 30 | sum := 0 31 | for i, ch := range number { 32 | // At this point the string only contains numbers 33 | // Errors can be ignored 34 | digit, _ := strconv.Atoi(string(ch)) 35 | if odd(length - i) { 36 | sum += doubleMod9(digit) 37 | } else { 38 | sum += digit 39 | } 40 | } 41 | return sum%10 == 0 42 | } 43 | -------------------------------------------------------------------------------- /track/luhn/solutions/6/solution.go: -------------------------------------------------------------------------------- 1 | // Package luhn provides calculation of the Luhn checksum. 2 | package luhn 3 | 4 | import "unicode" 5 | 6 | // Valid checks whether a string contains a number valid per the Luhn algorithm. 7 | func Valid(input string) bool { 8 | sum := 0 9 | digitIndex := 0 10 | for n := len(input) - 1; n >= 0; n-- { 11 | char := rune(input[n]) 12 | if char == ' ' { 13 | continue 14 | } 15 | if !unicode.IsDigit(char) { 16 | return false 17 | } 18 | value := int(char - '0') 19 | if (digitIndex)%2 == 0 { 20 | sum += value 21 | } else { 22 | value2 := value * 2 23 | if value2 > 9 { 24 | sum += value2 - 9 25 | } else { 26 | sum += value2 27 | } 28 | } 29 | digitIndex++ 30 | } 31 | if digitIndex <= 1 { 32 | return false 33 | } 34 | return sum%10 == 0 35 | } 36 | -------------------------------------------------------------------------------- /track/luhn/solutions/7/solution.go: -------------------------------------------------------------------------------- 1 | // Package luhn checks the validity of creditcard numbers 2 | // and canadian social security numbers by applying 3 | // the "Luhn" algorithm. 4 | package luhn 5 | 6 | import ( 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | // Valid validates a given string by the means stated in the 12 | // package description. 13 | func Valid(s string) bool { 14 | // Remove spaces. 15 | o := strings.Replace(s, " ", "", -1) 16 | // Check if the remaining string is at lest 2 chars long. 17 | if len(o) <= 1 { 18 | return false 19 | } 20 | // Check for non-digits. 21 | if ok, _ := regexp.MatchString(`((?s)[\D[:punct:]])+`, o); ok { 22 | return false 23 | } 24 | 25 | // Algorithm violently torn off of 26 | // http://rosettacode.org/wiki/Luhn_test_of_credit_card_numbers#Go 27 | t := []int{0, 2, 4, 6, 8, 1, 3, 5, 7, 9} 28 | odd := len(o) & 1 29 | var sum int 30 | for k, v := range o { 31 | if k&1 == odd { 32 | sum += t[v-'0'] 33 | } else { 34 | sum += int(v - '0') 35 | } 36 | } 37 | 38 | return sum%10 == 0 39 | 40 | } 41 | -------------------------------------------------------------------------------- /track/luhn/tpl/mustcompile.md: -------------------------------------------------------------------------------- 1 | - To compile a static regex, it's a good idea to use `MustCompile()`. This will cause the program to panic if the regex is invalid, so you'll know about it right away. 2 | -------------------------------------------------------------------------------- /track/luhn/tpl/one-loop.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | Your solution with multiple loops is readable and totally fine. If you want to avoid looping over the string multiple times, here's a detailed explanation of one possible way to do it: 4 | 5 | 1. First, incorporate the non-digit handling into the main loop: 6 | 7 | - Whitespace: whitespace is allowed in valid inputs, so when we encounter whitespace, we can use `continue` to jump to the next iteration of the loop. 8 | 9 | - Other non-digits: we `return false` if we encounter them in the loop. You can do that with `unicode.IsDigit` if you are working with runes. 10 | 11 | 1. With this whitespace change, we introduced a problem: the index `i` of the loop is not counting "correctly" any more. We can create a new variable `counter` to keep track of how many digits we've seen, and increment it manually in the loop (after the `continue` and `return` cases). Don't forget to check after the loop if `counter > 1` to see if we got more than one digit. 12 | 13 | 1. Another problem we introduced with the whitespace change is that we cannot calculate the `length` before the loop anymore. But we can now iterate backwards over the string since we have an independent digit counter: `for i := len(str) - 1; 0 <= i; i-- {...}`. 14 | 15 | The digit rune is then `r := rune(str[i])`. You can test the value of `counter` to see whether or not the current digit should be doubled. 16 | 17 | At this point things should work again and be many times faster! 18 | 19 | - One last thing: We can replace `!unicode.IsDigit(r)` with `r < '0' || r > '9'` and we get another increase in speed. (However, calling `unicode.IsDigit` makes it slightly clearer what's going on. Usually, readability is more important than speed.) 20 | 21 | Note: with this step we can drop the conversion to `rune`. It works the same with `byte`. 22 | 23 | Now we should be in the area of `20-100 ns/op` depending on the hardware the benchmarks are run on. 24 | 25 | --- 26 | -------------------------------------------------------------------------------- /track/luhn/tpl/reg.go: -------------------------------------------------------------------------------- 1 | //go:generate go-bindata -ignore=\.go -pkg=tpl -o=bindata.go ./... 2 | 3 | package tpl 4 | 5 | import "github.com/exercism/exalysis/gtpl" 6 | 7 | //Templates to be used in the response of suggester 8 | var ( 9 | RegexInFunc = gtpl.NewStringTemplate("regex-in-func.md", MustAsset) 10 | MustCompile = gtpl.NewStringTemplate("mustcompile.md", MustAsset) 11 | RegexToFast = gtpl.NewStringTemplate("regex-to-fast.md", MustAsset) 12 | OneLoop = gtpl.NewStringTemplate("one-loop.md", MustAsset) 13 | ) 14 | -------------------------------------------------------------------------------- /track/luhn/tpl/regex-in-func.md: -------------------------------------------------------------------------------- 1 | - Regular expressions in Go work a little differently from other languages. For better performance, a regex is compiled before it's used. Right now, you're compiling the regex inside your function, but it only needs to be compiled once. If the regex is static you can move the compilation to package level. 2 | -------------------------------------------------------------------------------- /track/luhn/tpl/regex-to-fast.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | Your solution with `regexp` is very readable and totally fine. Regular expressions are very powerful, but usually slow. If things can be done manually with reasonable effort the extra work is often worth it. Here is a step-by-step guide to a faster solution: 4 | 5 | 1. First, eliminate the regular expression by moving the logic into the loop: 6 | 7 | - Whitespace is allowed, so we can `continue` if we encounter whitespace in the loop. 8 | 9 | - Other non-digits: we `return false` if we encounter them in the loop. You can do that with `unicode.IsDigit` if you are working with runes. 10 | 11 | 1. With this whitespace change, we introduced a problem: the index `i` of the loop is not counting "correctly" any more. We can create a new variable `counter` to keep track of how many digits we've seen, and increment it manually in the loop (after the `continue` and `return` cases). Don't forget to check after the loop if `counter > 1` to see if we got more than one digit. 12 | 13 | 1. Another problem we introduced with the whitespace change is that we cannot calculate the `length` before the loop anymore. But we can now iterate backwards over the string since we have an independent digit counter: `for i := len(str) - 1; 0 <= i; i-- {...}`. 14 | 15 | The digit rune is then `r := rune(str[i])`. You can test the value of `counter` to see whether or not the current digit should be doubled. 16 | 17 | At this point things should work again and be many times faster! 18 | 19 | - One last thing: We can replace `!unicode.IsDigit(r)` with `r < '0' || r > '9'` and we get another increase in speed. (However, calling `unicode.IsDigit` makes it slightly clearer what's going on. Usually, readability is more important than speed.) 20 | 21 | Note: with this step we can drop the conversion to `rune`. It works the same with `byte`. 22 | 23 | Now we should be in the area of `20-100 ns/op` depending on the hardware the benchmarks are run on. 24 | 25 | --- 26 | -------------------------------------------------------------------------------- /track/paraletterfreq/paraletterfreq.go: -------------------------------------------------------------------------------- 1 | package paraletterfreq 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/exercism/exalysis/extypes" 7 | "github.com/exercism/exalysis/track/paraletterfreq/tpl" 8 | "github.com/tehsphinx/astrav" 9 | ) 10 | 11 | // Suggest builds suggestions for the exercise solution 12 | func Suggest(pkg *astrav.Package, r *extypes.Response) { 13 | for _, tf := range exFuncs { 14 | tf(pkg, r) 15 | } 16 | } 17 | 18 | var exFuncs = []extypes.SuggestionFunc{ 19 | addConcurrencyNotFaster, 20 | examAddOne, 21 | examWaitGroup, 22 | examBufferSizeLen, 23 | examSelect, 24 | examGoroutineLeak, 25 | examForRange, 26 | examCombineWhileWaiting, 27 | examMutex, 28 | examRangeChan, 29 | } 30 | 31 | func addConcurrencyNotFaster(_ *astrav.Package, r *extypes.Response) { 32 | r.AppendOutro(tpl.ConcurrencyNotFaster) 33 | } 34 | 35 | func examWaitGroup(pkg *astrav.Package, r *extypes.Response) { 36 | wgs := pkg.FindByName("WaitGroup") 37 | goTokens := pkg.FindByNodeType(astrav.NodeTypeGoStmt) 38 | if len(wgs) != 0 && 1 < len(goTokens) { 39 | r.AppendImprovementTpl(tpl.WaitGroup) 40 | } 41 | if len(wgs) != 0 && len(goTokens) == 1 { 42 | r.AppendImprovementTpl(tpl.WaitGroupNotNeeded) 43 | } 44 | } 45 | 46 | func examAddOne(pkg *astrav.Package, r *extypes.Response) { 47 | nodes := pkg.FindByName("Add") 48 | for _, node := range nodes { 49 | bLit := node.NextParentByType(astrav.NodeTypeCallExpr).FindFirstByNodeType(astrav.NodeTypeBasicLit) 50 | if bLit == nil { 51 | continue 52 | } 53 | if bLit.(*astrav.BasicLit).Value == "1" { 54 | r.AppendImprovementTpl(tpl.WaitGroupAddOne) 55 | } 56 | } 57 | } 58 | 59 | func examBufferSizeLen(pkg *astrav.Package, r *extypes.Response) { 60 | nodes := pkg.FindByName("make") 61 | for _, node := range nodes { 62 | if !node.IsNodeType(astrav.NodeTypeCallExpr) { 63 | continue 64 | } 65 | lenNode := node.FindFirstByName("len") 66 | if lenNode != nil { 67 | r.AppendImprovementTpl(tpl.BufferSizeLen) 68 | } 69 | } 70 | } 71 | 72 | func examSelect(pkg *astrav.Package, r *extypes.Response) { 73 | nodes := pkg.FindByNodeType(astrav.NodeTypeSelectStmt) 74 | for _, node := range nodes { 75 | if len(node.Children()) == 1 { 76 | r.AppendImprovementTpl(tpl.SelectNotNeeded) 77 | } 78 | } 79 | } 80 | 81 | func examGoroutineLeak(pkg *astrav.Package, r *extypes.Response) { 82 | nodes := pkg.FindByNodeType(astrav.NodeTypeGoStmt) 83 | for _, node := range nodes { 84 | loops := node.FindByNodeType(astrav.NodeTypeForStmt) 85 | for _, loop := range loops { 86 | cond := loop.(*astrav.ForStmt).Cond() 87 | if cond == nil { 88 | r.AppendTodoTpl(tpl.GoroutineLeak) 89 | } 90 | } 91 | } 92 | } 93 | 94 | func examForRange(pkg *astrav.Package, r *extypes.Response) { 95 | loops := pkg.FindByNodeType(astrav.NodeTypeForStmt) 96 | if len(loops) == 0 { 97 | return 98 | } 99 | mergeLoop := loops[len(loops)-1] 100 | cond := mergeLoop.(*astrav.ForStmt).Cond() 101 | if cond != nil { 102 | r.AppendImprovementTpl(tpl.ForRangeNoVars) 103 | } 104 | } 105 | 106 | func examCombineWhileWaiting(pkg *astrav.Package, r *extypes.Response) { 107 | loops := pkg.FindByNodeType(astrav.NodeTypeRangeStmt) 108 | if len(loops) == 0 { 109 | return 110 | } 111 | mergeLoop := loops[len(loops)-1] 112 | for _, node := range mergeLoop.FindByNodeType(astrav.NodeTypeAssignStmt) { 113 | token := node.(*astrav.AssignStmt).Tok.String() 114 | if token == "+=" { 115 | return 116 | } 117 | } 118 | // They didn't update the map inside the merge loop 119 | r.AppendImprovementTpl(tpl.CombineMapsWhileWaiting) 120 | } 121 | 122 | func examMutex(pkg *astrav.Package, r *extypes.Response) { 123 | mutexes := pkg.FindByValueType("sync.Mutex") 124 | if len(mutexes) > 0 { 125 | r.AppendImprovementTpl(tpl.Mutex) 126 | } 127 | } 128 | 129 | func examRangeChan(pkg *astrav.Package, r *extypes.Response) { 130 | for _, node := range pkg.FindByNodeType(astrav.NodeTypeRangeStmt) { 131 | x := node.(*astrav.RangeStmt).X() 132 | if strings.Contains(x.ValueType().String(), "chan") { 133 | r.AppendImprovementTpl(tpl.RangeChan) 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /track/paraletterfreq/paraletterfreq_test.go: -------------------------------------------------------------------------------- 1 | package paraletterfreq 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/exercism/exalysis/extypes" 8 | "github.com/exercism/exalysis/gtpl" 9 | "github.com/exercism/exalysis/testhelper" 10 | "github.com/exercism/exalysis/track/paraletterfreq/tpl" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var suggestTests = []struct { 15 | path string 16 | suggestion gtpl.Template 17 | expected bool 18 | }{ 19 | {path: "./solutions/1", suggestion: tpl.WaitGroup, expected: true}, 20 | {path: "./solutions/1", suggestion: tpl.WaitGroupNotNeeded, expected: false}, 21 | {path: "./solutions/1", suggestion: tpl.WaitGroupAddOne, expected: false}, 22 | {path: "./solutions/1", suggestion: tpl.RangeChan, expected: false}, 23 | {path: "./solutions/1", suggestion: tpl.BufferSizeLen, expected: true}, 24 | {path: "./solutions/1", suggestion: tpl.SelectNotNeeded, expected: true}, 25 | {path: "./solutions/1", suggestion: tpl.GoroutineLeak, expected: true}, 26 | {path: "./solutions/2", suggestion: tpl.WaitGroup, expected: false}, 27 | {path: "./solutions/2", suggestion: tpl.WaitGroupNotNeeded, expected: true}, 28 | {path: "./solutions/2", suggestion: tpl.WaitGroupAddOne, expected: true}, 29 | {path: "./solutions/2", suggestion: tpl.RangeChan, expected: false}, 30 | {path: "./solutions/2", suggestion: tpl.BufferSizeLen, expected: false}, 31 | {path: "./solutions/2", suggestion: tpl.SelectNotNeeded, expected: true}, 32 | {path: "./solutions/3", suggestion: tpl.BufferSizeLen, expected: false}, 33 | {path: "./solutions/3", suggestion: tpl.ForRangeNoVars, expected: true}, 34 | {path: "./solutions/4", suggestion: tpl.BufferSizeLen, expected: true}, 35 | {path: "./solutions/5", suggestion: tpl.CombineMapsWhileWaiting, expected: true}, 36 | {path: "./solutions/6", suggestion: tpl.ForRangeNoVars, expected: true}, 37 | {path: "./solutions/7", suggestion: tpl.Mutex, expected: true}, 38 | {path: "./solutions/7", suggestion: tpl.WaitGroupAddOne, expected: true}, 39 | {path: "./solutions/8", suggestion: tpl.RangeChan, expected: true}, 40 | {path: "./solutions/8", suggestion: tpl.WaitGroupAddOne, expected: true}, 41 | {path: "./solutions/9", suggestion: tpl.ForRangeNoVars, expected: true}, 42 | {path: "./solutions/10", suggestion: tpl.WaitGroup, expected: true}, 43 | } 44 | 45 | func Test_Suggest(t *testing.T) { 46 | for _, test := range suggestTests { 47 | _, pkg, err := testhelper.LoadExample(test.path, "letter") 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | r := extypes.NewResponse() 53 | Suggest(pkg, r) 54 | 55 | assert.Equal(t, test.expected, r.HasSuggestion(test.suggestion), 56 | fmt.Sprintf("test failed: %+v", test)) 57 | } 58 | } 59 | 60 | /* 61 | TODO: More solutions to look through: 62 | 63 | https://exercism.io/mentor/solutions/01c8d3773ac941a1aba4ac40583c0b04 64 | https://exercism.io/mentor/solutions/6be812e536484fe8b4cf81dadc14e532 65 | https://exercism.io/mentor/solutions/8abc3969b5834267bf564e4ce421132a 66 | https://exercism.io/mentor/solutions/916f4e9532e24367a1d39116e9a81d40 67 | https://exercism.io/mentor/solutions/a90a88b7f4964251b1de8cb16c1cd03e 68 | https://exercism.io/mentor/solutions/061b04cb2d6e4518b06971306ac7adbf 69 | https://exercism.io/mentor/solutions/0980af90bb6649c7ab0453a7abde0fe0 70 | https://exercism.io/mentor/solutions/70c5fb9aa5df4950915b30b721850136 71 | https://exercism.io/mentor/solutions/5c68967dc4d245a0aee1c5694dd54c41 72 | */ 73 | -------------------------------------------------------------------------------- /track/paraletterfreq/solutions/1/solution.go: -------------------------------------------------------------------------------- 1 | package letter 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type FreqMap map[rune]int 8 | 9 | type SyncFreqMap struct { 10 | sync.Mutex 11 | FreqMap 12 | } 13 | 14 | func Frequency(s string) FreqMap { 15 | m := FreqMap{} 16 | for _, r := range s { 17 | m[r]++ 18 | } 19 | return m 20 | } 21 | 22 | func ConcurrentFrequency(input []string) FreqMap { 23 | m := FreqMap{} 24 | var wg sync.WaitGroup 25 | wg.Add(len(input)) 26 | var ch = make(chan rune, len(input)) 27 | for _, in := range input { 28 | go func(chunk string, c chan rune) { 29 | for _, r := range chunk { 30 | c <- r 31 | } 32 | wg.Done() 33 | }(in, ch) 34 | } 35 | go func() { 36 | for { 37 | select { 38 | case ltr := <-ch: 39 | m[ltr]++ 40 | } 41 | } 42 | }() 43 | wg.Wait() 44 | return m 45 | } 46 | -------------------------------------------------------------------------------- /track/paraletterfreq/solutions/10/solution.go: -------------------------------------------------------------------------------- 1 | // Package letter provide methods to count letter frequency. 2 | package letter 3 | 4 | import "sync" 5 | 6 | // FreqMap express a character map with the number of occurencies for each letter. 7 | type FreqMap map[rune]int 8 | 9 | // Frequency takes a string and returns a FreqMap with the total frequency of each letter. 10 | func Frequency(s string) FreqMap { 11 | m := FreqMap{} 12 | for _, r := range s { 13 | m[r]++ 14 | } 15 | return m 16 | } 17 | 18 | // ConcurrentFrequency takes a list of strings and calculate letter frequency concurrently, it returns a FreqMap with the total frequency of each letter. 19 | func ConcurrentFrequency(texts []string) FreqMap { 20 | return <-collect(count(texts)) 21 | } 22 | 23 | func count(texts []string) <-chan FreqMap { 24 | ch := make(chan FreqMap, len(texts)) 25 | go func() { 26 | var wg sync.WaitGroup 27 | wg.Add(len(texts)) 28 | for _, t := range texts { 29 | go func(t string) { 30 | ch <- Frequency(t) 31 | wg.Done() 32 | }(t) 33 | } 34 | wg.Wait() 35 | close(ch) 36 | }() 37 | return ch 38 | } 39 | 40 | func collect(ch <-chan FreqMap) <-chan FreqMap { 41 | res := make(chan FreqMap) 42 | go func() { 43 | m := FreqMap{} 44 | for r := range ch { 45 | merge(m, r) 46 | } 47 | res <- m 48 | close(res) 49 | }() 50 | return res 51 | } 52 | 53 | func merge(to, this FreqMap) { 54 | for k, v := range this { 55 | to[k] += v 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /track/paraletterfreq/solutions/2/solution.go: -------------------------------------------------------------------------------- 1 | package letter 2 | 3 | import "sync" 4 | 5 | type FreqMap map[rune]int 6 | 7 | func Frequency(s string) FreqMap { 8 | m := FreqMap{} 9 | for _, r := range s { 10 | m[r]++ 11 | } 12 | return m 13 | } 14 | 15 | func mergeFreqMap(c chan FreqMap, num int) FreqMap { 16 | out := FreqMap{} 17 | 18 | for i := 0; i < num; i++ { 19 | select { 20 | case m := <-c: 21 | for k, v := range m { 22 | out[k] += v 23 | } 24 | } 25 | } 26 | return out 27 | } 28 | 29 | // func ConcurrentFrequency(texts []string) FreqMap { 30 | // var globMap sync.Map 31 | // for _, s := range texts { 32 | // go func() { 33 | // result := Frequency(s) 34 | // for k, v := range result { 35 | // globMap 36 | // } 37 | // }() 38 | // } 39 | 40 | // } 41 | 42 | func ConcurrentFrequency(texts []string) FreqMap { 43 | // This isnt the right way to do it 44 | // maps := make([]FreqMap, 3) 45 | var wg sync.WaitGroup 46 | c := make(chan FreqMap) 47 | for _, s := range texts { 48 | wg.Add(1) 49 | go func(wg *sync.WaitGroup, str string) { 50 | defer wg.Done() 51 | result := Frequency(str) 52 | c <- result 53 | }(&wg, s) 54 | } 55 | result := mergeFreqMap(c, len(texts)) 56 | wg.Wait() 57 | 58 | return result 59 | } 60 | -------------------------------------------------------------------------------- /track/paraletterfreq/solutions/3/solution.go: -------------------------------------------------------------------------------- 1 | package letter 2 | 3 | type FreqMap map[rune]int 4 | 5 | func Frequency(s string) FreqMap { 6 | m := FreqMap{} 7 | for _, r := range s { 8 | m[r]++ 9 | } 10 | return m 11 | } 12 | 13 | func ConcurrentFrequency(s []string) FreqMap { 14 | 15 | freqCh := make(chan FreqMap) 16 | 17 | for _, sentence := range s { 18 | go func(s string) { 19 | freqCh <- Frequency(s) 20 | }(sentence) 21 | } 22 | 23 | finalMap := FreqMap{} 24 | for i := 0; i < len(s); i++ { 25 | tmpFreqMap := <-freqCh 26 | for word, freq := range tmpFreqMap { 27 | finalMap[word] += freq 28 | } 29 | } 30 | return finalMap 31 | } -------------------------------------------------------------------------------- /track/paraletterfreq/solutions/4/solution.go: -------------------------------------------------------------------------------- 1 | package letter 2 | 3 | // FreqMap is a map of each letter and it's frequency 4 | type FreqMap map[rune]int 5 | 6 | // Frequency is a function to count the frequency of letters in a string 7 | func Frequency(s string) FreqMap { 8 | m := FreqMap{} 9 | for _, r := range s { 10 | m[r]++ 11 | } 12 | return m 13 | } 14 | 15 | // ConcurrentFrequency is a function that uses channels and goroutines to 16 | // concurrently count the letter frequency in different strings at once. 17 | func ConcurrentFrequency(texts []string) FreqMap { 18 | channel := make(chan FreqMap, len(texts)) 19 | for _, text := range texts { 20 | go func(text string) { 21 | channel <- Frequency(text) 22 | }(text) 23 | } 24 | 25 | result := FreqMap{} 26 | for range texts { 27 | for letter, count := range <-channel { 28 | result[letter] += count 29 | } 30 | } 31 | return result 32 | } -------------------------------------------------------------------------------- /track/paraletterfreq/solutions/5/solution.go: -------------------------------------------------------------------------------- 1 | package letter 2 | 3 | // FreqMap structure 4 | type FreqMap map[rune]int 5 | 6 | // Frequency counts letter frequencies in a text 7 | func Frequency(s string) FreqMap { 8 | m := FreqMap{} 9 | for _, r := range s { 10 | m[r]++ 11 | } 12 | return m 13 | } 14 | 15 | func frequencyWithChannel(s string, ch chan FreqMap) { 16 | m := FreqMap{} 17 | for _, r := range s { 18 | m[r]++ 19 | } 20 | ch <- m 21 | } 22 | 23 | func combineMaps(maps []FreqMap) FreqMap { 24 | res := FreqMap{} 25 | for _, m := range maps { 26 | for letter, freq := range m { 27 | res[letter] += freq 28 | } 29 | } 30 | return res 31 | } 32 | 33 | // ConcurrentFrequency calculates letter frequencies in each text on parallelism 34 | // And combine all results 35 | func ConcurrentFrequency(texts []string) FreqMap { 36 | maps := []FreqMap{} 37 | channel := make(chan FreqMap, 3) 38 | for _, text := range texts { 39 | go frequencyWithChannel(text, channel) 40 | } 41 | for range texts { 42 | maps = append(maps, <-channel) 43 | } 44 | return combineMaps(maps) 45 | } 46 | -------------------------------------------------------------------------------- /track/paraletterfreq/solutions/6/solution.go: -------------------------------------------------------------------------------- 1 | package letter 2 | 3 | // FreqMap is a map that maps runes to int 4 | // It is a map that gives the count of each 5 | // letter in a string, with the letters as keys. 6 | type FreqMap map[rune]int 7 | 8 | // Frequency takes in a string and returns a FreqMap 9 | // map where the keys are the unique letters in the input 10 | // string and the values are the counts of each of those 11 | // letters in the input string. This is a synchronous 12 | // function. 13 | func Frequency(s string) FreqMap { 14 | m := FreqMap{} 15 | for _, r := range s { 16 | m[r]++ 17 | } 18 | return m 19 | } 20 | 21 | // ConcurrentFrequency takes in a slice of strings and returns 22 | // a FreqMap where the keys are the unique letters in all the 23 | // input strings in the input slice combined, and the values 24 | // are the counts of those letters. 25 | func ConcurrentFrequency(stringList []string) FreqMap { 26 | 27 | c := make(chan FreqMap, len(stringList)) 28 | finalMap := FreqMap{} 29 | 30 | // Spawn goroutines for each string in the input slice 31 | for _, str := range stringList { 32 | go func(aString string) { 33 | c <- Frequency(aString) 34 | }(str) 35 | } 36 | 37 | // Loop over the channel and return final FreqMap 38 | for i := 1; i <= len(stringList); i++ { 39 | aMap := <-c 40 | for key, value := range aMap { 41 | finalMap[key] += value 42 | } 43 | } 44 | return finalMap 45 | } 46 | -------------------------------------------------------------------------------- /track/paraletterfreq/solutions/7/solution.go: -------------------------------------------------------------------------------- 1 | package letter 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // FreqMap custom map type 8 | type FreqMap map[rune]int 9 | 10 | // Frequency calculates chars in string 11 | func Frequency(s string) FreqMap { 12 | m := FreqMap{} 13 | for _, r := range s { 14 | m[r]++ 15 | } 16 | return m 17 | } 18 | 19 | // ConcurrentFrequency calculates chars in string with concurrency 20 | func ConcurrentFrequency(sl []string) FreqMap { 21 | resMap := FreqMap{} 22 | mx := &sync.Mutex{} 23 | wg := &sync.WaitGroup{} 24 | for _, s := range sl { 25 | wg.Add(1) 26 | go func(s string) { 27 | defer wg.Done() 28 | for _, r := range s { 29 | mx.Lock() 30 | resMap[r]++ 31 | mx.Unlock() 32 | } 33 | }(s) 34 | } 35 | wg.Wait() 36 | 37 | return resMap 38 | } 39 | -------------------------------------------------------------------------------- /track/paraletterfreq/solutions/8/solution.go: -------------------------------------------------------------------------------- 1 | package letter 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // FreqMap custom map type 8 | type FreqMap map[rune]int 9 | 10 | // Frequency calculates chars in string 11 | func Frequency(s string) FreqMap { 12 | m := FreqMap{} 13 | for _, r := range s { 14 | m[r]++ 15 | } 16 | return m 17 | } 18 | 19 | // ConcurrentFrequency calculates chars in string with concurrency 20 | func ConcurrentFrequency(sl []string) FreqMap { 21 | resMap := FreqMap{} 22 | wg := &sync.WaitGroup{} 23 | ch := make(chan rune) 24 | for _, s := range sl { 25 | wg.Add(1) 26 | go func(s string) { 27 | defer wg.Done() 28 | for _, r := range s { 29 | ch <- r 30 | } 31 | }(s) 32 | } 33 | 34 | go func() { 35 | wg.Wait() 36 | close(ch) 37 | }() 38 | 39 | for r := range ch { 40 | resMap[r]++ 41 | } 42 | 43 | return resMap 44 | } 45 | -------------------------------------------------------------------------------- /track/paraletterfreq/solutions/9/solution.go: -------------------------------------------------------------------------------- 1 | package letter 2 | 3 | // FreqMap custom map type 4 | type FreqMap map[rune]int 5 | 6 | // Frequency calculates chars in string 7 | func Frequency(s string) FreqMap { 8 | m := FreqMap{} 9 | for _, r := range s { 10 | m[r]++ 11 | } 12 | return m 13 | } 14 | 15 | // ConcurrentFrequency calculates chars in string with concurrency 16 | func ConcurrentFrequency(sl []string) FreqMap { 17 | resMap := FreqMap{} 18 | ch := make(chan FreqMap) 19 | 20 | for _, s := range sl { 21 | go func(s string) { 22 | ch <- Frequency(s) 23 | }(s) 24 | } 25 | 26 | for i := 0; i < len(sl); i++ { 27 | for k, v := range <-ch { 28 | resMap[k] += v 29 | } 30 | } 31 | 32 | return resMap 33 | } 34 | -------------------------------------------------------------------------------- /track/paraletterfreq/tpl/buffer-size-len.md: -------------------------------------------------------------------------------- 1 | - Using a buffered channel is a good idea, and will prevent goroutines from blocking when sending to the channel. However, the buffer doesn't need as many slots as there are goroutines, because we will be able to start receiving from the channel while some goroutines are still sending to it. The exact optimal buffer size depends on the relative speed of the senders and receivers, and you can use trial and error to determine this, but for most purposes a small fixed size (say 10) is just fine. 2 | -------------------------------------------------------------------------------- /track/paraletterfreq/tpl/combine-maps-while-waiting.md: -------------------------------------------------------------------------------- 1 | - It looks like you wait until all results have been received from the goroutines before starting to merge them. In fact, you can (and should) start processing as soon as you receive the first result. 2 | -------------------------------------------------------------------------------- /track/paraletterfreq/tpl/concurrency-not-faster.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | ***Concurrency: What's the point?*** 4 | 5 | If you've benchmarked your solution to this exercise, you might be mildly surprised that the concurrent version isn't much faster than the sequential one. Why not? 6 | 7 | The point of this exercise is that using concurrency does not always improve performance. Sometimes it actually makes things worse! In this exercise, you can get the concurrent version to run about as fast as the sequential one; maybe slightly faster. But concurrency isn't free; we have to do a fair bit of work to organise the concurrent goroutines and pass the data and results around between them. Also, counting letter frequency is a relatively fast task; merging maps is slow. So we are actually parallelising only a small part of the work here. 8 | 9 | Using concurrency is most beneficial when you can break up a task into multiple chunks that can be processed independently, and when the process of merging the chunked results isn't too expensive. Keep this in mind when you're thinking about whether a concurrent approach to solving a given problem makes sense or not. 10 | 11 | But concurrency isn't just about speed. It's also about dealing with multiple things at once; for example, handling requests to a web server. Processing requests concurrently means that you can get very high throughput (requests per second). Whenever you're dealing with multiple simultaneous requests, a concurrent solution is probably the way to go. 12 | 13 | To learn more about concurrency in Go and how to use it effectively, watch Rob Pike's talk on [Go Concurrency Patterns](https://www.youtube.com/watch?v=f6kdp27TYZs). 14 | 15 | --- 16 | -------------------------------------------------------------------------------- /track/paraletterfreq/tpl/for-range-novars.md: -------------------------------------------------------------------------------- 1 | - By the way, a shorter way to write your merging loop is just `for range texts {...}`. You probably know that `range` returns up to two values (index and element), and you can ignore either of them. If you ignore both of them, as here, you get a loop that executes once per element. 2 | -------------------------------------------------------------------------------- /track/paraletterfreq/tpl/goroutine-leak.md: -------------------------------------------------------------------------------- 1 | - A good rule of thumb with goroutines is that whenever you start one, you should know how it's going to finish. Your goroutine here has no termination condition and so will run forever in the background. This is a so-called _goroutine leak_; a long-running program with a goroutine leak will gradually accumulate more and more useless goroutines until it runs out of resources and crashes. 2 | -------------------------------------------------------------------------------- /track/paraletterfreq/tpl/mutex.md: -------------------------------------------------------------------------------- 1 | - You are using a mutex to synchronize your map writes. A mutex will lose you most of the benefits of concurrency, because it forces all the goroutines to queue up waiting to enter the critical section of code protected by the mutex. There's no point using concurrent goroutines if you're going to force them all to execute sequentially! Instead, try using a channel to pass the results back from the goroutines. Channels are inherently concurrency-safe, so you can send and receive on a channel without needing a mutex to prevent data races. 2 | 3 | (Channels aren't the answer to everything; sometimes you do need to protect concurrent access to a single piece of shared memory, and a mutex is ideal for this. In general, though, as a Go proverb reminds us, "Don't communicate by sharing memory; share memory by communicating".) 4 | -------------------------------------------------------------------------------- /track/paraletterfreq/tpl/range-chan.md: -------------------------------------------------------------------------------- 1 | - Instead of having the receiver range over the channel, which will keep receiving until the channel is closed, use `for range texts` (ranging over the input slice). That way you'll loop until you've received the same number of results as there were inputs. 2 | -------------------------------------------------------------------------------- /track/paraletterfreq/tpl/reg.go: -------------------------------------------------------------------------------- 1 | //go:generate go-bindata -ignore=\.go -pkg=tpl -o=bindata.go ./... 2 | 3 | package tpl 4 | 5 | import "github.com/exercism/exalysis/gtpl" 6 | 7 | // Templates to be used in the response of suggester 8 | var ( 9 | ConcurrencyNotFaster = gtpl.NewStringTemplate("concurrency-not-faster.md", MustAsset) 10 | WaitGroup = gtpl.NewStringTemplate("waitgroup.md", MustAsset) 11 | WaitGroupAddOne = gtpl.NewStringTemplate("waitgroup-add-one.md", MustAsset) 12 | WaitGroupNotNeeded = gtpl.NewStringTemplate("waitgroup-not-needed.md", MustAsset) 13 | RangeChan = gtpl.NewStringTemplate("range-chan.md", MustAsset) 14 | BufferSizeLen = gtpl.NewStringTemplate("buffer-size-len.md", MustAsset) 15 | CombineMapsWhileWaiting = gtpl.NewStringTemplate("combine-maps-while-waiting.md", MustAsset) 16 | ForRangeNoVars = gtpl.NewStringTemplate("for-range-novars.md", MustAsset) 17 | SelectNotNeeded = gtpl.NewStringTemplate("select-not-needed.md", MustAsset) 18 | GoroutineLeak = gtpl.NewStringTemplate("goroutine-leak.md", MustAsset) 19 | Mutex = gtpl.NewStringTemplate("mutex.md", MustAsset) 20 | ) 21 | -------------------------------------------------------------------------------- /track/paraletterfreq/tpl/select-not-needed.md: -------------------------------------------------------------------------------- 1 | - You are using a `select` with only one case and no other code in a `for` loop. As a channel receive operation will block until a value is available, the `select` is unnecessary here. 2 | -------------------------------------------------------------------------------- /track/paraletterfreq/tpl/waitgroup-add-one.md: -------------------------------------------------------------------------------- 1 | - You are using a `sync.WaitGroup` where you `Add` one in every loop iteration. If you know from the beginning how many iterations there will be, just add that number right away: `wg.Add(len(texts))`. 2 | -------------------------------------------------------------------------------- /track/paraletterfreq/tpl/waitgroup-not-needed.md: -------------------------------------------------------------------------------- 1 | - You are using a `sync.WaitGroup` here that is not really needed. Can you check and remove it? 2 | -------------------------------------------------------------------------------- /track/paraletterfreq/tpl/waitgroup.md: -------------------------------------------------------------------------------- 1 | - You are using a `sync.WaitGroup` here. You could move the receiving side of the channel into the main goroutine and get rid of the `WaitGroup`. Remember: you know how many you're expecting to receive from the channel. When you've received them all, you're done. 2 | -------------------------------------------------------------------------------- /track/raindrops/raindrops_test.go: -------------------------------------------------------------------------------- 1 | package raindrops 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/exercism/exalysis/extypes" 8 | "github.com/exercism/exalysis/gtpl" 9 | "github.com/exercism/exalysis/testhelper" 10 | "github.com/exercism/exalysis/track/raindrops/tpl" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var suggestTests = []struct { 15 | path string 16 | suggestion gtpl.Template 17 | expected bool 18 | }{ 19 | {path: "./solutions/1", suggestion: tpl.ManyLoops, expected: true}, 20 | {path: "./solutions/1", suggestion: tpl.RemoveExtraBool, expected: false}, 21 | {path: "./solutions/1", suggestion: tpl.AllCases, expected: false}, 22 | {path: "./solutions/2", suggestion: tpl.RemoveExtraBool, expected: false}, 23 | {path: "./solutions/3", suggestion: tpl.StringsBuilder, expected: true}, 24 | {path: "./solutions/3", suggestion: tpl.Itoa, expected: false}, 25 | {path: "./solutions/3", suggestion: tpl.RemoveExtraBool, expected: false}, 26 | {path: "./solutions/4", suggestion: tpl.PlusEqual, expected: true}, 27 | {path: "./solutions/4", suggestion: tpl.RemoveExtraBool, expected: false}, 28 | {path: "./solutions/4", suggestion: tpl.FmtPrint, expected: false}, 29 | {path: "./solutions/5", suggestion: tpl.Itoa, expected: true}, 30 | {path: "./solutions/5", suggestion: tpl.RemoveExtraBool, expected: false}, 31 | {path: "./solutions/5", suggestion: tpl.FmtPrint, expected: false}, 32 | {path: "./solutions/5", suggestion: tpl.AllCases, expected: false}, 33 | {path: "./solutions/6", suggestion: tpl.ExtensiveFor, expected: true}, 34 | {path: "./solutions/6", suggestion: tpl.PlusEqual, expected: true}, 35 | {path: "./solutions/6", suggestion: tpl.RemoveExtraBool, expected: false}, 36 | {path: "./solutions/7", suggestion: tpl.LoopMap, expected: true}, 37 | {path: "./solutions/7", suggestion: tpl.RemoveExtraBool, expected: false}, 38 | {path: "./solutions/8", suggestion: tpl.ExtensiveFor, expected: true}, 39 | {path: "./solutions/8", suggestion: tpl.RemoveExtraBool, expected: false}, 40 | {path: "./solutions/9", suggestion: tpl.ExtensiveFor, expected: true}, 41 | {path: "./solutions/10", suggestion: tpl.ExtensiveFor, expected: true}, 42 | {path: "./solutions/11", suggestion: tpl.ExtensiveFor, expected: false}, 43 | {path: "./solutions/12", suggestion: tpl.StringsBuilder, expected: true}, 44 | {path: "./solutions/12", suggestion: tpl.RemoveExtraBool, expected: true}, 45 | {path: "./solutions/13", suggestion: tpl.RemoveExtraBool, expected: true}, 46 | {path: "./solutions/14", suggestion: tpl.FmtPrint, expected: true}, 47 | {path: "./solutions/14", suggestion: tpl.Itoa, expected: false}, 48 | {path: "./solutions/15", suggestion: tpl.AllCases, expected: true}, 49 | {path: "./solutions/16", suggestion: tpl.AllCases, expected: true}, 50 | {path: "./solutions/17", suggestion: tpl.ManyLoops, expected: true}, 51 | {path: "./solutions/17", suggestion: tpl.Itoa, expected: true}, 52 | {path: "./solutions/17", suggestion: tpl.RemoveExtraBool, expected: true}, 53 | } 54 | 55 | func Test_Suggest(t *testing.T) { 56 | for _, test := range suggestTests { 57 | _, pkg, err := testhelper.LoadExample(test.path, "raindrops") 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | 62 | r := extypes.NewResponse() 63 | Suggest(pkg, r) 64 | 65 | assert.Equal(t, test.expected, r.HasSuggestion(test.suggestion), 66 | fmt.Sprintf("test failed: %+v", test)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /track/raindrops/solutions/1/solution.go: -------------------------------------------------------------------------------- 1 | //Package raindrops converts number to string and outputs info depending on numbers factors 2 | package raindrops 3 | 4 | import ( 5 | "strconv" 6 | ) 7 | 8 | //Convert converts number and returns Pling, Plang, Plong or number 9 | func Convert(number int) (output string) { 10 | var factors []int 11 | for i := 1; i <= number; i++ { 12 | if number%i == 0 { 13 | factors = append(factors, i) 14 | } 15 | if i == 7 { 16 | break 17 | } 18 | } 19 | 20 | ppp := map[int]string{3: "Pling", 5: "Plang", 7: "Plong"} 21 | for f := 0; f < len(factors); f++ { 22 | for k, v := range ppp { 23 | if factors[f] == k { 24 | output = output + v 25 | } 26 | } 27 | } 28 | if output == "" { 29 | output = strconv.Itoa(number) 30 | } 31 | 32 | return output 33 | } 34 | -------------------------------------------------------------------------------- /track/raindrops/solutions/10/solution.go: -------------------------------------------------------------------------------- 1 | // Package raindrops convert a number to a string, the contents of which depend on the number's factors. 2 | package raindrops 3 | 4 | import "strconv" 5 | 6 | // Convert converts a number to a string, the contents of which depend on the number's factors. 7 | func Convert(i int) string { 8 | var result string 9 | for n := 3; n <= 7; n += 1 { 10 | if i%n == 0 { 11 | switch n { 12 | case 3: 13 | result = result + "Pling" 14 | case 5: 15 | result = result + "Plang" 16 | case 7: 17 | result = result + "Plong" 18 | } 19 | } 20 | } 21 | if result == "" { 22 | return strconv.Itoa(i) 23 | } 24 | return result 25 | } 26 | -------------------------------------------------------------------------------- /track/raindrops/solutions/11/solution.go: -------------------------------------------------------------------------------- 1 | // Package raindrops convert a number to a string, the contents of which depend on the number's factors. 2 | package raindrops 3 | 4 | import "strconv" 5 | 6 | // Convert converts a number to a string, the contents of which depend on the number's factors. 7 | func Convert(i int) string { 8 | var result string 9 | for n := 3; n <= 7; n += 2 { 10 | if i%n == 0 { 11 | switch n { 12 | case 3: 13 | result = result + "Pling" 14 | case 5: 15 | result = result + "Plang" 16 | case 7: 17 | result = result + "Plong" 18 | } 19 | } 20 | } 21 | if result == "" { 22 | return strconv.Itoa(i) 23 | } 24 | return result 25 | } 26 | -------------------------------------------------------------------------------- /track/raindrops/solutions/12/solution.go: -------------------------------------------------------------------------------- 1 | package raindrops 2 | 3 | import ( 4 | "bytes" 5 | "strconv" 6 | ) 7 | 8 | func Convert(num int) string { 9 | // Store the requisite Strings as constant variables 10 | const pling = "Pling" 11 | const plang = "Plang" 12 | const plong = "Plong" 13 | 14 | // I opted to use a Byte Buffer to build my string, there are other ways 15 | var buffer bytes.Buffer 16 | 17 | // This flag handles whether or not we found a factor were looking for 18 | // This is done to handle the requirement that if the number does not have a factor, we return the string version of that number 19 | foundName := false 20 | 21 | // We iterate from 1 to the number, checking each number to see if it is a factor 22 | for i := 1; i <= num; i++ { 23 | 24 | if num%i == 0 { 25 | switch i { 26 | case 3: 27 | buffer.WriteString(pling) 28 | foundName = true 29 | case 5: 30 | buffer.WriteString(plang) 31 | foundName = true 32 | case 7: 33 | buffer.WriteString(plong) 34 | foundName = true 35 | } 36 | } 37 | } 38 | //If we did not find the factors, then we just return the string version of the number 39 | if !foundName { 40 | return strconv.Itoa(num) 41 | } 42 | 43 | // Otherwise, we convert the buffer to a string and return it 44 | return buffer.String() 45 | 46 | } 47 | -------------------------------------------------------------------------------- /track/raindrops/solutions/13/solution.go: -------------------------------------------------------------------------------- 1 | // Package raindrops is involved in converting numbers to pattern-based strings 2 | package raindrops 3 | 4 | import ( 5 | "strconv" 6 | ) 7 | 8 | // Convert takes a number and converts it to a modified string based on its factors 9 | func Convert(num int) string { 10 | result := "" 11 | hasFactor := false 12 | 13 | if num%3 == 0 { 14 | result += "Pling" 15 | hasFactor = true 16 | } 17 | 18 | if num%5 == 0 { 19 | result += "Plang" 20 | hasFactor = true 21 | } 22 | 23 | if num%7 == 0 { 24 | result += "Plong" 25 | hasFactor = true 26 | } 27 | 28 | if hasFactor { 29 | return result 30 | } 31 | 32 | return strconv.Itoa(num) 33 | } 34 | -------------------------------------------------------------------------------- /track/raindrops/solutions/14/solution.go: -------------------------------------------------------------------------------- 1 | // Package raindrops converts ints into onomatopoeia depending on factors of that int. 2 | package raindrops 3 | 4 | import ( 5 | "fmt" 6 | "strconv" 7 | ) 8 | 9 | // Convert takes an int and returns a string containing either the original int, or a series of sounds based on factors of that int. 10 | func Convert(n int) string { 11 | res := "" 12 | if hasFactor(n, 3) { 13 | res = "Pling" 14 | } 15 | if hasFactor(n, 5) { 16 | res = fmt.Sprintf("%sPlang", res) 17 | } 18 | if hasFactor(n, 7) { 19 | res = fmt.Sprintf("%sPlong", res) 20 | } 21 | if len(res) == 0 { 22 | res = strconv.Itoa(n) 23 | } 24 | 25 | return res 26 | } 27 | 28 | func hasFactor(n, f int) bool { 29 | return n%f == 0 30 | } 31 | -------------------------------------------------------------------------------- /track/raindrops/solutions/15/solution.go: -------------------------------------------------------------------------------- 1 | package raindrops 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // Convert prints a number into a raindrop-speak by checking its factors 8 | func Convert(number int) string { 9 | if number%7 == 0 && number%5 == 0 && number%3 == 0 { 10 | return "PlingPlangPlong" 11 | } else if number%5 == 0 && number%3 == 0 { 12 | return "PlingPlang" 13 | } else if number%7 == 0 && number%5 == 0 { 14 | return "PlangPlong" 15 | } else if number%7 == 0 && number%3 == 0 { 16 | return "PlingPlong" 17 | } else if number%7 == 0 { 18 | return "Plong" 19 | } else if number%5 == 0 { 20 | return "Plang" 21 | } else if number%3 == 0 { 22 | return "Pling" 23 | } else { 24 | return strconv.Itoa(number) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /track/raindrops/solutions/16/solution.go: -------------------------------------------------------------------------------- 1 | package raindrops 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // Convert turns integers into string based on factor 8 | func Convert(a int) string { 9 | 10 | factor := []int{3, 5, 7} 11 | 12 | three := "Pling" 13 | five := "Plang" 14 | seven := "Plong" 15 | 16 | if a%factor[0] == 0 && a%factor[1] == 0 && a%factor[2] == 0 { 17 | return three + five + seven 18 | } 19 | if a%factor[0] == 0 && a%factor[1] == 0 { 20 | return three + five 21 | } 22 | if a%factor[0] == 0 && a%factor[2] == 0 { 23 | return three + seven 24 | } 25 | if a%factor[0] == 0 { 26 | return three 27 | } 28 | if a%factor[1] == 0 && a%factor[2] == 0 { 29 | return five + seven 30 | } 31 | 32 | if a%factor[1] == 0 { 33 | return five 34 | } 35 | if a%factor[2] == 0 { 36 | return seven 37 | } 38 | 39 | return strconv.Itoa(a) 40 | } 41 | -------------------------------------------------------------------------------- /track/raindrops/solutions/17/solution.go: -------------------------------------------------------------------------------- 1 | package raindrops 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | ) 7 | 8 | var factorMap = map[int]string{ 9 | 3: "Pling", 10 | 5: "Plang", 11 | 7: "Plong", 12 | } 13 | 14 | // Convert converting the input nr to rain drops 15 | func Convert(input int) string { 16 | factors := extractFactors(input) 17 | var msg string 18 | for _, factor := range factors { 19 | if m, ok := factorMap[factor]; ok { 20 | msg += m 21 | } 22 | } 23 | if msg == "" { 24 | msg = fmt.Sprintf("%d", input) 25 | } 26 | return msg 27 | } 28 | 29 | func extractFactors(number int) []int { 30 | if number == 1 { 31 | return []int{1} 32 | } 33 | var factors = []int{} 34 | var factorsMap = map[int]bool{} 35 | var factor = 1 36 | var factorExist = false 37 | for !factorExist { 38 | if number%factor == 0 { 39 | factors = append(factors, factor) 40 | factorsMap[factor] = true 41 | var result = number / factor 42 | if result != factor { 43 | factors = append(factors, result) 44 | factorsMap[number/factor] = true 45 | } 46 | } 47 | factor++ 48 | _, factorExist = factorsMap[factor] 49 | } 50 | sort.Ints(factors) 51 | return factors 52 | } 53 | -------------------------------------------------------------------------------- /track/raindrops/solutions/2/solution.go: -------------------------------------------------------------------------------- 1 | // Package raindrops provides a simple int to raindrop-speak conversion function 2 | package raindrops 3 | 4 | import "strconv" 5 | 6 | // Convert int to raindrop speak 7 | func Convert(n int) string { 8 | 9 | var raindrop string 10 | if n%3 == 0 { 11 | raindrop += "Pling" 12 | } 13 | 14 | if n%5 == 0 { 15 | raindrop += "Plang" 16 | } 17 | 18 | if n%7 == 0 { 19 | raindrop += "Plong" 20 | } 21 | 22 | if raindrop == "" { 23 | raindrop += strconv.Itoa(n) 24 | } 25 | 26 | return raindrop 27 | } 28 | -------------------------------------------------------------------------------- /track/raindrops/solutions/3/solution.go: -------------------------------------------------------------------------------- 1 | package raindrops 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | func Convert(num int) string { 9 | result := strings.Builder{} 10 | if num%3 == 0 { 11 | result.WriteString("Pling") 12 | } 13 | if num%5 == 0 { 14 | result.WriteString("Plang") 15 | } 16 | if num%7 == 0 { 17 | result.WriteString("Plong") 18 | } 19 | if result.Len() == 0 { 20 | result.WriteString(strconv.Itoa(num)) 21 | } 22 | return result.String() 23 | } 24 | -------------------------------------------------------------------------------- /track/raindrops/solutions/4/solution.go: -------------------------------------------------------------------------------- 1 | // Package raindrops is used to transform a number into the lovely sound of rain 2 | package raindrops 3 | 4 | import "fmt" 5 | 6 | // Convert converts an integer into a raindrop-speak string 7 | func Convert(num int) string { 8 | convertedString := "" 9 | 10 | if num%3 == 0 { 11 | convertedString = convertedString + "Pling" 12 | } 13 | 14 | if num%5 == 0 { 15 | convertedString = convertedString + "Plang" 16 | } 17 | 18 | if num%7 == 0 { 19 | convertedString = convertedString + "Plong" 20 | } 21 | 22 | if convertedString == "" { 23 | return fmt.Sprintf("%d", num) 24 | } 25 | 26 | return convertedString 27 | } 28 | -------------------------------------------------------------------------------- /track/raindrops/solutions/5/solution.go: -------------------------------------------------------------------------------- 1 | package raindrops 2 | 3 | import "fmt" 4 | 5 | func Convert(number int) string { 6 | result := "" 7 | if number%3 == 0 { 8 | result += "Pling" 9 | } 10 | if number%5 == 0 { 11 | result += "Plang" 12 | } 13 | if number%7 == 0 { 14 | result += "Plong" 15 | } 16 | if len(result) == 0 { 17 | result = fmt.Sprintf("%v", number) 18 | } 19 | return result 20 | } 21 | -------------------------------------------------------------------------------- /track/raindrops/solutions/6/solution.go: -------------------------------------------------------------------------------- 1 | // Package raindrops convert a number to a string, the contents of which depend on the number's factors. 2 | package raindrops 3 | 4 | import "strconv" 5 | 6 | // Convert converts a number to a string, the contents of which depend on the number's factors. 7 | func Convert(i int) string { 8 | var result string 9 | for n := 1; n <= i; n++ { 10 | if i%n == 0 { 11 | switch n { 12 | case 3: 13 | result = result + "Pling" 14 | case 5: 15 | result = result + "Plang" 16 | case 7: 17 | result = result + "Plong" 18 | } 19 | } 20 | } 21 | if result == "" { 22 | return strconv.Itoa(i) 23 | } 24 | return result 25 | } 26 | -------------------------------------------------------------------------------- /track/raindrops/solutions/7/solution.go: -------------------------------------------------------------------------------- 1 | // Package raindrops ... . 2 | package raindrops 3 | 4 | import ( 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | var m = map[int]string{ 10 | 3: "Pling", 11 | 5: "Plang", 12 | 7: "Plong", 13 | } 14 | 15 | // Convert ... . 16 | func Convert(num int) string { 17 | var out strings.Builder 18 | 19 | for k, v := range m { 20 | if num%k == 0 { 21 | out.WriteString(v) 22 | } 23 | } 24 | 25 | if out.Len() == 0 { 26 | out.WriteString(strconv.Itoa(num)) 27 | } 28 | 29 | return out.String() 30 | } 31 | -------------------------------------------------------------------------------- /track/raindrops/solutions/8/solution.go: -------------------------------------------------------------------------------- 1 | // Package raindrops convert a number to a string, the contents of which depend on the number's factors. 2 | package raindrops 3 | 4 | import "strconv" 5 | 6 | // Convert converts a number to a string, the contents of which depend on the number's factors. 7 | func Convert(i int) string { 8 | var result string 9 | for n := 1; n <= 7; n++ { 10 | if i%n == 0 { 11 | switch n { 12 | case 3: 13 | result = result + "Pling" 14 | case 5: 15 | result = result + "Plang" 16 | case 7: 17 | result = result + "Plong" 18 | } 19 | } 20 | } 21 | if result == "" { 22 | return strconv.Itoa(i) 23 | } 24 | return result 25 | } 26 | -------------------------------------------------------------------------------- /track/raindrops/solutions/9/solution.go: -------------------------------------------------------------------------------- 1 | // Package raindrops convert a number to a string, the contents of which depend on the number's factors. 2 | package raindrops 3 | 4 | import "strconv" 5 | 6 | // Convert converts a number to a string, the contents of which depend on the number's factors. 7 | func Convert(i int) string { 8 | var result string 9 | for n := 3; n <= 7; n++ { 10 | if i%n == 0 { 11 | switch n { 12 | case 3: 13 | result = result + "Pling" 14 | case 5: 15 | result = result + "Plang" 16 | case 7: 17 | result = result + "Plong" 18 | } 19 | } 20 | } 21 | if result == "" { 22 | return strconv.Itoa(i) 23 | } 24 | return result 25 | } 26 | -------------------------------------------------------------------------------- /track/raindrops/tpl/all-cases.md: -------------------------------------------------------------------------------- 1 | - Although this solution passes the tests, it's a little too tightly coupled to the specific cases. If you imagine what changes you'd have to make to add a new case, you can see this code would rapidly become difficult to maintain. Can you find a way to make it more flexible, but still simple? (Hint: you can append to strings using the `+=` operator.) 2 | -------------------------------------------------------------------------------- /track/raindrops/tpl/extensive-for-loop.md: -------------------------------------------------------------------------------- 1 | - I see you are using a `for` loop that is doing more than really necessary. Can you improve on that? Try dealing only with the cases `3`, `5` and `7`. 2 | -------------------------------------------------------------------------------- /track/raindrops/tpl/fmt-print.md: -------------------------------------------------------------------------------- 1 | - I see you are using `%s` to concatenate the strings. In this case it would probably make most sense to use the `+=` operator which is also faster. 2 | -------------------------------------------------------------------------------- /track/raindrops/tpl/itoa.md: -------------------------------------------------------------------------------- 1 | - `strconv.Itoa` is the idiomatic way to transform an `int` to `string`, and it's very efficient. (Why is this faster than using `fmt`? See if you can guess.) 2 | -------------------------------------------------------------------------------- /track/raindrops/tpl/loop-map.md: -------------------------------------------------------------------------------- 1 | - Using a map is a perfectly natural idea in this kind of situation. However, be careful: because maps are implemented as hash tables, the order of their keys is not guaranteed. In fact, when you iterate over the keys of a map, you will get them in a different, random order each time. So in this situation, a map is not the right choice (try a slice instead). See the `Iteration order` section of this article for more information: [Go maps in action](https://blog.golang.org/go-maps-in-action). 2 | -------------------------------------------------------------------------------- /track/raindrops/tpl/many-loops.md: -------------------------------------------------------------------------------- 1 | - Programming is often about finding the simplest working solution. This solution uses multiple loops. Can you see if you can solve it with fewer loops, or none at all? 2 | -------------------------------------------------------------------------------- /track/raindrops/tpl/plus-equal.md: -------------------------------------------------------------------------------- 1 | - Did you know you can append strings in Go using a syntax like this: `speech += "Plang"`? 2 | -------------------------------------------------------------------------------- /track/raindrops/tpl/reg.go: -------------------------------------------------------------------------------- 1 | //go:generate go-bindata -ignore=\.go -pkg=tpl -o=bindata.go ./... 2 | 3 | package tpl 4 | 5 | import "github.com/exercism/exalysis/gtpl" 6 | 7 | // Templates to be used in the response of suggester 8 | var ( 9 | ManyLoops = gtpl.NewStringTemplate("many-loops.md", MustAsset) 10 | StringsBuilder = gtpl.NewFormatTemplate("strings-builder.md", MustAsset) 11 | PlusEqual = gtpl.NewStringTemplate("plus-equal.md", MustAsset) 12 | Itoa = gtpl.NewStringTemplate("itoa.md", MustAsset) 13 | ExtensiveFor = gtpl.NewStringTemplate("extensive-for-loop.md", MustAsset) 14 | LoopMap = gtpl.NewStringTemplate("loop-map.md", MustAsset) 15 | RemoveExtraBool = gtpl.NewFormatTemplate("remove-extra-bool.md", MustAsset) 16 | FmtPrint = gtpl.NewFormatTemplate("fmt-print.md", MustAsset) 17 | AllCases = gtpl.NewStringTemplate("all-cases.md", MustAsset) 18 | ) 19 | -------------------------------------------------------------------------------- /track/raindrops/tpl/remove-extra-bool.md: -------------------------------------------------------------------------------- 1 | - You are using an extra variable `%s` to check whether or not any of the cases applied. You could instead check if the string is still empty, saving yourself a variable. 2 | -------------------------------------------------------------------------------- /track/raindrops/tpl/strings-builder.md: -------------------------------------------------------------------------------- 1 | - I see you are using a `%s` here. That works, but it's probably overkill for this exercise. The string append operator `+=` is faster and uses less memory. You can run the benchmarks as described in the exercise instructions, and check that for yourself. 2 | -------------------------------------------------------------------------------- /track/scrabble/scrabble.go: -------------------------------------------------------------------------------- 1 | package scrabble 2 | 3 | import ( 4 | "go/ast" 5 | "reflect" 6 | 7 | "github.com/exercism/exalysis/extypes" 8 | "github.com/exercism/exalysis/gtpl" 9 | "github.com/exercism/exalysis/track/scrabble/tpl" 10 | "github.com/tehsphinx/astrav" 11 | ) 12 | 13 | // Suggest builds suggestions for the exercise solution 14 | func Suggest(pkg *astrav.Package, r *extypes.Response) { 15 | addSpeedComment = getAddSpeedComment() 16 | 17 | for _, tf := range exFuncs { 18 | tf(pkg, r) 19 | } 20 | } 21 | 22 | var exFuncs = []extypes.SuggestionFunc{ 23 | testGoRoutine, 24 | testRegex, 25 | testMapInFunc, 26 | testFlattenMap, 27 | testMapRuneInt, 28 | testSliceRuneConv, 29 | testToLowerUpper("strings.ToLower"), 30 | testToLowerUpper("strings.ToUpper"), 31 | testTrySwitch, 32 | testIfElseToSwitch, 33 | testRuneLoop, 34 | } 35 | 36 | func testSliceRuneConv(pkg *astrav.Package, r *extypes.Response) { 37 | calls := pkg.FindByNodeType(astrav.NodeTypeCallExpr) 38 | for _, call := range calls { 39 | if reflect.TypeOf(call.(*astrav.CallExpr).Fun).String() != "*ast.ArrayType" { 40 | continue 41 | } 42 | 43 | if call.(*astrav.CallExpr).NodeName() == "[]rune" { 44 | r.AppendImprovementTpl(tpl.SliceRuneConv) 45 | } 46 | } 47 | } 48 | 49 | func testRegex(pkg *astrav.Package, r *extypes.Response) { 50 | rgx := pkg.FindFirstByName("Score").FindFirstByName("regexp.MustCompile") 51 | if rgx == nil { 52 | rgx = pkg.FindFirstByName("Score").FindFirstByName("regexp.Compile") 53 | } 54 | 55 | if rgx != nil && 56 | rgx.(*astrav.SelectorExpr).X != nil && 57 | rgx.(*astrav.SelectorExpr).X.(*ast.Ident).Name == "regexp" { 58 | 59 | lit := rgx.Parent().ChildByNodeType(astrav.NodeTypeBasicLit) 60 | if lit != nil && lit.IsValueType("string") { 61 | // is a static regex 62 | r.AppendTodoTpl(tpl.Regex) 63 | r.AppendCommentTpl(tpl.Challenge) 64 | r.AppendOutro(gtpl.Regex) 65 | addSpeedComment(r) 66 | } 67 | } 68 | } 69 | 70 | func testToLowerUpper(fnName string) extypes.SuggestionFunc { 71 | return func(pkg *astrav.Package, r *extypes.Response) { 72 | if r.HasSuggestion(tpl.Regex) { 73 | return 74 | } 75 | 76 | fns := pkg.FindByName(fnName) 77 | for _, fn := range fns { 78 | if _, ok := fn.(*astrav.SelectorExpr); !ok { 79 | continue 80 | } 81 | addSpeedComment(r) 82 | 83 | fName := fnName[8:] 84 | if fn.NextParentByType(astrav.NodeTypeBlockStmt).IsContainedByType(astrav.NodeTypeRangeStmt) { 85 | r.AppendImprovementTpl(tpl.UnicodeLoop.Format(fName)) 86 | } else { 87 | r.AppendImprovementTpl(tpl.Unicode.Format(fName)) 88 | } 89 | } 90 | } 91 | } 92 | 93 | func testFlattenMap(pkg *astrav.Package, r *extypes.Response) { 94 | entryFn := pkg.FindFirstByName("Score") 95 | loopCount := len(entryFn.FindNodeTypeInCallTree(astrav.NodeTypeForStmt)) 96 | loopCount += len(entryFn.FindNodeTypeInCallTree(astrav.NodeTypeRangeStmt)) 97 | if 1 < loopCount { 98 | addSpeedComment(r) 99 | r.AppendImprovementTpl(tpl.FlattenMap) 100 | } 101 | } 102 | 103 | func testMapRuneInt(pkg *astrav.Package, r *extypes.Response) { 104 | if r.HasSuggestion(tpl.FlattenMap) { 105 | return 106 | } 107 | if len(pkg.FindByValueType("map[string]int")) != 0 { 108 | addSpeedComment(r) 109 | r.AppendImprovementTpl(tpl.MapRune.Format("map[string]int")) 110 | return 111 | } 112 | if len(pkg.FindByValueType("map[int]string")) != 0 { 113 | addSpeedComment(r) 114 | r.AppendImprovementTpl(tpl.MapRune.Format("map[int]string")) 115 | return 116 | } 117 | } 118 | 119 | func testTrySwitch(pkg *astrav.Package, r *extypes.Response) { 120 | if r.HasSuggestion(tpl.FlattenMap) { 121 | return 122 | } 123 | if len(pkg.FindByValueType("map[rune]int")) != 0 { 124 | addSpeedComment(r) 125 | r.AppendCommentTpl(tpl.TrySwitch) 126 | return 127 | } 128 | if len(pkg.FindByValueType("map[string]int")) != 0 { 129 | addSpeedComment(r) 130 | r.AppendCommentTpl(tpl.TrySwitch) 131 | return 132 | } 133 | if len(pkg.FindByValueType("map[int]string")) != 0 { 134 | addSpeedComment(r) 135 | r.AppendCommentTpl(tpl.TrySwitch) 136 | return 137 | } 138 | } 139 | 140 | func testRuneLoop(pkg *astrav.Package, r *extypes.Response) { 141 | ranges := pkg.FindFirstByName("Score").FindByNodeType(astrav.NodeTypeRangeStmt) 142 | for _, rng := range ranges { 143 | l := rng.(*astrav.RangeStmt) 144 | if l.Value() == nil { 145 | if l.Key() != nil { 146 | r.AppendImprovementTpl(tpl.LoopRuneNotByte) 147 | } 148 | } else { 149 | var isByte bool 150 | for _, ident := range rng.FindIdentByName(l.Value().(*astrav.Ident).Name) { 151 | if ident.IsValueType("byte") { 152 | isByte = true 153 | } 154 | } 155 | if isByte { 156 | r.AppendImprovementTpl(tpl.LoopRuneNotByte) 157 | } 158 | } 159 | 160 | if rng.FindByName("string") != nil && 161 | !r.HasSuggestion(tpl.MapRune) && 162 | !r.HasSuggestion(tpl.FlattenMap) { 163 | 164 | r.AppendImprovementTpl(tpl.TypeConversion) 165 | return 166 | } 167 | } 168 | } 169 | 170 | func testGoRoutine(pkg *astrav.Package, r *extypes.Response) { 171 | goStmts := pkg.FindByNodeType(astrav.NodeTypeGoStmt) 172 | if len(goStmts) != 0 { 173 | addSpeedComment(r) 174 | r.AppendTodoTpl(tpl.GoRoutines) 175 | } 176 | } 177 | 178 | func testIfElseToSwitch(pkg *astrav.Package, r *extypes.Response) { 179 | ranges := pkg.FindFirstByName("Score").FindByNodeType(astrav.NodeTypeRangeStmt) 180 | for _, rng := range ranges { 181 | ifs := rng.FindByNodeType(astrav.NodeTypeIfStmt) 182 | if 5 < len(ifs) { 183 | r.AppendTodoTpl(tpl.IfsToSwitch) 184 | return 185 | } 186 | } 187 | } 188 | 189 | func testMapInFunc(pkg *astrav.Package, r *extypes.Response) { 190 | fn := pkg.FindFirstByName("Score") 191 | maps := fn.FindMaps() 192 | 193 | var hasMapDef bool 194 | for _, m := range maps { 195 | if !m.IsNodeType(astrav.NodeTypeIdent) { 196 | hasMapDef = true 197 | } 198 | } 199 | if hasMapDef { 200 | addSpeedComment(r) 201 | r.AppendTodoTpl(tpl.MoveMap) 202 | } 203 | } 204 | 205 | var addSpeedComment func(r *extypes.Response) 206 | 207 | func getAddSpeedComment() func(r *extypes.Response) { 208 | var speedCommentAdded bool 209 | return func(r *extypes.Response) { 210 | if speedCommentAdded { 211 | return 212 | } 213 | speedCommentAdded = true 214 | r.AppendOutro(gtpl.Benchmarking) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /track/scrabble/scrabble_test.go: -------------------------------------------------------------------------------- 1 | package scrabble 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/exercism/exalysis/extypes" 8 | "github.com/exercism/exalysis/gtpl" 9 | "github.com/exercism/exalysis/testhelper" 10 | "github.com/exercism/exalysis/track/scrabble/tpl" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var suggestTests = []struct { 15 | path string 16 | suggestion gtpl.Template 17 | expected bool 18 | }{ 19 | {path: "./solutions/1", suggestion: tpl.Unicode, expected: true}, 20 | {path: "./solutions/3", suggestion: tpl.Unicode, expected: true}, 21 | {path: "./solutions/2", suggestion: tpl.UnicodeLoop, expected: true}, 22 | {path: "./solutions/3", suggestion: tpl.UnicodeLoop, expected: false}, 23 | {path: "./solutions/2", suggestion: tpl.MoveMap, expected: true}, 24 | {path: "./solutions/3", suggestion: tpl.MoveMap, expected: false}, 25 | {path: "./solutions/4", suggestion: tpl.MoveMap, expected: true}, 26 | {path: "./solutions/2", suggestion: tpl.MapRune, expected: true}, 27 | {path: "./solutions/2", suggestion: tpl.TrySwitch, expected: true}, 28 | {path: "./solutions/5", suggestion: tpl.FlattenMap, expected: true}, 29 | {path: "./solutions/5", suggestion: tpl.TrySwitch, expected: false}, 30 | {path: "./solutions/5", suggestion: tpl.TypeConversion, expected: false}, 31 | {path: "./solutions/6", suggestion: tpl.Unicode, expected: false}, 32 | {path: "./solutions/6", suggestion: tpl.UnicodeLoop, expected: false}, 33 | {path: "./solutions/6", suggestion: tpl.Regex, expected: true}, 34 | {path: "./solutions/6", suggestion: tpl.Challenge, expected: true}, 35 | {path: "./solutions/7", suggestion: tpl.FlattenMap, expected: false}, 36 | {path: "./solutions/8", suggestion: tpl.SliceRuneConv, expected: true}, 37 | {path: "./solutions/8", suggestion: tpl.TrySwitch, expected: false}, 38 | {path: "./solutions/9", suggestion: tpl.FlattenMap, expected: true}, 39 | } 40 | 41 | func Test_Suggest(t *testing.T) { 42 | for _, test := range suggestTests { 43 | _, pkg, err := testhelper.LoadExample(test.path, "scrabble") 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | r := extypes.NewResponse() 49 | Suggest(pkg, r) 50 | 51 | assert.Equal(t, test.expected, r.HasSuggestion(test.suggestion), 52 | fmt.Sprintf("test failed: %+v", test)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /track/scrabble/solutions/1/solution.go: -------------------------------------------------------------------------------- 1 | package scrabble 2 | 3 | import "strings" 4 | 5 | func scoreLetter(char rune) int { 6 | switch char { 7 | case 'A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T': 8 | return 1 9 | case 'D', 'G': 10 | return 2 11 | case 'B', 'C', 'M', 'P': 12 | return 3 13 | case 'F', 'H', 'V', 'W', 'Y': 14 | return 4 15 | case 'K': 16 | return 5 17 | case 'J', 'X': 18 | return 8 19 | case 'Q', 'Z': 20 | return 10 21 | default: 22 | return 0 23 | } 24 | } 25 | 26 | // Score calculates scrabble score for the given word 27 | func Score(word string) int { 28 | word = strings.ToUpper(word) 29 | sum := 0 30 | for _, char := range word { 31 | sum += scoreLetter(char) 32 | } 33 | return sum 34 | } 35 | -------------------------------------------------------------------------------- /track/scrabble/solutions/2/solution.go: -------------------------------------------------------------------------------- 1 | //Package scrabble implements a scoring function for taking scrabble words and finding the associated points 2 | package scrabble 3 | 4 | import "strings" 5 | 6 | //Score returns an int based on the Scrabble score of the string that is passed in as a parameter 7 | func Score(input string) (scrabbleScore int) { 8 | scoreMap := map[string]int{ 9 | "A": 1, 10 | "B": 3, 11 | "C": 3, 12 | "D": 2, 13 | "E": 1, 14 | "F": 4, 15 | "G": 2, 16 | "H": 4, 17 | "I": 1, 18 | "J": 8, 19 | "K": 5, 20 | "L": 1, 21 | "M": 3, 22 | "N": 1, 23 | "O": 1, 24 | "P": 3, 25 | "Q": 10, 26 | "R": 1, 27 | "S": 1, 28 | "T": 1, 29 | "U": 1, 30 | "V": 4, 31 | "W": 4, 32 | "X": 8, 33 | "Y": 4, 34 | "Z": 10, 35 | } 36 | 37 | for _, scrabbleChar := range input { 38 | scrabbleScore += scoreMap[strings.ToUpper(string(scrabbleChar))] 39 | } 40 | 41 | return scrabbleScore 42 | } 43 | -------------------------------------------------------------------------------- /track/scrabble/solutions/3/solution.go: -------------------------------------------------------------------------------- 1 | package scrabble 2 | 3 | import "strings" 4 | 5 | var pointsByLetter = map[rune]int{ 6 | 'a': 1, 7 | 'b': 3, 8 | 'c': 3, 9 | 'd': 2, 10 | 'e': 1, 11 | 'f': 4, 12 | 'g': 2, 13 | 'h': 4, 14 | 'i': 1, 15 | 'j': 8, 16 | 'k': 5, 17 | 'l': 1, 18 | 'm': 3, 19 | 'n': 1, 20 | 'o': 1, 21 | 'p': 3, 22 | 'q': 10, 23 | 'r': 1, 24 | 's': 1, 25 | 't': 1, 26 | 'u': 1, 27 | 'v': 4, 28 | 'w': 4, 29 | 'x': 8, 30 | 'y': 4, 31 | 'z': 10, 32 | } 33 | 34 | //Score calculates score for a scrabble word 35 | func Score(word string) int { 36 | var score int 37 | 38 | for _, c := range strings.ToLower(word) { 39 | score += pointsByLetter[c] 40 | } 41 | 42 | return score 43 | } 44 | -------------------------------------------------------------------------------- /track/scrabble/solutions/4/solution.go: -------------------------------------------------------------------------------- 1 | package scrabble 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Score computes the score for that word 8 | func Score(input string) int { 9 | values := map[byte](int){ 10 | 'A': 1, 'E': 1, 'I': 1, 'O': 1, 'U': 1, 'L': 1, 'N': 1, 'R': 1, 'S': 1, 'T': 1, 11 | 'D': 2, 'G': 2, 12 | 'B': 3, 'C': 3, 'M': 3, 'P': 3, 13 | 'F': 4, 'H': 4, 'V': 4, 'W': 4, 'Y': 4, 14 | 'K': 5, 15 | 'J': 8, 'X': 8, 16 | 'Q': 10, 'Z': 10, 17 | } 18 | score := 0 19 | input = strings.ToUpper(input) 20 | for i := range input { 21 | score += values[input[i]] 22 | } 23 | return score 24 | 25 | } 26 | -------------------------------------------------------------------------------- /track/scrabble/solutions/5/solution.go: -------------------------------------------------------------------------------- 1 | package scrabble 2 | 3 | import "strings" 4 | 5 | // Score Compute the scrabble score for that input string. 6 | func Score(input string) int { 7 | score := 0 8 | scoreMap := map[string]int{ 9 | "AEIOULNRSTaeioulnrst": 1, 10 | "DGdg": 2, 11 | "BCMPbcmp": 3, 12 | "FHVWYfhvwy": 4, 13 | "Kk": 5, 14 | "JXjx": 8, 15 | "QZqz": 10, 16 | } 17 | 18 | for _, v := range input { 19 | for key, value := range scoreMap { 20 | if strings.Contains(key, string(v)) { 21 | score += value 22 | break 23 | } 24 | } 25 | } 26 | 27 | return score 28 | } 29 | -------------------------------------------------------------------------------- /track/scrabble/solutions/6/solution.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package scrabble used to calculate score for word 3 | */ 4 | package scrabble 5 | 6 | import ( 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | //Score used to calculate score for input word 12 | func Score(input string) int { 13 | input = strings.ToLower(input) 14 | oneReg := regexp.MustCompile("[aeioulnrst]") 15 | twoReg := regexp.MustCompile("[dg]") 16 | threeReg := regexp.MustCompile("[bcmp]") 17 | thourReg := regexp.MustCompile("[fhvwy]") 18 | fiveReg := regexp.MustCompile("[k]") 19 | eightReg := regexp.MustCompile("[jx]") 20 | tenReg := regexp.MustCompile("[qz]") 21 | return len(oneReg.FindAllString(input, -1)) + 22 | len(twoReg.FindAllString(input, -1))*2 + 23 | len(threeReg.FindAllString(input, -1))*3 + 24 | len(thourReg.FindAllString(input, -1))*4 + 25 | len(fiveReg.FindAllString(input, -1))*5 + 26 | len(eightReg.FindAllString(input, -1))*8 + 27 | len(tenReg.FindAllString(input, -1))*10 28 | } 29 | -------------------------------------------------------------------------------- /track/scrabble/solutions/7/solution.go: -------------------------------------------------------------------------------- 1 | package scrabble 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | var letterScores [26]int 8 | 9 | func setScores(scoreStr string, score int) { 10 | for _, letter := range scoreStr { 11 | letterScores[letter-'A'] = score 12 | } 13 | } 14 | 15 | func init() { 16 | setScores("AEIOULNRST", 1) 17 | setScores("DG", 2) 18 | setScores("BCMP", 3) 19 | setScores("FHWVY", 4) 20 | setScores("K", 5) 21 | setScores("JX", 8) 22 | setScores("QZ", 10) 23 | } 24 | 25 | // Score computes a scrabble score for the given word. 26 | func Score(word string) int { 27 | result := 0 28 | for _, letter := range strings.ToUpper(word) { 29 | result += letterScores[letter-'A'] 30 | } 31 | return result 32 | } 33 | -------------------------------------------------------------------------------- /track/scrabble/solutions/8/solution.go: -------------------------------------------------------------------------------- 1 | // Package scrabble implements the Exercism Scrabble Score solution. 2 | package scrabble 3 | 4 | import ( 5 | "log" 6 | "strings" 7 | ) 8 | 9 | /* 10 | Letter Value 11 | A, E, I, O, U, L, N, R, S, T 1 12 | D, G 2 13 | B, C, M, P 3 14 | F, H, V, W, Y 4 15 | K 5 16 | J, X 8 17 | Q, Z 10 18 | */ 19 | var letterValues = []int{ 20 | 1, 3, 3, 2, 1, 4, 2, // A-G 21 | 4, 1, 8, 5, 1, 3, 1, 1, 3, // H-P 22 | 10, 1, 1, // Q-S 23 | 1, 1, 4, // T-V 24 | 4, 8, 4, 10, // W-Z 25 | } 26 | 27 | // Define bounds checking values. 28 | var asciiA = 'A' 29 | var asciiZ = 'Z' 30 | 31 | // Score returns the Scrabble score of a provided word. 32 | func Score(input string) (score int) { 33 | r := []rune(strings.ToUpper(input)) 34 | 35 | for _, letter := range r { 36 | // Score can only evaluate ASCII characters. 37 | if letter < asciiA || letter > asciiZ { 38 | log.Printf("Invalid letter: %s\n", string(letter)) 39 | return 40 | } 41 | 42 | score += letterValues[letter-asciiA] 43 | } 44 | 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /track/scrabble/solutions/9/solution.go: -------------------------------------------------------------------------------- 1 | package scrabble 2 | 3 | import "strings" 4 | 5 | var scoreMap = map[string]int{ 6 | "AEIOULNRSTaeioulnrst": 1, 7 | "DGdg": 2, 8 | "BCMPbcmp": 3, 9 | "FHVWYfhvwy": 4, 10 | "Kk": 5, 11 | "JXjx": 8, 12 | "QZqz": 10, 13 | } 14 | 15 | // Score Compute the scrabble score for that input string. 16 | func Score(input string) int { 17 | score := 0 18 | 19 | for _, r := range input { 20 | score += getVal(r) 21 | } 22 | 23 | return score 24 | } 25 | 26 | func getVal(r rune) int { 27 | for key, value := range scoreMap { 28 | if strings.Contains(key, string(r)) { 29 | return value 30 | } 31 | } 32 | 33 | return 0 34 | } 35 | -------------------------------------------------------------------------------- /track/scrabble/tpl/challenge.md: -------------------------------------------------------------------------------- 1 | - Challenge: You know how to run and interpret the benchmarks now. Can you think of other ways to solve this that might be (a lot) faster? 2 | -------------------------------------------------------------------------------- /track/scrabble/tpl/flatten-map.md: -------------------------------------------------------------------------------- 1 | - The way the map is created you currently need two loops. Have a look at the benchmarks, rearrange the map for direct lookup, and check the speed again. Your best choice might be a `map[rune]int`. 2 | -------------------------------------------------------------------------------- /track/scrabble/tpl/go-routines.md: -------------------------------------------------------------------------------- 1 | - I see you are using goroutines! That's cool; you probably want to make it super fast. Did you check the benchmarks to see how much faster it is? The answer may surprise you. 2 | -------------------------------------------------------------------------------- /track/scrabble/tpl/ifs-to-switch.md: -------------------------------------------------------------------------------- 1 | - Try a `switch` statement instead of several `if` statements. 2 | -------------------------------------------------------------------------------- /track/scrabble/tpl/loop-rune-not-byte.md: -------------------------------------------------------------------------------- 1 | - Iterating over a string produces runes. A rune represents a Unicode character and can consist of **multiple** bytes. Try using runes instead of bytes. 2 | -------------------------------------------------------------------------------- /track/scrabble/tpl/maprune.md: -------------------------------------------------------------------------------- 1 | - You could use a `map[rune]int` instead of `%s` for direct lookup of the score value for a rune: no type conversion needed! A rune literal uses single quotes, like this: `'a'`. 2 | -------------------------------------------------------------------------------- /track/scrabble/tpl/move-map.md: -------------------------------------------------------------------------------- 1 | - You're creating the map inside the `Score` function. Since the map never changes, try moving this code outside the function. That way, it's only initialized once, when the package is loaded. 2 | -------------------------------------------------------------------------------- /track/scrabble/tpl/reg.go: -------------------------------------------------------------------------------- 1 | //go:generate go-bindata -ignore=\.go -pkg=tpl -o=bindata.go ./... 2 | 3 | package tpl 4 | 5 | import "github.com/exercism/exalysis/gtpl" 6 | 7 | // Templates to be used in the response of suggester 8 | var ( 9 | Unicode = gtpl.NewFormatTemplate("unicode.md", MustAsset) 10 | UnicodeLoop = gtpl.NewFormatTemplate("unicode-loop.md", MustAsset) 11 | MapRune = gtpl.NewFormatTemplate("maprune.md", MustAsset) 12 | FlattenMap = gtpl.NewFormatTemplate("flatten-map.md", MustAsset) 13 | TypeConversion = gtpl.NewStringTemplate("type-conversion.md", MustAsset) 14 | TrySwitch = gtpl.NewStringTemplate("try-switch.md", MustAsset) 15 | LoopRuneNotByte = gtpl.NewStringTemplate("loop-rune-not-byte.md", MustAsset) 16 | IfsToSwitch = gtpl.NewStringTemplate("ifs-to-switch.md", MustAsset) 17 | GoRoutines = gtpl.NewStringTemplate("go-routines.md", MustAsset) 18 | MoveMap = gtpl.NewStringTemplate("move-map.md", MustAsset) 19 | Regex = gtpl.NewStringTemplate("regex.md", MustAsset) 20 | Challenge = gtpl.NewStringTemplate("challenge.md", MustAsset) 21 | SliceRuneConv = gtpl.NewStringTemplate("slice-rune-conversion.md", MustAsset) 22 | ) 23 | -------------------------------------------------------------------------------- /track/scrabble/tpl/regex.md: -------------------------------------------------------------------------------- 1 | - You are compiling a static regular expression in a function that will most likely be called several times. Could you have a look at the section about regular expressions below and see if you can avoid this? 2 | -------------------------------------------------------------------------------- /track/scrabble/tpl/slice-rune-conversion.md: -------------------------------------------------------------------------------- 1 | - I noticed you're converting a string into a `[]rune` to then loop over it. Actually, using `range` with a string can return runes directly: 2 | ```go 3 | for i, r := range word { 4 | // r is a rune 5 | } 6 | ``` 7 | -------------------------------------------------------------------------------- /track/scrabble/tpl/try-switch.md: -------------------------------------------------------------------------------- 1 | - If you'd like to try it, using a `switch` instead of a `map` will increase speed significantly in this particular case. Note: `switch` isn't necessarily faster than a map in all cases. See: [Switch vs. Map](https://hashrocket.com/blog/posts/switch-vs-map-which-is-the-better-way-to-branch-in-go) 2 | -------------------------------------------------------------------------------- /track/scrabble/tpl/type-conversion.md: -------------------------------------------------------------------------------- 1 | - In order to avoid an unnecessary type conversion, you could work with type `rune` instead of `string` in the `for` loop. A rune literal uses single quotes, like this: `'A'`. 2 | -------------------------------------------------------------------------------- /track/scrabble/tpl/unicode-loop.md: -------------------------------------------------------------------------------- 1 | - You could try using `unicode.%[1]s` to replace `strings.%[1]s` inside the loop to increase speed. 2 | -------------------------------------------------------------------------------- /track/scrabble/tpl/unicode.md: -------------------------------------------------------------------------------- 1 | - Where performance is important, think about using `unicode.%[1]s` inside the `for` loop instead of `strings.%[1]s` before the loop, because it's a little faster. Usually, however, readability is more important than performance, so what you have is just fine. 2 | --------------------------------------------------------------------------------