├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── _extra ├── day-1.log ├── day-2.log ├── day-3.log ├── day-4.log ├── http.log.gz ├── nlp.go ├── rtb.go ├── sherlock.txt ├── sites_time.go ├── stemmer.go ├── taxi-sha256.zip ├── taxi_check.go └── tokenize_cases.toml ├── banner └── banner.go ├── counter └── counter.go ├── div └── div.go ├── empty └── empty.go ├── freq ├── freq.go └── sherlock.txt ├── game └── game.go ├── github └── github.go ├── go.mod ├── go.sum ├── go_chan ├── go_chan.go └── sleep_sort.sh ├── hw └── hw.go ├── nlp ├── LICENSE.txt ├── README.md ├── cmd │ └── nlpd │ │ ├── main.go │ │ └── main_test.go ├── doc.go ├── example_test.go ├── go.mod ├── go.sum ├── nlp.go ├── nlp_test.go ├── stemmer │ ├── stemmer.go │ └── stemmer_test.go └── testdata │ └── tokenize_cases.toml ├── payment └── payment.go ├── rtb └── rtb.go ├── select └── select.go ├── sha1 └── sha1.go ├── sites_time └── sites_time.go ├── slices └── slices.go └── taxi └── taxi_check.go /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.224.3/containers/go/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.16, 1.17, 1-bullseye, 1.16-bullseye, 1.17-bullseye, 1-buster, 1.16-buster, 1.17-buster 4 | ARG VARIANT="1.20-bullseye" 5 | FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT} 6 | 7 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 8 | ARG NODE_VERSION="none" 9 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 10 | 11 | # [Optional] Uncomment this section to install additional OS packages. 12 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 13 | && apt-get -y install --no-install-recommends \ 14 | graphviz 15 | 16 | # [Optional] Uncomment the next lines to use go get to install anything else you need 17 | USER vscode 18 | # VSCode tools 19 | RUN go install github.com/cweill/gotests/gotests@v1.6.0 20 | RUN go install github.com/fatih/gomodifytags@v1.16.0 21 | RUN go install github.com/josharian/impl@v1.1.0 22 | RUN go install github.com/haya14busa/goplay/cmd/goplay@v1.0.0 23 | RUN go install github.com/go-delve/delve/cmd/dlv@latest 24 | RUN go install honnef.co/go/tools/cmd/staticcheck@latest 25 | RUN go install golang.org/x/tools/gopls@latest 26 | 27 | # Benchmarking 28 | RUN go install github.com/rakyll/hey@latest 29 | RUN go install golang.org/x/perf/cmd/benchstat@latest 30 | 31 | # [Optional] Uncomment this line to install global node packages. 32 | # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 33 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Go", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | "args": { 6 | // Update the VARIANT arg to pick a version of Go: 1, 1.18, 1.17 7 | // Append -bullseye or -buster to pin to an OS version. 8 | // Use -bullseye variants on local arm64/Apple Silicon. 9 | "VARIANT": "1.20", 10 | // Options 11 | "NODE_VERSION": "lts/*" 12 | } 13 | }, 14 | "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], 15 | 16 | // Set *default* container specific settings.json values on container create. 17 | "settings": { 18 | "go.toolsManagement.checkForUpdates": "local", 19 | "go.useLanguageServer": true, 20 | "go.gopath": "/go" 21 | }, 22 | 23 | // Add the IDs of extensions you want installed when the container is created. 24 | "extensions": [ 25 | "GitHub.github-vscode-theme", 26 | "golang.Go" 27 | ], 28 | 29 | "features": { 30 | "ghcr.io/devcontainers/features/docker-in-docker:2.1.0": {} 31 | }, 32 | 33 | 34 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 35 | // "forwardPorts": [], 36 | 37 | // Use 'postCreateCommand' to run commands after the container is created. 38 | //"postCreateCommand": "bash .devcontainer/init.sh", 39 | 40 | // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 41 | "remoteUser": "vscode", 42 | // "onCreateCommand" : "echo PS1='\"$ \"' >> ~/.bashrc", //Set Terminal Prompt to $ 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.fontSize": 22, 3 | "editor.formatOnPaste": true, 4 | "editor.formatOnSave": true, 5 | "editor.lineNumbers": "on", 6 | "editor.minimap.enabled": false, 7 | "editor.smoothScrolling": true, 8 | "emmet.triggerExpansionOnTab": true, 9 | "explorer.openEditors.visible": 0, 10 | "files.autoSave": "afterDelay", 11 | "terminal.integrated.fontSize": 18, 12 | "workbench.colorTheme": "Visual Studio Dark", 13 | "workbench.fontAliasing": "antialiased", 14 | "workbench.statusBar.visible": true 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Practical Go for Developers 2 | 3 | This repo contains the material for the "Practical Go Foundations" class. 4 | The code & links are synced with the [online class](for Developers). 5 | 6 | This is an assorted collection of exercises for teaching, not a real Go project. 7 | 8 | ## Setting Up 9 | 10 | We highly recommend that you set up a local working environment and follow along with the videos. 11 | To setup a local environment install the following: 12 | - The Go SDK either from your package manager (`brew`, `apt`, `choco` ...) or from [here](https://go.dev/dl/) 13 | - `git` 14 | - And IDE such as [VSCode](https://code.visualstudio.com/) with [the Go extension](https://marketplace.visualstudio.com/items?itemName=golang.Go) or [GoLand](https://www.jetbrains.com/go/) 15 | 16 | However, if you want to jump right in, you can use GitHub codespaces with the course repository. 17 | To start a codespaces follow these steps: 18 | 19 | - Click on the green "<> Code" button 20 | - Select the "Codespaces" tab 21 | - Click on the green "Create codespace on main" button 22 | 23 | After a while you should have a new tab with Visual Studio Code already set up. 24 | You can read more about codespaces [here](https://github.com/features/codespaces). 25 | 26 | --- 27 | 28 | ## Day 1 29 | 30 | ### Agenda 31 | 32 | - Strings & formatted output 33 | - What is a string? 34 | - Unicode basics 35 | - Using fmt package for formatted output 36 | - Calling REST APIs 37 | - Making HTTP calls with net/http 38 | - Defining structs 39 | - Serializing JSON 40 | - Working with files 41 | - Handling errors 42 | - Using defer to manage resources 43 | - Working with io.Reader & io.Writer interfaces 44 | 45 | ### Code 46 | 47 | - [hw.go](hw/hw.go) - Hello World 48 | - `GOOS=drawin go build` (also `GOARCH`) 49 | - [banner.go](banner/banner.go) - Strings & printing 50 | - [github.go](github/github.go) - Calling REST APIs 51 | - [sha1.go](sha1/sha1.go) - Working with `io.Reader` & `io.Writer` 52 | 53 | [Terminal Log](_extra/day-1.log) 54 | 55 | 56 | ### Links 57 | 58 | - [HTTP status cats](https://http.cat/) 59 | - [errors](https://pkg.go.dev/errors/) package ([Go 1.13](https://go.dev/blog/go1.13-errors)) 60 | - [encoding/json](https://pkg.go.dev/encoding/json) 61 | - [net/http](https://pkg.go.dev/net/http) 62 | - [Let's talk about logging](https://dave.cheney.net/2015/11/05/lets-talk-about-logging) by Dave Cheney 63 | - Numbers 64 | - [math/big](https://pkg.go.dev/math/big/) - Big numbers 65 | - [Numeric types](https://go.dev/ref/spec#Numeric_types) 66 | - Strings 67 | - [Unicode table](https://unicode-table.com/) 68 | - [strings](https://pkg.go.dev/strings/) package - string utilities 69 | - [Go strings](https://go.dev/blog/strings) 70 | - [Annotated "Hello World"](https://www.353solutions.com/annotated-go) 71 | - [Effective Go](https://go.dev/doc/effective_go.html) - Read this! 72 | - [Go standard library](https://pkg.go.dev/) - official documentation 73 | - [A Tour of Go](https://do.dev/tour/) 74 | - Setting Up 75 | - [The Go SDK](https://go.dev/dl/) 76 | - [git](https://git-scm.com/) 77 | - IDE's: [Visual Studio Code](https://code.visualstudio.com/) + [Go extension](https://marketplace.visualstudio.com/items?itemName=golang.Go) or [Goland](https://www.jetbrains.com/go/) (paid) 78 | 79 | ### Data & Other 80 | 81 | - `G☺` 82 | - `♡` 83 | - [http.log.gz](_extra/http.log.gz) 84 | 85 | --- 86 | 87 | ## Day 2 88 | 89 | ### Agenda 90 | 91 | - Sorting 92 | - Working with slices 93 | - Writing methods 94 | - Understanding interfaces 95 | - Catching panics 96 | - The built-in recover function 97 | - Named return values 98 | - Processing text 99 | - Reading line by line with bufio.Scanner 100 | - Using regular expressions 101 | - Working with maps 102 | 103 | ### Code 104 | 105 | - [slices.go](slices/slices.go) - Working with slices 106 | - [game.go](game/game.go) - Structs, methods & interfaces 107 | - [empty.go](empty/empty.go) - The empty interface, type assertions 108 | - [div.go](div/div.go) - Catching panics 109 | - [freq.go](freq/freq.go) - Most common word (files, regular expressions, maps) 110 | 111 | [Terminal Log](_extra/day-2.log) 112 | 113 | ### Exercises 114 | 115 | - Read and understand the [sort package examples](https://pkg.go.dev/sort/#pkg-examples) 116 | - Implement `sortByDistance(players []Player, x, y int)` in `game.go` 117 | - Change `mostCommon` to return the most common `n` words (e.g. `func mostCommon(r io.Reader, n int) ([]string, error)`) 118 | 119 | ### Links 120 | 121 | - [regex101](https://regex101.com/) - Regular expression builder 122 | - [Go Proverbs](https://go-proverbs.github.io/) - Think about them ☺ 123 | - [sort examples](https://pkg.go.dev/sort/#pkg-examples) - Read and try to understand 124 | - [When to use generics](https://go.dev/blog/when-generics) 125 | - [Generics tutorial](https://go.dev/doc/tutorial/generics) 126 | - [Methods, interfaces & embedded types in Go](https://www.ardanlabs.com/blog/2014/05/methods-interfaces-and-embedded-types.html) 127 | - [Methods & Interfaces](https://go.dev/tour/methods/1) in the Go tour 128 | - Slices 129 | - [Slices](https://go.dev/blog/slices) & [Slice internals](https://go.dev/blog/go-slices-usage-and-internals) on the Go blog 130 | - [Slice tricks](https://github.com/golang/go/wiki/SliceTricks) 131 | - Error Handling 132 | - [Defer, Panic and Recover](https://go.dev/blog/defer-panic-and-recover) 133 | - [errors](https://pkg.go.dev/errors/) package ([Go 1.13](https://go.dev/blog/go1.13-errors)) 134 | - [pkg/errors](https://github.com/pkg/errors) 135 | 136 | 137 | ### Data & Other 138 | 139 | - [sherlock.txt](_extra/sherlock.txt) 140 | 141 | --- 142 | 143 | ## Day 3 144 | 145 | ### Agenda 146 | 147 | - Distributing work 148 | - Using goroutines & channels 149 | - Using the sync package to coordinate work 150 | - Timeouts & cancellation 151 | - Working with multiple channels using select 152 | - Using context for timeouts & cancellations 153 | - Standard library support for context 154 | 155 | ### Code 156 | 157 | - [go_chan.go](go_chan/go_chan.go) - Goroutines & channels 158 | - [sleep_sort.sh](go_chan/sleep_sort.sh) - Sleep sort in bash 159 | - [taxi_check.go](taxi/taxi_check.go) - Turn sequential code to parallel 160 | - [sites_time.go](sites_time/sites_time.go) - Using sync.WaitGroup 161 | - [payment.go](payment/payment.go) - Using sync.Once & sync.WaitGroup 162 | - [counter.go](counter/counter.go) - Using the race detector, sync.Mutex and sync/atomic 163 | - [select.go](select/select.go) - Using `select` 164 | - [rtb.go](rtb/rtb.go) - Using `context` for cancellations 165 | 166 | [Terminal Log](_extra/day-3.log) 167 | 168 | ### Exercise 169 | 170 | In `taxi_check.go` 171 | - Limit the number of goroutines to "n". Which "n" yields the best results? 172 | - Cancel all goroutines once there's an error or mismatch in signature 173 | 174 | ### Links 175 | 176 | - [The race detector](https://go.dev/doc/articles/race_detector) 177 | - [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md) 178 | - [errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) 179 | - [Data Race Patterns in Go](https://eng.uber.com/data-race-patterns-in-go/) 180 | - [Go Concurrency Patterns: Pipelines and cancellation](https://go.dev/blog/pipelines) 181 | - [Go Concurrency Patterns: Context](https://go.dev/blog/context) 182 | - [Curious Channels](https://dave.cheney.net/2013/04/30/curious-channels) 183 | - [The Behavior of Channels](https://www.ardanlabs.com/blog/2017/10/the-behavior-of-channels.html) 184 | - [Channel Semantics](https://www.353solutions.com/channel-semantics) 185 | - [Why are there nil channels in Go?](https://medium.com/justforfunc/why-are-there-nil-channels-in-go-9877cc0b2308) 186 | - [Amdahl's Law](https://en.wikipedia.org/wiki/Amdahl%27s_law) - Limits of concurrency 187 | - [Computer Latency at Human Scale](https://twitter.com/jordancurve/status/1108475342468120576/photo/1) 188 | - [Concurrency is not Parallelism](https://www.youtube.com/watch?v=cN_DpYBzKso) by Rob Pike 189 | - [Scheduling in Go](https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html) 190 | 191 | ### Data & Other 192 | 193 | - [rtb.go](_extra/rtb.go) 194 | - [site_times.go](_extra/site_time.go) 195 | - [taxi_check.go](_extra/taxi_check.go) 196 | - [taxi-sha256.zip](_extra/taxi-sha256.zip) 197 | 198 | --- 199 | 200 | ## Day 4 201 | 202 | ### Agenda 203 | 204 | - Testing your code 205 | - Working with the testing package 206 | - Using testify 207 | - Managing dependencies with go mod 208 | - Structuring your code 209 | - Writing sub-packages 210 | - Writing an HTTP server 211 | - Writing handlers 212 | - Using gorilla/mux for routing 213 | Adding metrics & logging 214 | - Using expvar for metrics 215 | - Using the log package and a look at user/zap 216 | - Configuration patterns 217 | - Reading environment variables and a look at external packages 218 | - Using the flag package for command line processing 219 | 220 | ### Code 221 | 222 | `nlp` project 223 | 224 |
225 | ├── go.mod - Project & dependencies
226 | ├── nlp.go - Package code
227 | ├── doc.go - Package level documentation
228 | ├── nlp_test.go - Test & benchmark file
229 | ├── example_test.go - Testable example
230 | ├── stemmer - Sub module
231 | │   ├── stemmer.go
232 | │   └── stemmer_test.go
233 | ├── testdata - Test data
234 | │      └── tokenize_cases.toml - Test cases
235 | └── cmd  - Executables
236 |     └── nlpd - HTTP server
237 |         ├── main.go
238 |         └── main_test.go
239 | 
240 | 241 | 242 | [Terminal Log](_extra/day-4.log) 243 | 244 | 245 | ### Links 246 | 247 | - Configuration 248 | - [envconfig](https://github.com/kelseyhightower/envconfig) 249 | - [viper](https://github.com/spf13/viper) & [cobra](https://github.com/spf13/cobra) 250 | - Logging 251 | - Built-in [log](https://pkg.go.dev/log/) 252 | - [uber/zap](https://pkg.go.dev/go.uber.org/zap) 253 | - [logrus](https://github.com/sirupsen/logrus) 254 | - [zerolog](https://github.com/rs/zerolog) 255 | - Metrics 256 | - Built-in [expvar](https://pkg.go.dev/expvar/) 257 | - [Open Telemetry](https://opentelemetry.io/) 258 | - [Prometheus](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus) 259 | - [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) 260 | - [Tutorial: Getting started with multi-module workspaces](https://go.dev/doc/tutorial/workspaces) 261 | - [Example Project Structure](https://github.com/ardanlabs/service) 262 | - [How to Write Go Code](https://go.dev/doc/code.html) 263 | - Documentation 264 | - [Godoc: documenting Go code](https://go.dev/blog/godoc) 265 | - [Testable examples in Go](https://go.dev/blog/examples) 266 | - [Go documentation tricks](https://godoc.org/github.com/fluhus/godoc-tricks) 267 | - [gob/doc.go](https://github.com/golang/go/blob/master/src/encoding/gob/doc.go) of the `gob` package. Generates [this documentation](https://pkg.go.dev/encoding/gob/) 268 | - `go install golang.org/x/pkgsite/cmd/pkgsite@9ffe8b928e4fbd3ff7dcf984254629a47f8b6e63` (require go 1.18) 269 | - `pkgsite -http=:8080` (open browser on http://localhost:8080/${module name}) 270 | - [Out Software Dependency Problem](https://research.swtch.com/deps) - Good read on dependencies by Russ Cox 271 | - Linters (static analysis) 272 | - [staticcheck](https://staticcheck.io/) 273 | - [gosec](https://github.com/securego/gosec) - Security oriented 274 | - [golang.org/x/tools/go/analysis](https://pkg.go.dev/golang.org/x/tools/go/analysis) - Helpers to write analysis tools (see [example](https://arslan.io/2019/06/13/using-go-analysis-to-write-a-custom-linter/)) 275 | - Testing 276 | - [testing](https://pkg.go.dev/testing/) 277 | - [testify](https://pkg.go.dev/github.com/stretchr/testify) - Many test utilities (including suites & mocking) 278 | - [Tutorial: Getting started with fuzzing](https://go.dev/doc/tutorial/fuzz) 279 | - [testing/quick](https://pkg.go.dev/testing/quick) - Initial fuzzing library 280 | - [test containers](https://golang.testcontainers.org/) 281 | - HTTP Servers 282 | - [net/http](https://pkg.go.dev/net/http/) 283 | - [net/http/httptest](https://pkg.go.dev/net/http/httptest) 284 | - [gorilla/mux](https://github.com/gorilla/mux) - HTTP router with more frills 285 | - [chi](https://github.com/go-chi/chi) - A nice web framework 286 | 287 | ### Data & Other 288 | 289 | - [nlp.go](_extra/nlp.go) 290 | - [stemmer.go](_extra/stemmer.go) 291 | - [tokenize_cases.toml](_extra/tokenize_cases.toml) 292 | - `github.com/BurntSushi/toml` 293 | -------------------------------------------------------------------------------- /_extra/day-1.log: -------------------------------------------------------------------------------- 1 | go version 2 | go version go1.19 linux/amd64 3 | 4 | practical-go/code on  znga [!] 5 | $ git --version 6 | git version 2.37.1 7 | 8 | practical-go/code on  znga [!] 9 | $ go mod init znga 10 | go: creating new go.mod: module znga 11 | 12 | practical-go/code on  znga [!?] via go v1.19 13 | $ cd hw 14 | 15 | practical-go/code/hw on  znga [!?] via go v1.19 16 | $ go build 17 | 18 | practical-go/code/hw on  znga [!?] via go v1.19 19 | $ ls 20 | hw hw.go 21 | 22 | practical-go/code/hw on  znga [!?] via go v1.19 23 | $ ./hw 24 | Hello Gophers ☺ 25 | 26 | practical-go/code/hw on  znga [!?] via go v1.19 27 | $go run hw.go 28 | Hello Gophers ☺ 29 | 30 | practical-go/code/hw on  znga [!?] via go v1.19 31 | $ time go run hw.go 32 | Hello Gophers ☺ 33 | go run hw.go 0.13s user 0.10s system 170% cpu 0.131 total 34 | 35 | practical-go/code/hw on  znga [!?] via go v1.19 36 | $ ls -lh 37 | total 1.8M 38 | -rwxr-xr-x 1 miki miki 1.8M Aug 15 18:42 hw 39 | -rw-r--r-- 1 miki miki 99 Aug 15 18:39 hw.go 40 | 41 | practical-go/code/hw on  znga [!?] via go v1.19 42 | $ file hw 43 | hw: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=dFgD23zZ7NI6O5ew16GP/XliIXyg1PplnDkBq2jUD/92icc3i6hrQ9iPN8NFE4/BiU5FfDBgZEc9PJQ_xNL, with debug_info, not stripped 44 | 45 | practical-go/code/hw on  znga [!?] via go v1.19 46 | $ go env GOARCH GOOS 47 | amd64 48 | linux 49 | 50 | practical-go/code/hw on  znga [!?] via go v1.19 51 | $ GOOS=darwin go build 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 45 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 67 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 227 size2 := 0 154 | 228 for _, r := range a { 155 | 229 // check for race 156 | 230 if size2 >= size1 { 157 | 231 break 158 | 232 } 159 | 233 size2 += encoderune(b[size2:], r) 160 | 234 } 161 | 235 return s[:size2] 162 | 236 } 163 | 237 164 | 238 type stringStruct struct { 165 | 239 str unsafe.Pointer 166 | 240 len int 167 | 241 } 168 | 242 169 | 243 // Variant with *byte pointer type for DWARF debugging. 170 | 244 type stringStructDWARF struct { 171 | 245 str *byte 172 | 246 len int 173 | 247 } 174 | 248 175 | 249 func stringStructOf(sp *string) *stringStruct { 176 | 250 return (*stringStruct)(unsafe.Pointer(sp)) 177 | COMMAND  SPELL [EN]   master⚡  103 | 2022/08/18 18:18:44 Warning: fetching url from deps.dev: Get "https://deps.dev/_/s/go/p/github.com%2F353solutions%2Fnlp/v/v0.0.0/exists": context deadline exceeded 104 | ^C 105 | 106 | practical-go/code/nlp on  znga [✘!?] via go v1.19 took 50s 107 | $ go test 108 | PASS 109 | ok github.com/353solutions/nlp 0.002s 110 | 111 | practical-go/code/nlp on  znga [✘!?] via go v1.19 112 | $ go test -v 113 | === RUN ExampleTokenize 114 | --- PASS: ExampleTokenize (0.00s) 115 | PASS 116 | ok github.com/353solutions/nlp 0.001s 117 | 118 | practical-go/code/nlp on  znga [✘!?] via go v1.19 119 | ❮ go test -v 120 | testing: warning: no tests to run 121 | PASS 122 | ok github.com/353solutions/nlp 0.001s 123 | 124 | practical-go/code/nlp on  znga [✘!?] via go v1.19 125 | ❮ go test -v 126 | === RUN ExampleTokenize 127 | --- PASS: ExampleTokenize (0.00s) 128 | PASS 129 | ok github.com/353solutions/nlp 0.001s 130 | practical-go/code/nlp on  znga [✘!?] via go v1.19 131 | ❮ go test -v 132 | testing: warning: no tests to run 133 | PASS 134 | ok github.com/353solutions/nlp 0.002s 135 | 136 | practical-go/code/nlp on  znga [✘!?] via go v1.19 137 | ❮ go test -v 138 | === RUN ExampleTokenize 139 | --- FAIL: ExampleTokenize (0.00s) 140 | got: 141 | [who s on first] 142 | want: 143 | [who s on first?] 144 | FAIL 145 | exit status 1 146 | FAIL github.com/353solutions/nlp 0.003s 147 | 148 | practical-go/code/nlp on  znga [✘!?] via go v1.19 149 | $ go test -v 150 | === RUN ExampleTokenize 151 | --- PASS: ExampleTokenize (0.00s) 152 | PASS 153 | ok github.com/353solutions/nlp 0.001s 154 | 155 | practical-go/code/nlp on  znga [✘!?] via go v1.19 156 | ❮ pkgsite -http=:8080 157 | 2022/08/18 18:30:56 Info: Listening on addr http://:8080 158 | 2022/08/18 18:31:08 Info: FetchDataSource: fetching github.com/353solutions/nlp@latest 159 | 2022/08/18 18:31:09 Info: LICENSE.txt license coverage too low ({Percent:0 Match:[]}), skipping 160 | 2022/08/18 18:31:09 Info: FetchDataSource: fetched github.com/353solutions/nlp@latest in 891.71268ms with error 161 | +([]string) (len=3) { 162 | (string) (len=4) "what", 163 | - (string) (len=1) "s", 164 | (string) (len=2) "on", 165 | Test: TestTokenize 166 | --- FAIL: TestTokenize (0.00s) 167 | === RUN ExampleTokenize 168 | --- FAIL: ExampleTokenize (0.00s) 169 | got: 170 | [who on first] 171 | want: 172 | [who s on first] 173 | FAIL 174 | exit status 1 175 | FAIL github.com/353solutions/nlp 0.004s 176 | 177 | practical-go/code/nlp on  znga [✘!?] via go v1.19 178 | $ 179 | 180 | practical-go/code/nlp on  znga [✘!?] via go v1.19 181 | $ go test -v 182 | === RUN TestTokenizeTable 183 | === RUN TestTokenizeTable/Who's_on_first? 184 | === RUN TestTokenizeTable/What's_on_second? 185 | === RUN TestTokenizeTable/#00 186 | --- PASS: TestTokenizeTable (0.00s) 187 | --- PASS: TestTokenizeTable/Who's_on_first? (0.00s) 188 | --- PASS: TestTokenizeTable/What's_on_second? (0.00s) 189 | --- PASS: TestTokenizeTable/#00 (0.00s) 190 | === RUN TestTokenize 191 | --- PASS: TestTokenize (0.00s) 192 | === RUN ExampleTokenize 193 | --- PASS: ExampleTokenize (0.00s) 194 | PASS 195 | ok github.com/353solutions/nlp 0.005s 196 | 197 | practical-go/code/nlp on  znga [✘!?] via go v1.19 198 | $ go env GOPAT 199 | 200 | 201 | practical-go/code/nlp on  znga [✘!?] via go v1.19 202 | $ go env GOPATH 203 | /home/miki/go 204 | 205 | practical-go/code/nlp on  znga [✘!?] via go v1.19 206 | $ go test -v 207 | === RUN TestTokenizeTable 208 | nlp_test.go:37: 209 | Error Trace: /home/miki/teaching/practical-go/code/nlp/nlp_test.go:37 210 | /home/miki/teaching/practical-go/code/nlp/nlp_test.go:46 211 | Error: Received unexpected error: 212 | open tokenize_cases.toml: no such file or directory 213 | Test: TestTokenizeTable 214 | Messages: Unmarshal TOML 215 | --- FAIL: TestTokenizeTable (0.00s) 216 | === RUN TestTokenize 217 | --- PASS: TestTokenize (0.00s) 218 | === RUN ExampleTokenize 219 | --- PASS: ExampleTokenize (0.00s) 220 | FAIL 221 | exit status 1 222 | FAIL github.com/353solutions/nlp 0.006s 223 | 224 | practical-go/code/nlp on  znga [✘!?] via go v1.19 225 | $ go test -v 226 | === RUN TestTokenizeTable 227 | === RUN TestTokenizeTable/Who's_on_first? 228 | === RUN TestTokenizeTable/What's_on_second? 229 | === RUN TestTokenizeTable/#00 230 | --- PASS: TestTokenizeTable (0.00s) 231 | --- PASS: TestTokenizeTable/Who's_on_first? (0.00s) 232 | --- PASS: TestTokenizeTable/What's_on_second? (0.00s) 233 | --- PASS: TestTokenizeTable/#00 (0.00s) 234 | === RUN TestTokenize 235 | --- PASS: TestTokenize (0.00s) 236 | === RUN ExampleTokenize 237 | --- PASS: ExampleTokenize (0.00s) 238 | PASS 239 | ok github.com/353solutions/nlp 0.007s 240 | 241 | practical-go/code/nlp on  znga [✘!?] via go v1.19 242 | $ go test -v ./... 243 | === RUN TestTokenizeTable 244 | === RUN TestTokenizeTable/Who's_on_first? 245 | === RUN TestTokenizeTable/What's_on_second? 246 | === RUN TestTokenizeTable/#00 247 | --- PASS: TestTokenizeTable (0.00s) 248 | --- PASS: TestTokenizeTable/Who's_on_first? (0.00s) 249 | --- PASS: TestTokenizeTable/What's_on_second? (0.00s) 250 | --- PASS: TestTokenizeTable/#00 (0.00s) 251 | === RUN TestTokenize 252 | --- PASS: TestTokenize (0.00s) 253 | === RUN ExampleTokenize 254 | --- PASS: ExampleTokenize (0.00s) 255 | PASS 256 | ok github.com/353solutions/nlp 0.006s 257 | === RUN TestStem 258 | === RUN TestStem/working:work 259 | === RUN TestStem/works:work 260 | === RUN TestStem/worked:work 261 | === RUN TestStem/work:work 262 | --- PASS: TestStem (0.00s) 263 | --- PASS: TestStem/working:work (0.00s) 264 | --- PASS: TestStem/works:work (0.00s) 265 | --- PASS: TestStem/worked:work (0.00s) 266 | --- PASS: TestStem/work:work (0.00s) 267 | PASS 268 | ok github.com/353solutions/nlp/stemmer 0.005s 269 | 270 | practical-go/code/nlp on  znga [✘!?] via go v1.19 271 | $ go test -v -race ./... 272 | === RUN TestTokenizeTable 273 | === RUN TestTokenizeTable/Who's_on_first? 274 | === RUN TestTokenizeTable/What's_on_second? 275 | === RUN TestTokenizeTable/#00 276 | --- PASS: TestTokenizeTable (0.00s) 277 | --- PASS: TestTokenizeTable/Who's_on_first? (0.00s) 278 | --- PASS: TestTokenizeTable/What's_on_second? (0.00s) 279 | --- PASS: TestTokenizeTable/#00 (0.00s) 280 | === RUN TestTokenize 281 | --- PASS: TestTokenize (0.00s) 282 | === RUN ExampleTokenize 283 | --- PASS: ExampleTokenize (0.00s) 284 | PASS 285 | ok github.com/353solutions/nlp 0.035s 286 | === RUN TestStem 287 | === RUN TestStem/working:work 288 | === RUN TestStem/works:work 289 | === RUN TestStem/worked:work 290 | === RUN TestStem/work:work 291 | --- PASS: TestStem (0.00s) 292 | --- PASS: TestStem/working:work (0.00s) 293 | --- PASS: TestStem/works:work (0.00s) 294 | --- PASS: TestStem/worked:work (0.00s) 295 | --- PASS: TestStem/work:work (0.00s) 296 | PASS 297 | ok github.com/353solutions/nlp/stemmer 0.033s 298 | 299 | practical-go/code/nlp on  znga [✘!?] via go v1.19 took 13s 300 | $ go test -v -race ./... 301 | === RUN TestTokenizeTable 302 | === RUN TestTokenizeTable/Who's_on_first? 303 | === RUN TestTokenizeTable/What's_on_second? 304 | === RUN TestTokenizeTable/#00 305 | --- PASS: TestTokenizeTable (0.00s) 306 | --- PASS: TestTokenizeTable/Who's_on_first? (0.00s) 307 | --- PASS: TestTokenizeTable/What's_on_second? (0.00s) 308 | --- PASS: TestTokenizeTable/#00 (0.00s) 309 | === RUN TestTokenize 310 | --- PASS: TestTokenize (0.00s) 311 | === RUN ExampleTokenize 312 | --- PASS: ExampleTokenize (0.00s) 313 | PASS 314 | ok github.com/353solutions/nlp (cached) 315 | === RUN TestStem 316 | === RUN TestStem/working:work 317 | === RUN TestStem/works:work 318 | === RUN TestStem/worked:work 319 | === RUN TestStem/work:work 320 | --- PASS: TestStem (0.00s) 321 | --- PASS: TestStem/working:work (0.00s) 322 | --- PASS: TestStem/works:work (0.00s) 323 | --- PASS: TestStem/worked:work (0.00s) 324 | --- PASS: TestStem/work:work (0.00s) 325 | PASS 326 | ok github.com/353solutions/nlp/stemmer (cached) 327 | 328 | practical-go/code/nlp on  znga [✘!?] via go v1.19 329 | $ go test -v -race -cover ./... 330 | === RUN TestTokenizeTable 331 | === RUN TestTokenizeTable/Who's_on_first? 332 | === RUN TestTokenizeTable/What's_on_second? 333 | === RUN TestTokenizeTable/#00 334 | --- PASS: TestTokenizeTable (0.00s) 335 | --- PASS: TestTokenizeTable/Who's_on_first? (0.00s) 336 | --- PASS: TestTokenizeTable/What's_on_second? (0.00s) 337 | --- PASS: TestTokenizeTable/#00 (0.00s) 338 | === RUN TestTokenize 339 | --- PASS: TestTokenize (0.00s) 340 | === RUN ExampleTokenize 341 | --- PASS: ExampleTokenize (0.00s) 342 | PASS 343 | coverage: 100.0% of statements 344 | ok github.com/353solutions/nlp 0.035s coverage: 100.0% of statements 345 | === RUN TestStem 346 | === RUN TestStem/working:work 347 | === RUN TestStem/works:work 348 | === RUN TestStem/worked:work 349 | === RUN TestStem/work:work 350 | --- PASS: TestStem (0.00s) 351 | --- PASS: TestStem/working:work (0.00s) 352 | --- PASS: TestStem/works:work (0.00s) 353 | --- PASS: TestStem/worked:work (0.00s) 354 | --- PASS: TestStem/work:work (0.00s) 355 | PASS 356 | coverage: 100.0% of statements 357 | ok github.com/353solutions/nlp/stemmer 0.024s coverage: 100.0% of statements 358 | 359 | practical-go/code/nlp on  znga [✘!?] via go v1.19 360 | ❮ go test -v -race -cover ./... 361 | === RUN TestTokenizeTable 362 | === RUN TestTokenizeTable/Who's_on_first? 363 | === RUN TestTokenizeTable/What's_on_second? 364 | === RUN TestTokenizeTable/#00 365 | --- PASS: TestTokenizeTable (0.00s) 366 | --- PASS: TestTokenizeTable/Who's_on_first? (0.00s) 367 | --- PASS: TestTokenizeTable/What's_on_second? (0.00s) 368 | --- PASS: TestTokenizeTable/#00 (0.00s) 369 | === RUN TestTokenize 370 | --- PASS: TestTokenize (0.00s) 371 | === RUN FuzzTokenize 372 | --- PASS: FuzzTokenize (0.00s) 373 | === RUN ExampleTokenize 374 | --- PASS: ExampleTokenize (0.00s) 375 | PASS 376 | coverage: 100.0% of statements 377 | ok github.com/353solutions/nlp 0.035s coverage: 100.0% of statements 378 | === RUN TestStem 379 | === RUN TestStem/working:work 380 | === RUN TestStem/works:work 381 | === RUN TestStem/worked:work 382 | === RUN TestStem/work:work 383 | --- PASS: TestStem (0.00s) 384 | --- PASS: TestStem/working:work (0.00s) 385 | --- PASS: TestStem/works:work (0.00s) 386 | --- PASS: TestStem/worked:work (0.00s) 387 | --- PASS: TestStem/work:work (0.00s) 388 | PASS 389 | coverage: 100.0% of statements 390 | ok github.com/353solutions/nlp/stemmer (cached) coverage: 100.0% of statements 391 | 392 | practical-go/code/nlp on  znga [✘!?] via go v1.19 393 | $ go test -fuzz . 394 | ^C 395 | 396 | practical-go/code/nlp on  znga [✘!?] via go v1.19 took 15s 397 | $ go test -fuzz . -fuzztime 10s -v 398 | === RUN TestTokenizeTable 399 | === RUN TestTokenizeTable/Who's_on_first? 400 | === RUN TestTokenizeTable/What's_on_second? 401 | === RUN TestTokenizeTable/#00 402 | --- PASS: TestTokenizeTable (0.00s) 403 | --- PASS: TestTokenizeTable/Who's_on_first? (0.00s) 404 | --- PASS: TestTokenizeTable/What's_on_second? (0.00s) 405 | --- PASS: TestTokenizeTable/#00 (0.00s) 406 | === RUN TestTokenize 407 | --- PASS: TestTokenize (0.00s) 408 | === RUN ExampleTokenize 409 | --- PASS: ExampleTokenize (0.00s) 410 | === FUZZ FuzzTokenize 411 | warning: starting with empty corpus 412 | fuzz: elapsed: 0s, execs: 0 (0/sec), new interesting: 0 (total: 0) 413 | fuzz: elapsed: 3s, execs: 77769 (25919/sec), new interesting: 71 (total: 71) 414 | fuzz: elapsed: 6s, execs: 126632 (16290/sec), new interesting: 78 (total: 78) 415 | fuzz: elapsed: 9s, execs: 148839 (7401/sec), new interesting: 82 (total: 82) 416 | fuzz: elapsed: 11s, execs: 148839 (0/sec), new interesting: 82 (total: 82) 417 | --- PASS: FuzzTokenize (11.01s) 418 | PASS 419 | ok github.com/353solutions/nlp 11.018s 420 | 421 | practical-go/code/nlp on  znga [✘!?] via go v1.19 took 18s 422 | $ go test -fuzz . -fuzztime 10s -v 423 | === RUN TestTokenizeTable 424 | === RUN TestTokenizeTable/Who's_on_first? 425 | === RUN TestTokenizeTable/What's_on_second? 426 | === RUN TestTokenizeTable/#00 427 | --- PASS: TestTokenizeTable (0.00s) 428 | --- PASS: TestTokenizeTable/Who's_on_first? (0.00s) 429 | --- PASS: TestTokenizeTable/What's_on_second? (0.00s) 430 | --- PASS: TestTokenizeTable/#00 (0.00s) 431 | === RUN TestTokenize 432 | --- PASS: TestTokenize (0.00s) 433 | === RUN ExampleTokenize 434 | --- PASS: ExampleTokenize (0.00s) 435 | === FUZZ FuzzTokenize 436 | fuzz: elapsed: 0s, gathering baseline coverage: 0/82 completed 437 | fuzz: minimizing 47-byte failing input file 438 | fuzz: elapsed: 0s, gathering baseline coverage: 0/82 completed 439 | --- FAIL: FuzzTokenize (0.05s) 440 | --- FAIL: FuzzTokenize (0.00s) 441 | nlp_test.go:70: 0 442 | 443 | Failing input written to testdata/fuzz/FuzzTokenize/771e938e4458e983a736261a702e27c7a414fd660a15b63034f290b146d2f217 444 | To re-run: 445 | go test -run=FuzzTokenize/771e938e4458e983a736261a702e27c7a414fd660a15b63034f290b146d2f217 446 | FAIL 447 | exit status 1 448 | FAIL github.com/353solutions/nlp 0.055s 449 | 450 | practical-go/code/nlp on  znga [✘!?] via go v1.19 451 | $ cat testdata/fuzz/FuzzTokenize/771e938e4458e983a736261a702e27c7a414fd660a15b63034f290b146d2f217 452 | go test fuzz v1 453 | string("0") 454 | 455 | practical-go/code/nlp on  znga [✘!?] via go v1.19 456 | $ mkdir -p cmd/nlpd 457 | 458 | practical-go/code/nlp on  znga [✘!?] via go v1.19 459 | $ curl http://localhost:8080/health 460 | OK 461 | 462 | practical-go/code/nlp on  znga [✘!?] via go v1.19 463 | ❮ go install github.com/rakyll/hey 464 | no required module provides package github.com/rakyll/hey; to add it: 465 | go get github.com/rakyll/hey 466 | 467 | practical-go/code/nlp on  znga [✘!?] via go v1.19 468 | $ go install github.com/rakyll/hey@latest 469 | go: downloading github.com/rakyll/hey v0.1.4 470 | go: downloading golang.org/x/net v0.0.0-20181017193950-04a2e542c03f 471 | 472 | practical-go/code/nlp on  znga [✘!?] via go v1.19 took 5s 473 | ❮ curl http://localhost:8080/health 474 | OK 475 | 476 | practical-go/code/nlp on  znga [✘!?] via go v1.19 477 | $ hey -n 10000 http://localhost:8080/health 478 | 479 | Summary: 480 | Total: 0.2854 secs 481 | Slowest: 0.0618 secs 482 | Fastest: 0.0001 secs 483 | Average: 0.0014 secs 484 | Requests/sec: 35042.4349 485 | 486 | Total data: 30000 bytes 487 | Size/request: 3 bytes 488 | 489 | Response time histogram: 490 | 0.000 [1] | 491 | 0.006 [9855] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 492 | 0.012 [67] | 493 | 0.019 [27] | 494 | 0.025 [0] | 495 | 0.031 [0] | 496 | 0.037 [0] | 497 | 0.043 [5] | 498 | 0.049 [43] | 499 | 0.056 [1] | 500 | 0.062 [1] | 501 | 502 | 503 | Latency distribution: 504 | 10% in 0.0002 secs 505 | 25% in 0.0004 secs 506 | 50% in 0.0008 secs 507 | 75% in 0.0013 secs 508 | 90% in 0.0026 secs 509 | 95% in 0.0034 secs 510 | 99% in 0.0083 secs 511 | 512 | Details (average, fastest, slowest): 513 | DNS+dialup: 0.0000 secs, 0.0001 secs, 0.0618 secs 514 | DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0105 secs 515 | req write: 0.0001 secs, 0.0000 secs, 0.0437 secs 516 | resp wait: 0.0009 secs, 0.0000 secs, 0.0425 secs 517 | resp read: 0.0003 secs, 0.0000 secs, 0.0447 secs 518 | 519 | Status code distribution: 520 | [200] 10000 responses 521 | 522 | 523 | 524 | 525 | practical-go/code/nlp on  znga [✘!?] via go v1.19 526 | $ curl -i -d"Who's on first?" http://localhost:8080/tokenize 527 | HTTP/1.1 200 OK 528 | Content-Type: application/json 529 | Date: Thu, 18 Aug 2022 18:12:17 GMT 530 | Content-Length: 31 531 | 532 | {"tokens":["who","on","first"]}% 533 | practical-go/code/nlp on  znga [✘!?] via go v1.19 534 | $ curl -i -d"Who's on first?" http://localhost:8080/tokenize 535 | HTTP/1.1 200 OK 536 | Content-Type: application/json 537 | Date: Thu, 18 Aug 2022 18:14:22 GMT 538 | Content-Length: 31 539 | 540 | {"tokens":["who","on","first"]}% 541 | practical-go/code/nlp on  znga [✘!?] via go v1.19 542 | $ curl -i -d"Who's on first?" http://localhost:8080/tokenize 543 | HTTP/1.1 200 OK 544 | Content-Type: application/json 545 | Date: Thu, 18 Aug 2022 18:14:54 GMT 546 | Content-Length: 41 547 | 548 | {"ok":true,"tokens":["who","on","first"]}% 549 | practical-go/code/nlp on  znga [✘!?] via go v1.19 550 | $ curl -i -d"Who's on first?" http://localhost:8080/tokenize 551 | HTTP/1.1 200 OK 552 | Content-Type: application/json 553 | Date: Thu, 18 Aug 2022 18:35:25 GMT 554 | Content-Length: 31 555 | 556 | {"tokens":["who","on","first"]}% 557 | practical-go/code/nlp on  znga [✘!?] via go v1.19 558 | $ curl http://localhost:8080/stem/running 559 | runn 560 | 561 | practical-go/code/nlp on  znga [✘!?] via go v1.19 562 | $ curl http://localhost:8080/stem/works 563 | work 564 | 565 | practical-go/code/nlp on  znga [✘!?] via go v1.19 566 | $ curl http://localhost:8080/debug/vars 567 | { 568 | "cmdline": ["/home/miki/teaching/practical-go/code/nlp/cmd/nlpd/__debug_bin"], 569 | "memstats": {"Alloc":345728,"TotalAlloc":345728,"Sys":12958736,"Lookups":0,"Mallocs":1127,"Frees":63,"HeapAlloc":345728,"HeapSys":3833856,"HeapIdle":2891776,"HeapInuse":942080,"HeapReleased":2859008,"HeapObjects":1064,"StackInuse":360448,"StackSys":360448,"MSpanInuse":37944,"MSpanSys":48960,"MCacheInuse":14400,"MCacheSys":15600,"BuckHashSys":3932,"GCSys":8055592,"OtherSys":640348,"NextGC":4194304,"LastGC":0,"PauseTotalNs":0,"PauseNs":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":0,"NumForcedGC":0,"GCCPUFraction":0,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":26,"Frees":0},{"Size":16,"Mallocs":409,"Frees":0},{"Size":24,"Mallocs":68,"Frees":0},{"Size":32,"Mallocs":76,"Frees":0},{"Size":48,"Mallocs":147,"Frees":0},{"Size":64,"Mallocs":54,"Frees":0},{"Size":80,"Mallocs":25,"Frees":0},{"Size":96,"Mallocs":14,"Frees":0},{"Size":112,"Mallocs":36,"Frees":0},{"Size":128,"Mallocs":7,"Frees":0},{"Size":144,"Mallocs":3,"Frees":0},{"Size":160,"Mallocs":32,"Frees":0},{"Size":176,"Mallocs":6,"Frees":0},{"Size":192,"Mallocs":1,"Frees":0},{"Size":208,"Mallocs":20,"Frees":0},{"Size":224,"Mallocs":2,"Frees":0},{"Size":240,"Mallocs":1,"Frees":0},{"Size":256,"Mallocs":9,"Frees":0},{"Size":288,"Mallocs":7,"Frees":0},{"Size":320,"Mallocs":8,"Frees":0},{"Size":352,"Mallocs":12,"Frees":0},{"Size":384,"Mallocs":2,"Frees":0},{"Size":416,"Mallocs":28,"Frees":0},{"Size":448,"Mallocs":0,"Frees":0},{"Size":480,"Mallocs":0,"Frees":0},{"Size":512,"Mallocs":0,"Frees":0},{"Size":576,"Mallocs":2,"Frees":0},{"Size":640,"Mallocs":7,"Frees":0},{"Size":704,"Mallocs":4,"Frees":0},{"Size":768,"Mallocs":0,"Frees":0},{"Size":896,"Mallocs":4,"Frees":0},{"Size":1024,"Mallocs":7,"Frees":0},{"Size":1152,"Mallocs":3,"Frees":0},{"Size":1280,"Mallocs":3,"Frees":0},{"Size":1408,"Mallocs":1,"Frees":0},{"Size":1536,"Mallocs":8,"Frees":0},{"Size":1792,"Mallocs":5,"Frees":0},{"Size":2048,"Mallocs":1,"Frees":0},{"Size":2304,"Mallocs":2,"Frees":0},{"Size":2688,"Mallocs":4,"Frees":0},{"Size":3072,"Mallocs":0,"Frees":0},{"Size":3200,"Mallocs":1,"Frees":0},{"Size":3456,"Mallocs":0,"Frees":0},{"Size":4096,"Mallocs":3,"Frees":0},{"Size":4864,"Mallocs":0,"Frees":0},{"Size":5376,"Mallocs":1,"Frees":0},{"Size":6144,"Mallocs":1,"Frees":0},{"Size":6528,"Mallocs":0,"Frees":0},{"Size":6784,"Mallocs":0,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":1,"Frees":0},{"Size":9472,"Mallocs":12,"Frees":0},{"Size":9728,"Mallocs":0,"Frees":0},{"Size":10240,"Mallocs":0,"Frees":0},{"Size":10880,"Mallocs":0,"Frees":0},{"Size":12288,"Mallocs":0,"Frees":0},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14336,"Mallocs":0,"Frees":0},{"Size":16384,"Mallocs":0,"Frees":0},{"Size":18432,"Mallocs":0,"Frees":0}]}, 570 | "tokenize.calls": 0 571 | } 572 | 573 | practical-go/code/nlp on  znga [✘!?] via go v1.19 574 | $ curl http://localhost:8080/stem/works 575 | 576 | practical-go/code/nlp on  znga [✘!?] via go v1.19 577 | $ curl -d "What's on second?" http://localhost:8080/tokenize 578 | {"tokens":["what","on","second"]}% 579 | practical-go/code/nlp on  znga [✘!?] via go v1.19 580 | ❮ curl http://localhost:8080/debug/vars 581 | { 582 | "cmdline": ["/home/miki/teaching/practical-go/code/nlp/cmd/nlpd/__debug_bin"], 583 | "memstats": {"Alloc":462624,"TotalAlloc":462624,"Sys":12958736,"Lookups":0,"Mallocs":1654,"Frees":140,"HeapAlloc":462624,"HeapSys":3866624,"HeapIdle":2801664,"HeapInuse":1064960,"HeapReleased":2703360,"HeapObjects":1514,"StackInuse":327680,"StackSys":327680,"MSpanInuse":37944,"MSpanSys":48960,"MCacheInuse":14400,"MCacheSys":15600,"BuckHashSys":3932,"GCSys":8086432,"OtherSys":609508,"NextGC":4194304,"LastGC":0,"PauseTotalNs":0,"PauseNs":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":0,"NumForcedGC":0,"GCCPUFraction":0,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":60,"Frees":0},{"Size":16,"Mallocs":638,"Frees":0},{"Size":24,"Mallocs":98,"Frees":0},{"Size":32,"Mallocs":83,"Frees":0},{"Size":48,"Mallocs":188,"Frees":0},{"Size":64,"Mallocs":79,"Frees":0},{"Size":80,"Mallocs":30,"Frees":0},{"Size":96,"Mallocs":18,"Frees":0},{"Size":112,"Mallocs":38,"Frees":0},{"Size":128,"Mallocs":12,"Frees":0},{"Size":144,"Mallocs":11,"Frees":0},{"Size":160,"Mallocs":35,"Frees":0},{"Size":176,"Mallocs":7,"Frees":0},{"Size":192,"Mallocs":1,"Frees":0},{"Size":208,"Mallocs":28,"Frees":0},{"Size":224,"Mallocs":4,"Frees":0},{"Size":240,"Mallocs":1,"Frees":0},{"Size":256,"Mallocs":18,"Frees":0},{"Size":288,"Mallocs":10,"Frees":0},{"Size":320,"Mallocs":8,"Frees":0},{"Size":352,"Mallocs":18,"Frees":0},{"Size":384,"Mallocs":2,"Frees":0},{"Size":416,"Mallocs":30,"Frees":0},{"Size":448,"Mallocs":0,"Frees":0},{"Size":480,"Mallocs":0,"Frees":0},{"Size":512,"Mallocs":3,"Frees":0},{"Size":576,"Mallocs":4,"Frees":0},{"Size":640,"Mallocs":7,"Frees":0},{"Size":704,"Mallocs":4,"Frees":0},{"Size":768,"Mallocs":0,"Frees":0},{"Size":896,"Mallocs":4,"Frees":0},{"Size":1024,"Mallocs":9,"Frees":0},{"Size":1152,"Mallocs":4,"Frees":0},{"Size":1280,"Mallocs":3,"Frees":0},{"Size":1408,"Mallocs":1,"Frees":0},{"Size":1536,"Mallocs":12,"Frees":0},{"Size":1792,"Mallocs":6,"Frees":0},{"Size":2048,"Mallocs":3,"Frees":0},{"Size":2304,"Mallocs":3,"Frees":0},{"Size":2688,"Mallocs":4,"Frees":0},{"Size":3072,"Mallocs":0,"Frees":0},{"Size":3200,"Mallocs":1,"Frees":0},{"Size":3456,"Mallocs":0,"Frees":0},{"Size":4096,"Mallocs":9,"Frees":0},{"Size":4864,"Mallocs":1,"Frees":0},{"Size":5376,"Mallocs":1,"Frees":0},{"Size":6144,"Mallocs":2,"Frees":0},{"Size":6528,"Mallocs":0,"Frees":0},{"Size":6784,"Mallocs":0,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":2,"Frees":0},{"Size":9472,"Mallocs":12,"Frees":0},{"Size":9728,"Mallocs":0,"Frees":0},{"Size":10240,"Mallocs":0,"Frees":0},{"Size":10880,"Mallocs":0,"Frees":0},{"Size":12288,"Mallocs":0,"Frees":0},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14336,"Mallocs":0,"Frees":0},{"Size":16384,"Mallocs":0,"Frees":0},{"Size":18432,"Mallocs":0,"Frees":0}]}, 584 | "tokenize.calls": 1 585 | } 586 | 587 | practical-go/code/nlp on  znga [✘!?] via go v1.19 588 | $ curl -d "What's on second?" http://localhost:8080/tokenize 589 | 590 | practical-go/code/nlp on  znga [✘!?] via go v1.19 591 | $ go run ./cmd/nlpd 592 | [nlpd] 2022/08/18 21:54:07 main.go:47: server starting on :8080 593 | ^Csignal: interrupt 594 | 595 | practical-go/code/nlp on  znga [✘!?] via go v1.19 took 2s 596 | $ NLPD_ADDR=:9999 go run ./cmd/nlpd 597 | [nlpd] 2022/08/18 21:54:14 main.go:47: server starting on :9999 598 | ^Csignal: interrupt 599 | 600 | practical-go/code/nlp on  znga [✘!?] via go v1.19 took 4s 601 | -------------------------------------------------------------------------------- /_extra/http.log.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardanlabs/practical-go/ed678f0c09981a9aa54ad5cef87b521ac92e1c26/_extra/http.log.gz -------------------------------------------------------------------------------- /_extra/nlp.go: -------------------------------------------------------------------------------- 1 | package nlp 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | wordRe = regexp.MustCompile(`[a-zA-Z]+`) 10 | ) 11 | 12 | func Tokenize(text string) []string { 13 | words := wordRe.FindAllString(text, -1) 14 | var tokens []string 15 | for _, w := range words { 16 | token := strings.ToLower(w) 17 | tokens = append(tokens, token) 18 | } 19 | return tokens 20 | } 21 | -------------------------------------------------------------------------------- /_extra/rtb.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | // We have 50 msec to return an answer 12 | ctx, cancel := // TODO 13 | defer cancel() 14 | url := "https://go.dev" // return the 7¢ ad 15 | // url := "http://go.dev" // return the default ad 16 | bid := bidOn(ctx, url) 17 | fmt.Println(bid) 18 | } 19 | 20 | // If algo didn't finish in time, return a default bid 21 | func bidOn(ctx context.Context, url string) Bid { 22 | // TODO 23 | return Bid{} 24 | } 25 | 26 | var defaultBid = Bid{ 27 | AdURL: "http://adsЯus.com/default", 28 | Price: 3, 29 | } 30 | 31 | // Written by Algo team, time to completion varies 32 | func bestBid(url string) Bid { 33 | // Simulate work 34 | d := 100 * time.Millisecond 35 | if strings.HasPrefix(url, "https://") { 36 | d = 20 * time.Millisecond 37 | } 38 | time.Sleep(d) 39 | 40 | return Bid{ 41 | AdURL: "http://adsЯus.com/ad17", 42 | Price: 7, 43 | } 44 | } 45 | 46 | type Bid struct { 47 | AdURL string 48 | Price int // In ¢ 49 | } 50 | -------------------------------------------------------------------------------- /_extra/sites_time.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | urls := []string{ 12 | "https://www.google.com", 13 | "https://www.apple.com", 14 | "https://httpbin.org/status/404", 15 | } 16 | 17 | if err := siteTimes(urls); err != nil { 18 | fmt.Println("ERROR:", err) 19 | } 20 | } 21 | 22 | func siteTimes(urls []string) error { 23 | // TODO 24 | return nil 25 | } 26 | 27 | func siteTime(url string) (time.Duration, error) { 28 | start := time.Now() 29 | 30 | resp, err := http.Get(url) 31 | if err != nil { 32 | return 0, err 33 | } 34 | 35 | if resp.StatusCode != http.StatusOK { 36 | return 0, fmt.Errorf("bad status: %d %s", resp.StatusCode, resp.Status) 37 | } 38 | _, err = io.Copy(io.Discard, resp.Body) 39 | if err != nil { 40 | return 0, err 41 | } 42 | 43 | return time.Since(start), nil 44 | } 45 | -------------------------------------------------------------------------------- /_extra/stemmer.go: -------------------------------------------------------------------------------- 1 | package stemmer 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | var ( 8 | suffixes = []string{"s", "ing", "ed"} 9 | ) 10 | 11 | func Stem(word string) string { 12 | for _, suffix := range suffixes { 13 | if strings.HasSuffix(word, suffix) { 14 | return word[:len(word)-len(suffix)] 15 | } 16 | } 17 | return word 18 | } 19 | -------------------------------------------------------------------------------- /_extra/taxi-sha256.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardanlabs/practical-go/ed678f0c09981a9aa54ad5cef87b521ac92e1c26/_extra/taxi-sha256.zip -------------------------------------------------------------------------------- /_extra/taxi_check.go: -------------------------------------------------------------------------------- 1 | /* 2 | Write a function that gets an index file with names of files and sha256 3 | signatures in the following format 4 | 0c4ccc63a912bbd6d45174251415c089522e5c0e75286794ab1f86cb8e2561fd taxi-01.csv 5 | f427b5880e9164ec1e6cda53aa4b2d1f1e470da973e5b51748c806ea5c57cbdf taxi-02.csv 6 | 4e251e9e98c5cb7be8b34adfcb46cc806a4ef5ec8c95ba9aac5ff81449fc630c taxi-03.csv 7 | ... 8 | 9 | You should compute concurrently sha256 signatures of these files and see if 10 | they math the ones in the index file. 11 | 12 | - Print the number of processed files 13 | - If there's a mismatch, print the offending file(s) and exit the program with 14 | non-zero value 15 | 16 | Grab taxi-sha256.zip from the web site and open it. The index file is sha256sum.txt 17 | */ 18 | package main 19 | 20 | import ( 21 | "bufio" 22 | "compress/bzip2" 23 | "crypto/sha256" 24 | "fmt" 25 | "io" 26 | "log" 27 | "os" 28 | "path" 29 | "strings" 30 | "time" 31 | ) 32 | 33 | func fileSig(path string) (string, error) { 34 | file, err := os.Open(path) 35 | if err != nil { 36 | return "", err 37 | } 38 | defer file.Close() 39 | 40 | hash := sha256.New() 41 | _, err = io.Copy(hash, bzip2.NewReader(file)) 42 | if err != nil { 43 | return "", err 44 | } 45 | 46 | return fmt.Sprintf("%x", hash.Sum(nil)), nil 47 | } 48 | 49 | // Parse signature file. Return map of path->signature 50 | func parseSigFile(r io.Reader) (map[string]string, error) { 51 | sigs := make(map[string]string) 52 | scanner := bufio.NewScanner(r) 53 | for scanner.Scan() { 54 | // Line example 55 | // 6c6427da7893932731901035edbb9214 nasa-00.log 56 | fields := strings.Fields(scanner.Text()) 57 | if len(fields) != 2 { 58 | // TODO: line number 59 | return nil, fmt.Errorf("bad line: %q", scanner.Text()) 60 | } 61 | sigs[fields[1]] = fields[0] 62 | } 63 | 64 | if err := scanner.Err(); err != nil { 65 | return nil, err 66 | } 67 | 68 | return sigs, nil 69 | } 70 | 71 | func main() { 72 | rootDir := "/tmp/taxi" // Change to where to unzipped taxi-sha256.zip 73 | file, err := os.Open(path.Join(rootDir, "sha256sum.txt")) 74 | if err != nil { 75 | log.Fatalf("error: %s", err) 76 | } 77 | defer file.Close() 78 | 79 | sigs, err := parseSigFile(file) 80 | if err != nil { 81 | log.Fatalf("error: %s", err) 82 | } 83 | 84 | start := time.Now() 85 | ok := true 86 | for name, signature := range sigs { 87 | fileName := path.Join(rootDir, name) + ".bz2" 88 | sig, err := fileSig(fileName) 89 | if err != nil { 90 | fmt.Fprintf(os.Stderr, "error: %s - %s\n", fileName, err) 91 | ok = false 92 | continue 93 | } 94 | 95 | if sig != signature { 96 | ok = false 97 | fmt.Printf("error: %s mismatch\n", fileName) 98 | } 99 | } 100 | 101 | duration := time.Since(start) 102 | fmt.Printf("processed %d files in %v\n", len(sigs), duration) 103 | if !ok { 104 | os.Exit(1) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /_extra/tokenize_cases.toml: -------------------------------------------------------------------------------- 1 | [[cases]] 2 | text = "Who's on first?" 3 | tokens = ["who", "s", "on", "first"] 4 | 5 | [[cases]] 6 | text = "What's on second?" 7 | tokens = ["what", "s", "on", "second"] 8 | 9 | [[cases]] 10 | text = "" 11 | -------------------------------------------------------------------------------- /banner/banner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "unicode/utf8" 6 | ) 7 | 8 | func main() { 9 | banner("Go", 6) 10 | banner("G☺", 6) 11 | 12 | s := "G☺" 13 | fmt.Println("len:", len(s)) 14 | // code point = rune ~= unicode character 15 | for i, r := range s { 16 | fmt.Println(i, r) 17 | if i == 0 { 18 | fmt.Printf("%c of type %T\n", r, r) 19 | // rune (int32) 20 | } 21 | } 22 | 23 | b := s[0] 24 | fmt.Printf("%c of type %T\n", b, b) 25 | // byte (uint8) 26 | 27 | x, y := 1, "1" 28 | fmt.Printf("x=%v, y=%v\n", x, y) 29 | fmt.Printf("x=%#v, y=%#v\n", x, y) // Use #v in debug/log 30 | 31 | fmt.Printf("%20s!\n", s) 32 | 33 | fmt.Println("g", isPalindrome("g")) 34 | fmt.Println("go", isPalindrome("go")) 35 | fmt.Println("gog", isPalindrome("gog")) 36 | fmt.Println("g☺g", isPalindrome("g☺g")) 37 | } 38 | 39 | // isPalindrome("g") -> true 40 | // isPalindrome("go") -> false 41 | // isPalindrome("gog") -> true 42 | // isPalindrome("gogo") -> false 43 | func isPalindrome(s string) bool { 44 | rs := []rune(s) // get slice of runes out of s 45 | for i := 0; i < len(rs)/2; i++ { 46 | if rs[i] != rs[len(rs)-i-1] { 47 | return false 48 | } 49 | } 50 | return true 51 | } 52 | 53 | func banner(text string, width int) { 54 | padding := (width - utf8.RuneCountInString(text)) / 2 // BUG: len is in bytes 55 | // padding := (width - len(text)) / 2 // BUG: len is in bytes 56 | for i := 0; i < padding; i++ { 57 | fmt.Print(" ") 58 | } 59 | fmt.Println(text) 60 | for i := 0; i < width; i++ { 61 | fmt.Print("-") 62 | } 63 | fmt.Println() 64 | } 65 | 66 | /* 67 | multi 68 | line 69 | comment 70 | */ 71 | -------------------------------------------------------------------------------- /counter/counter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | func main() { 10 | /* solution 1: use mutex 11 | var mu sync.Mutex 12 | count := 0 13 | */ 14 | // count := int64(0) 15 | var count int64 16 | 17 | const n = 10 18 | var wg sync.WaitGroup 19 | wg.Add(n) 20 | for i := 0; i < n; i++ { 21 | go func() { 22 | defer wg.Done() 23 | for j := 0; j < 10_000; j++ { 24 | /* 25 | mu.Lock() 26 | count++ 27 | mu.Unlock() 28 | */ 29 | atomic.AddInt64(&count, 1) 30 | } 31 | }() 32 | } 33 | wg.Wait() 34 | fmt.Println(count) 35 | } 36 | -------------------------------------------------------------------------------- /div/div.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | // fmt.Println(div(1, 0)) 10 | fmt.Println(safeDiv(1, 0)) 11 | fmt.Println(safeDiv(7, 2)) 12 | } 13 | 14 | // named return values 15 | func safeDiv(a, b int) (q int, err error) { 16 | // q & err are local variables in safeDiv 17 | // (just like a & b) 18 | defer func() { 19 | // e's type is any (or interface{}) *not* error 20 | if e := recover(); e != nil { 21 | log.Println("ERROR:", e) 22 | err = fmt.Errorf("%v", e) 23 | } 24 | }() 25 | 26 | // panic("ouch!") 27 | 28 | return a / b, nil 29 | /* Miki don't like this kind of programming 30 | q = a / b 31 | return 32 | */ 33 | } 34 | 35 | func div(a, b int) int { 36 | return a / b 37 | } 38 | -------------------------------------------------------------------------------- /empty/empty.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var i any 7 | // go < 1.18 8 | // var i interface{} 9 | 10 | i = 7 11 | fmt.Println(i) 12 | 13 | i = "hi" 14 | fmt.Println(i) 15 | 16 | // Rule of thumb: Don't use any :) 17 | 18 | s := i.(string) // type assertion 19 | fmt.Println("s:", s) 20 | 21 | /* 22 | n := i.(int) // will panic 23 | fmt.Println("n:", n) 24 | */ 25 | 26 | // comma, ok 27 | n, ok := i.(int) 28 | if ok { 29 | fmt.Println("n:", n) 30 | } else { 31 | fmt.Println("not an int") 32 | } 33 | 34 | switch i.(type) { // type switch 35 | case int: 36 | fmt.Println("an int") 37 | case string: 38 | fmt.Println("a string") 39 | default: 40 | fmt.Printf("unknown type: %T\n", i) 41 | } 42 | 43 | /* 44 | fmt.Println(maxInts([]int{3, 1, 2})) 45 | fmt.Println(maxFloat64s([]float64{3, 1, 2})) 46 | */ 47 | fmt.Println(max([]int{3, 1, 2})) 48 | fmt.Println(max([]float64{3, 1, 2})) 49 | } 50 | 51 | type Number interface { 52 | int | float64 53 | } 54 | 55 | // func max[T int | float64](nums []T) T { 56 | func max[T Number](nums []T) T { 57 | if len(nums) == 0 { 58 | return 0 59 | } 60 | 61 | max := nums[0] 62 | for _, n := range nums[1:] { 63 | if n > max { 64 | max = n 65 | } 66 | } 67 | return max 68 | } 69 | 70 | func maxInts(nums []int) int { 71 | if len(nums) == 0 { 72 | return 0 73 | } 74 | 75 | max := nums[0] 76 | for _, n := range nums[1:] { 77 | if n > max { 78 | max = n 79 | } 80 | } 81 | return max 82 | } 83 | 84 | func maxFloat64s(nums []float64) float64 { 85 | if len(nums) == 0 { 86 | return 0 87 | } 88 | 89 | max := nums[0] 90 | for _, n := range nums[1:] { 91 | if n > max { 92 | max = n 93 | } 94 | } 95 | return max 96 | } 97 | -------------------------------------------------------------------------------- /freq/freq.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Q: What is the most common word (ignoring case) in sherlock.txt? 4 | // Word frequency 5 | 6 | import ( 7 | "bufio" 8 | "fmt" 9 | "io" 10 | "log" 11 | "os" 12 | "regexp" 13 | "strings" 14 | ) 15 | 16 | func main() { 17 | file, err := os.Open("sherlock.txt") 18 | // goland: freq/sherlock.txt 19 | if err != nil { 20 | log.Fatalf("error: %s", err) 21 | } 22 | defer file.Close() 23 | 24 | w, err := mostCommon(file) 25 | if err != nil { 26 | log.Fatalf("error: %s", err) 27 | } 28 | fmt.Println(w) 29 | // mapDemo() 30 | 31 | /* 32 | // path := "C:\to\new\report.csv" 33 | // `s` is a "raw" string, \ is just a \ 34 | path := `C:\to\new\report.csv` 35 | fmt.Println(path) 36 | */ 37 | } 38 | 39 | func mostCommon(r io.Reader) (string, error) { 40 | freqs, err := wordFrequency(r) 41 | if err != nil { 42 | return "", err 43 | } 44 | return maxWord(freqs) 45 | } 46 | 47 | /* You can also use raw strings to create mutli lines strings 48 | var request = `GET /ip HTTP/1.1 49 | Host: httpbin.org 50 | Connection: Close 51 | 52 | ` 53 | */ 54 | 55 | // "Who's on first?" -> [Who s on first] 56 | var wordRe = regexp.MustCompile(`[a-zA-Z]+`) 57 | 58 | /* Will run before main 59 | func init() { 60 | // ... 61 | } 62 | */ 63 | 64 | func mapDemo() { 65 | var stocks map[string]float64 // symbol -> price 66 | sym := "TTWO" 67 | price := stocks[sym] 68 | fmt.Printf("%s -> $%.2f\n", sym, price) 69 | 70 | if price, ok := stocks[sym]; ok { 71 | fmt.Printf("%s -> $%.2f\n", sym, price) 72 | } else { 73 | fmt.Printf("%s not found\n", sym) 74 | } 75 | 76 | /* 77 | stocks = make(map[string]float64) 78 | stocks[sym] = 136.73 79 | */ 80 | stocks = map[string]float64{ 81 | sym: 137.73, 82 | "AAPL": 172.35, 83 | } 84 | if price, ok := stocks[sym]; ok { 85 | fmt.Printf("%s -> $%.2f\n", sym, price) 86 | } else { 87 | fmt.Printf("%s not found\n", sym) 88 | } 89 | 90 | for k := range stocks { // keys 91 | fmt.Println(k) 92 | } 93 | 94 | for k, v := range stocks { // key & value 95 | fmt.Println(k, "->", v) 96 | } 97 | 98 | for _, v := range stocks { // values 99 | fmt.Println(v) 100 | } 101 | 102 | delete(stocks, "AAPL") 103 | fmt.Println(stocks) 104 | delete(stocks, "AAPL") // no panic 105 | } 106 | 107 | func maxWord(freqs map[string]int) (string, error) { 108 | if len(freqs) == 0 { 109 | return "", fmt.Errorf("empty map") 110 | } 111 | 112 | maxN, maxW := 0, "" 113 | for word, count := range freqs { 114 | if count > maxN { 115 | maxN, maxW = count, word 116 | } 117 | } 118 | 119 | return maxW, nil 120 | } 121 | 122 | func wordFrequency(r io.Reader) (map[string]int, error) { 123 | s := bufio.NewScanner(r) 124 | freqs := make(map[string]int) // word -> count 125 | for s.Scan() { 126 | words := wordRe.FindAllString(s.Text(), -1) // current line 127 | for _, w := range words { 128 | freqs[strings.ToLower(w)]++ 129 | } 130 | } 131 | if err := s.Err(); err != nil { 132 | return nil, err 133 | } 134 | 135 | return freqs, nil 136 | } 137 | -------------------------------------------------------------------------------- /game/game.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/slices" 7 | ) 8 | 9 | func main() { 10 | var i1 Item 11 | fmt.Println(i1) 12 | fmt.Printf("i1: %#v\n", i1) 13 | 14 | i2 := Item{1, 2} 15 | fmt.Printf("i2: %#v\n", i2) 16 | 17 | i3 := Item{ 18 | Y: 10, 19 | // X: 20, 20 | } 21 | fmt.Printf("i3: %#v\n", i3) 22 | fmt.Println(NewItem(10, 20)) 23 | fmt.Println(NewItem(10, -20)) 24 | 25 | i3.Move(100, 200) 26 | fmt.Printf("i3 (move): %#v\n", i3) 27 | 28 | p1 := Player{ 29 | Name: "Parzival", 30 | Item: Item{500, 300}, 31 | } 32 | fmt.Printf("p1: %#v\n", p1) 33 | fmt.Printf("p1.X: %#v\n", p1.X) 34 | fmt.Printf("p1.Item.X: %#v\n", p1.Item.X) 35 | p1.Move(400, 600) 36 | fmt.Printf("p1 (move): %#v\n", p1) 37 | 38 | ms := []mover{ 39 | &i1, 40 | &p1, 41 | &i2, 42 | } 43 | moveAll(ms, 0, 0) 44 | for _, m := range ms { 45 | fmt.Println(m) 46 | } 47 | 48 | k := Jade 49 | fmt.Println("k:", k) 50 | fmt.Println("key:", Key(17)) 51 | 52 | // time.Time import json.Marshaler interface 53 | // json.NewEncoder(os.Stdout).Encode(time.Now()) 54 | 55 | p1.FoundKey(Jade) 56 | fmt.Println(p1.Keys) 57 | p1.FoundKey(Jade) 58 | fmt.Println(p1.Keys) 59 | } 60 | 61 | // Implement fmt.Stringer interface 62 | func (k Key) String() string { 63 | switch k { 64 | case Jade: 65 | return "jade" 66 | case Copper: 67 | return "copper" 68 | case Crystal: 69 | return "crystal" 70 | } 71 | 72 | return fmt.Sprintf("", k) 73 | } 74 | 75 | /* Exercise 76 | - Add a "Keys" field to Player which is a slice of Key 77 | - Add a "FoundKey(k Key) error" method to player which will add k to Key if it's not there 78 | - Err if k is not one of the known keys 79 | */ 80 | 81 | // Go's version of "enum" 82 | const ( 83 | Jade Key = iota + 1 84 | Copper 85 | Crystal 86 | invalidKey // internal (not exported) 87 | ) 88 | 89 | type Key byte 90 | 91 | /* go >= 1.18 92 | func NewNumber[T int | float64](kind string) T { 93 | if kind == "int" { 94 | return 0 95 | } 96 | return 0.0 97 | } 98 | */ 99 | 100 | // Rule of thumb: Accept interfaces, return types 101 | 102 | func moveAll(ms []mover, x, y int) { 103 | for _, m := range ms { 104 | m.Move(x, y) 105 | } 106 | } 107 | 108 | type mover interface { 109 | Move(x, y int) 110 | // Move(int, int) 111 | } 112 | 113 | func (p *Player) FoundKey(k Key) error { 114 | if k < Jade || k >= invalidKey { 115 | return fmt.Errorf("invalid key: %#v", k) 116 | } 117 | 118 | // if !containsKey(p.Keys, k) { 119 | if !slices.Contains(p.Keys, k) { 120 | p.Keys = append(p.Keys, k) 121 | } 122 | 123 | return nil 124 | } 125 | 126 | func containsKey(keys []Key, k Key) bool { 127 | for _, k2 := range keys { 128 | if k2 == k { 129 | return true 130 | } 131 | } 132 | return false 133 | } 134 | 135 | type Player struct { 136 | Name string 137 | // X int 138 | Item // Embed Item 139 | // T 140 | Keys []Key 141 | } 142 | 143 | /* 144 | type T struct { 145 | X int 146 | } 147 | */ 148 | 149 | // i is called "the receiver" 150 | // if you want to mutate, use pointer receiver 151 | func (i *Item) Move(x, y int) { 152 | i.X = x 153 | i.Y = y 154 | } 155 | 156 | // func NewItem(x, y int) Item { 157 | // func NewItem(x, y int) *Item { 158 | // func NewItem(x, y int) (Item, error) { 159 | func NewItem(x, y int) (*Item, error) { 160 | if x < 0 || x > maxX || y < 0 || y > maxY { 161 | return nil, fmt.Errorf("%d/%d out of bounds %d/%d", x, y, maxX, maxY) 162 | } 163 | 164 | i := Item{ 165 | X: x, 166 | Y: y, 167 | } 168 | // The Go compiler does "escape analysis" and will allocation i on the heap 169 | return &i, nil 170 | } 171 | 172 | // zero vs missing value 173 | 174 | const ( 175 | maxX = 1000 176 | maxY = 600 177 | ) 178 | 179 | // Item is an item in the game 180 | type Item struct { 181 | X int 182 | Y int 183 | } 184 | -------------------------------------------------------------------------------- /github/github.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "net/url" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Millisecond) 14 | defer cancel() 15 | fmt.Println(githubInfo(ctx, "tebeka")) 16 | } 17 | 18 | // githubInfo returns name and number of public repos for login 19 | func githubInfo(ctx context.Context, login string) (string, int, error) { 20 | url := "https://api.github.com/users/" + url.PathEscape(login) 21 | // resp,err := http.Get(url) 22 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 23 | if err != nil { 24 | return "", 0, err 25 | } 26 | 27 | resp, err := http.DefaultClient.Do(req) 28 | if err != nil { 29 | return "", 0, err 30 | } 31 | 32 | if resp.StatusCode != http.StatusOK { 33 | return "", 0, fmt.Errorf("%#v - %s", url, resp.Status) 34 | } 35 | 36 | defer resp.Body.Close() 37 | 38 | // fmt.Printf("Content-Type: %s\n", resp.Header.Get("Content-Type")) 39 | // var r Reply 40 | 41 | var r struct { // anonymous struct 42 | Name string 43 | // Public_Repos int 44 | NumRepos int `json:"public_repos"` 45 | } 46 | dec := json.NewDecoder(resp.Body) 47 | if err := dec.Decode(&r); err != nil { 48 | return "", 0, err 49 | } 50 | 51 | return r.Name, r.NumRepos, nil 52 | } 53 | 54 | /* 55 | type Reply struct { 56 | Name string 57 | // Public_Repos int 58 | NumRepos int `json:"public_repos"` 59 | } 60 | */ 61 | 62 | /* JSON <-> Go 63 | true/false <-> true/false 64 | string <-> string 65 | null <-> nil 66 | number <-> float64, float32, int8, int16, int32, int64, int, uint8, ... 67 | array <-> []any ([]interface{}) 68 | object <-> map[string]any, struct 69 | 70 | encoding/json API 71 | JSON -> io.Reader -> Go: json.Decoder 72 | JSON -> []byte -> Go: json.Unmarshal 73 | Go -> io.Writer -> JSON: json.Encoder 74 | Go -> []byte -> JSON: json.Marshal 75 | */ 76 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ardanlabs/practical-go 2 | 3 | go 1.19 4 | 5 | require golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4= 2 | golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 3 | -------------------------------------------------------------------------------- /go_chan/go_chan.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | go fmt.Println("goroutine") 10 | fmt.Println("main") 11 | 12 | for i := 0; i < 3; i++ { 13 | // Fix 2: Use a loop body variable 14 | i := i // "i" shadows "i" from the for loop 15 | go func() { 16 | fmt.Println(i) // i from line 14 17 | }() 18 | 19 | /* Fix 1: Use a parameter 20 | go func(n int) { 21 | fmt.Println(n) 22 | }(i) 23 | */ 24 | /* BUG: All goroutines use the "i" for the for loop 25 | go func() { 26 | fmt.Println(i) // i from line 12 27 | }() 28 | */ 29 | } 30 | 31 | time.Sleep(10 * time.Millisecond) 32 | 33 | shadowExample() 34 | 35 | ch := make(chan string) 36 | go func() { 37 | ch <- "hi" // send 38 | }() 39 | msg := <-ch // receive 40 | fmt.Println(msg) 41 | 42 | go func() { 43 | for i := 0; i < 3; i++ { 44 | msg := fmt.Sprintf("message #%d", i+1) 45 | ch <- msg 46 | } 47 | close(ch) 48 | }() 49 | 50 | for msg := range ch { 51 | fmt.Println("got:", msg) 52 | } 53 | 54 | /* for/range does this 55 | for { 56 | msg, ok := <-ch 57 | if !ok { 58 | break 59 | } 60 | fmt.Println("got:", msg) 61 | } 62 | */ 63 | 64 | msg = <-ch // ch is closed 65 | fmt.Printf("closed: %#v\n", msg) 66 | 67 | msg, ok := <-ch // ch is closed 68 | fmt.Printf("closed: %#v (ok=%v)\n", msg, ok) 69 | 70 | // ch <- "hi" // ch is closed -> panic 71 | values := []int{15, 8, 42, 16, 4, 23} 72 | fmt.Println(sleepSort(values)) 73 | } 74 | 75 | /* 76 | For every value "n" in values, spin a goroutine that will 77 | - sleep "n" milliseconds 78 | - Send "n" over a channel 79 | 80 | In the function body, collect values from the channel to a slice and return it. 81 | */ 82 | 83 | func sleepSort(values []int) []int { 84 | ch := make(chan int) 85 | for _, n := range values { 86 | n := n 87 | go func() { 88 | time.Sleep(time.Duration(n) * time.Millisecond) 89 | ch <- n 90 | }() 91 | } 92 | 93 | var out []int 94 | // for i := 0; i < len(values); i++ { 95 | for range values { 96 | n := <-ch 97 | out = append(out, n) 98 | } 99 | return out 100 | } 101 | 102 | /* Channel semantics 103 | - send & receive will block until opposite operation (*) 104 | - Buffered channel has cap(ch) non-blocking sends 105 | - receive from a closed channel will return the zero value without blocking 106 | - send to a closed channel will panic 107 | - closing a closed channel will panic 108 | - send/receive to a nil channel will block forever 109 | 110 | See also https://www.353solutions.com/channel-semantics 111 | */ 112 | 113 | func shadowExample() { 114 | n := 7 115 | { 116 | n := 2 // from here to } this is "n" 117 | // n = 2 // outer n 118 | fmt.Println("inner", n) 119 | } 120 | fmt.Println("outer", n) 121 | } 122 | -------------------------------------------------------------------------------- /go_chan/sleep_sort.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for n in $@; do 4 | (sleep $n && echo $n)& 5 | done 6 | wait -------------------------------------------------------------------------------- /hw/hw.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // import "fmt" 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | fmt.Println("Hello Gophers ☺") 10 | } 11 | -------------------------------------------------------------------------------- /nlp/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Placed in public domain by Miki -------------------------------------------------------------------------------- /nlp/README.md: -------------------------------------------------------------------------------- 1 | # nlp - Natural Language Processing for Go 2 | 3 | ... 4 | 5 | 6 | ## Hacking 7 | 8 | To run the tests ... -------------------------------------------------------------------------------- /nlp/cmd/nlpd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "expvar" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net/http" 10 | "os" 11 | 12 | "github.com/gorilla/mux" 13 | 14 | "github.com/353solutions/nlp" 15 | "github.com/353solutions/nlp/stemmer" 16 | ) 17 | 18 | var ( 19 | numTok = expvar.NewInt("tokenize.calls") 20 | ) 21 | 22 | func main() { 23 | 24 | // Create server 25 | logger := log.New(log.Writer(), "[nlpd] ", log.LstdFlags|log.Lshortfile) 26 | s := Server{ 27 | logger: logger, // dependency injection 28 | } 29 | // routing 30 | // /health is an exact match 31 | // /health/ is a prefix match 32 | /* 33 | http.HandleFunc("/health", healthHandler) 34 | http.HandleFunc("/tokenize", tokenizeHandler) 35 | */ 36 | r := mux.NewRouter() 37 | r.HandleFunc("/health", s.healthHandler).Methods(http.MethodGet) 38 | r.HandleFunc("/tokenize", s.tokenizeHandler).Methods(http.MethodPost) 39 | r.HandleFunc("/stem/{word}", s.stemHandler).Methods(http.MethodGet) 40 | http.Handle("/", r) 41 | 42 | // run server 43 | addr := os.Getenv("NLPD_ADDR") 44 | if addr == "" { 45 | addr = ":8080" 46 | } 47 | s.logger.Printf("server starting on %s", addr) 48 | if err := http.ListenAndServe(addr, nil); err != nil { 49 | log.Fatalf("error: %s", err) 50 | } 51 | } 52 | 53 | type Server struct { 54 | logger *log.Logger 55 | } 56 | 57 | func (s *Server) stemHandler(w http.ResponseWriter, r *http.Request) { 58 | vars := mux.Vars(r) 59 | word := vars["word"] 60 | stem := stemmer.Stem(word) 61 | fmt.Fprintln(w, stem) 62 | } 63 | 64 | func (s *Server) tokenizeHandler(w http.ResponseWriter, r *http.Request) { 65 | /* Before gorilla/mux 66 | if r.Method != http.MethodPost { 67 | http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 68 | return 69 | } 70 | */ 71 | 72 | numTok.Add(1) 73 | 74 | // Step 1: Get, convert & validate the data 75 | rdr := io.LimitReader(r.Body, 1_000_000) 76 | data, err := io.ReadAll(rdr) 77 | if err != nil { 78 | s.logger.Printf("error: can't read - %s", err) 79 | http.Error(w, "can't read", http.StatusBadRequest) 80 | return 81 | } 82 | 83 | if len(data) == 0 { 84 | http.Error(w, "missing data", http.StatusBadRequest) 85 | return 86 | } 87 | 88 | text := string(data) 89 | 90 | // Step 2: Work 91 | tokens := nlp.Tokenize(text) 92 | 93 | // Step 3: Encode & emit output 94 | resp := map[string]any{ 95 | "tokens": tokens, 96 | } 97 | // You can also do: 98 | // err = json.NewEncoder(w).Encode(resp) 99 | data, err = json.Marshal(resp) 100 | if err != nil { 101 | http.Error(w, "can't encode", http.StatusInternalServerError) 102 | return 103 | } 104 | 105 | w.Header().Set("Content-Type", "application/json") 106 | w.Write(data) 107 | } 108 | 109 | // exercise: Write tokenizeHandler that will read the text form r.Body and 110 | // return JSON in the format: "{"tokens": ["who", "on", "first"]} 111 | 112 | func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { 113 | // TODO: Run a health check 114 | fmt.Fprintln(w, "OK") 115 | } 116 | -------------------------------------------------------------------------------- /nlp/cmd/nlpd/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestHealth(t *testing.T) { 13 | w := httptest.NewRecorder() 14 | r := httptest.NewRequest(http.MethodGet, "/health", nil) 15 | 16 | s := Server{logger: log.Default()} 17 | // Note: This bypasses routing & middleware 18 | s.healthHandler(w, r) 19 | 20 | resp := w.Result() 21 | require.Equal(t, http.StatusOK, resp.StatusCode) 22 | } 23 | -------------------------------------------------------------------------------- /nlp/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package nlp provides natural language processing utilities. 3 | 4 | ... 5 | */ 6 | package nlp 7 | -------------------------------------------------------------------------------- /nlp/example_test.go: -------------------------------------------------------------------------------- 1 | package nlp_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/353solutions/nlp" 7 | ) 8 | 9 | func ExampleTokenize() { 10 | text := "Who's on first?" 11 | tokens := nlp.Tokenize(text) 12 | fmt.Println(tokens) 13 | 14 | // Output: 15 | // [who on first] 16 | } 17 | 18 | /* 19 | Test discovery: 20 | For every file ending with _test.go, run every function that matches either: 21 | - Example[A-Z_].*, body must include // Output: comment 22 | - Test[A-Z_].* 23 | */ 24 | -------------------------------------------------------------------------------- /nlp/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/353solutions/nlp 2 | 3 | go 1.19 4 | 5 | require ( 6 | // Prod requirements 7 | github.com/gorilla/mux v1.8.0 8 | 9 | // Dev requirements 10 | github.com/BurntSushi/toml v1.2.0 11 | github.com/stretchr/testify v1.8.0 12 | ) 13 | 14 | 15 | require ( 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | gopkg.in/yaml.v3 v3.0.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /nlp/go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= 2 | github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 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/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 7 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 8 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 10 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 11 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 12 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 13 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 14 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 18 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 19 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /nlp/nlp.go: -------------------------------------------------------------------------------- 1 | package nlp 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "github.com/353solutions/nlp/stemmer" 8 | ) 9 | 10 | var ( 11 | wordRe = regexp.MustCompile(`[a-zA-Z]+`) 12 | ) 13 | 14 | // Tokenize returns list of (lower case) tokens found in text. 15 | func Tokenize(text string) []string { 16 | words := wordRe.FindAllString(text, -1) 17 | var tokens []string 18 | for _, w := range words { 19 | token := strings.ToLower(w) 20 | token = stemmer.Stem(token) 21 | if len(token) != 0 { 22 | tokens = append(tokens, token) 23 | } 24 | } 25 | return tokens 26 | } 27 | -------------------------------------------------------------------------------- /nlp/nlp_test.go: -------------------------------------------------------------------------------- 1 | package nlp 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/BurntSushi/toml" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | /* 12 | var tokenizeCases = []struct { // anonymous struct 13 | text string 14 | tokens []string 15 | }{ 16 | {"Who's on first?", []string{"who", "s", "on", "first"}}, 17 | {"", nil}, 18 | } 19 | */ 20 | 21 | type tokenizeCase struct { 22 | Text string 23 | Tokens []string 24 | } 25 | 26 | func loadTokenizeCases(t *testing.T) []tokenizeCase { 27 | /* 28 | data, err := ioutil.ReadFile("tokenize_cases.toml") 29 | require.NoError(t, err, "Read file") 30 | */ 31 | 32 | var testCases struct { 33 | Cases []tokenizeCase 34 | } 35 | 36 | // err = toml.Unmarshal(data, &testCases) 37 | _, err := toml.DecodeFile("testdata/tokenize_cases.toml", &testCases) 38 | require.NoError(t, err, "Unmarshal TOML") 39 | return testCases.Cases 40 | } 41 | 42 | // Exercise: Read test cases from tokenize_cases.toml 43 | // Use github.com/BurntSushi/toml to read TOML 44 | 45 | func TestTokenizeTable(t *testing.T) { 46 | // for _, tc := range tokenizeCases { 47 | for _, tc := range loadTokenizeCases(t) { 48 | t.Run(tc.Text, func(t *testing.T) { 49 | tokens := Tokenize(tc.Text) 50 | require.Equal(t, tc.Tokens, tokens) 51 | }) 52 | } 53 | } 54 | 55 | func TestTokenize(t *testing.T) { 56 | text := "What's on second?" 57 | expected := []string{"what", "on", "second"} 58 | tokens := Tokenize(text) 59 | require.Equal(t, expected, tokens) 60 | /* Before testify 61 | // if tokens != expected { // Can't compare slices with == in Go (only to nil) 62 | if !reflect.DeepEqual(expected, tokens) { 63 | t.Fatalf("expected %#v, got %#v", expected, tokens) 64 | } 65 | */ 66 | } 67 | 68 | func FuzzTokenize(f *testing.F) { 69 | f.Fuzz(func(t *testing.T, text string) { 70 | tokens := Tokenize(text) 71 | lText := strings.ToLower(text) 72 | for _, tok := range tokens { 73 | if !strings.Contains(lText, tok) { 74 | t.Fatal(tok) 75 | } 76 | } 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /nlp/stemmer/stemmer.go: -------------------------------------------------------------------------------- 1 | package stemmer 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | var ( 8 | suffixes = []string{"s", "ing", "ed"} 9 | ) 10 | 11 | func Stem(word string) string { 12 | for _, suffix := range suffixes { 13 | if strings.HasSuffix(word, suffix) { 14 | return word[:len(word)-len(suffix)] 15 | } 16 | } 17 | return word 18 | } 19 | -------------------------------------------------------------------------------- /nlp/stemmer/stemmer_test.go: -------------------------------------------------------------------------------- 1 | package stemmer 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | var testCases = []struct { 11 | word string 12 | stem string 13 | }{ 14 | {"working", "work"}, 15 | {"works", "work"}, 16 | {"worked", "work"}, 17 | {"work", "work"}, 18 | } 19 | 20 | func TestStem(t *testing.T) { 21 | for _, tc := range testCases { 22 | name := fmt.Sprintf("%s:%s", tc.word, tc.stem) 23 | t.Run(name, func(t *testing.T) { 24 | stem := Stem(tc.word) 25 | require.Equal(t, tc.stem, stem) 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /nlp/testdata/tokenize_cases.toml: -------------------------------------------------------------------------------- 1 | [[cases]] 2 | text = "Who's on first?" 3 | tokens = ["who", "on", "first"] 4 | 5 | [[cases]] 6 | text = "What's on second?" 7 | tokens = ["what", "on", "second"] 8 | 9 | [[cases]] 10 | text = "" 11 | -------------------------------------------------------------------------------- /payment/payment.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | p := Payment{ 11 | From: "Wile. E. Coyote", 12 | To: "ACME", 13 | Amount: 123.34, 14 | } 15 | p.Process() 16 | p.Process() 17 | } 18 | 19 | func (p *Payment) Process() { 20 | t := time.Now() 21 | p.once.Do(func() { p.process(t) }) 22 | } 23 | 24 | func (p *Payment) process(t time.Time) { 25 | ts := t.Format(time.RFC3339) 26 | fmt.Printf("[%s] %s -> $%.2f -> %s\n", ts, p.From, p.Amount, p.To) 27 | } 28 | 29 | type Payment struct { 30 | From string 31 | To string 32 | Amount float64 // USD 33 | 34 | once sync.Once 35 | } 36 | -------------------------------------------------------------------------------- /rtb/rtb.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | // We have 50 msec to return an answer 12 | ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) 13 | defer cancel() 14 | url := "https://go.dev" 15 | bid := bidOn(ctx, url) 16 | fmt.Println(bid) 17 | } 18 | 19 | // If algo didn't finish in time, return a default bid 20 | func bidOn(ctx context.Context, url string) Bid { 21 | ch := make(chan Bid, 1) // buffered channel to avoid goroutine leak 22 | go func() { 23 | ch <- bestBid(url) 24 | }() 25 | 26 | select { 27 | case bid := <-ch: 28 | return bid 29 | case <-ctx.Done(): 30 | return defaultBid 31 | } 32 | } 33 | 34 | var defaultBid = Bid{ 35 | AdURL: "http://adsЯus.com/default", 36 | Price: 3, 37 | } 38 | 39 | // Written by Algo team, time to completion varies 40 | func bestBid(url string) Bid { 41 | // Simulate work 42 | d := 100 * time.Millisecond 43 | if strings.HasPrefix(url, "https://") { 44 | d = 20 * time.Millisecond 45 | } 46 | time.Sleep(d) 47 | 48 | return Bid{ 49 | AdURL: "http://adsЯus.com/ad17", 50 | Price: 7, 51 | } 52 | } 53 | 54 | type Bid struct { 55 | AdURL string 56 | Price int // In ¢ 57 | } 58 | -------------------------------------------------------------------------------- /select/select.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | ch1, ch2 := make(chan int), make(chan int) 11 | 12 | go func() { 13 | time.Sleep(10 * time.Millisecond) 14 | ch1 <- 1 15 | }() 16 | 17 | go func() { 18 | time.Sleep(20 * time.Millisecond) 19 | ch2 <- 2 20 | }() 21 | 22 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) 23 | defer cancel() 24 | 25 | select { 26 | case val := <-ch1: 27 | fmt.Println("ch1:", val) 28 | case val := <-ch2: 29 | fmt.Println("ch2:", val) 30 | // case <-time.After(5 * time.Millisecond): 31 | case <-ctx.Done(): 32 | fmt.Println("timeout") 33 | } 34 | 35 | // select {} // blocks forever without consumit CPU 36 | } 37 | -------------------------------------------------------------------------------- /sha1/sha1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "compress/gzip" 5 | "crypto/sha1" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | func main() { 14 | 15 | /* 16 | a := 1 17 | { 18 | // a := 2 // shadows outer a 19 | a = 2 // change outer a 20 | fmt.Println("inner", a) // affect only innter a 21 | } 22 | fmt.Println("outer", a) 23 | return 24 | */ 25 | 26 | sig, err := sha1Sum("http.log.gz") 27 | if err != nil { 28 | log.Fatalf("error: %s", err) 29 | } 30 | fmt.Println(sig) 31 | 32 | sig, err = sha1Sum("sha1.go") 33 | if err != nil { 34 | log.Fatalf("error: %s", err) 35 | } 36 | fmt.Println(sig) 37 | } 38 | 39 | /* 40 | if file names ends with .gz 41 | 42 | $ cat http.log.gz| gunzip | sha1sum 43 | 44 | else 45 | 46 | $ cat http.log.gz| sha1sum 47 | */ 48 | func sha1Sum(fileName string) (string, error) { 49 | // idiom: acquire a resource, check for error, defer release 50 | file, err := os.Open(fileName) 51 | if err != nil { 52 | return "", nil 53 | } 54 | defer file.Close() // deferred are called in LIFO order 55 | var r io.Reader = file 56 | 57 | if strings.HasSuffix(fileName, ".gz") { 58 | gz, err := gzip.NewReader(file) 59 | if err != nil { 60 | return "", err 61 | } 62 | defer gz.Close() 63 | r = gz 64 | } 65 | 66 | // io.CopyN(os.Stdout, r, 100) 67 | w := sha1.New() 68 | 69 | if _, err := io.Copy(w, r); err != nil { 70 | return "", err 71 | } 72 | 73 | sig := w.Sum(nil) 74 | return fmt.Sprintf("%x", sig), nil 75 | } 76 | -------------------------------------------------------------------------------- /sites_time/sites_time.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net/http" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | urls := []string{ 13 | "https://google.com", 14 | "https://apple.com", 15 | "https://no-such-site.biz", 16 | } 17 | 18 | var wg sync.WaitGroup 19 | wg.Add(len(urls)) 20 | for _, url := range urls { 21 | // wg.Add(1) 22 | url := url 23 | go func() { 24 | defer wg.Done() 25 | siteTime(url) 26 | }() 27 | } 28 | wg.Wait() 29 | } 30 | 31 | func siteTime(url string) { 32 | start := time.Now() 33 | 34 | resp, err := http.Get(url) 35 | if err != nil { 36 | log.Printf("ERROR: %s -> %s", url, err) 37 | return 38 | } 39 | defer resp.Body.Close() 40 | if _, err := io.Copy(io.Discard, resp.Body); err != nil { 41 | log.Printf("ERROR: %s -> %s", url, err) 42 | } 43 | 44 | duration := time.Since(start) 45 | log.Printf("INFO: %s -> %v", url, duration) 46 | } 47 | -------------------------------------------------------------------------------- /slices/slices.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | ) 7 | 8 | func main() { 9 | var s []int // s is a slice of int 10 | fmt.Println("len", len(s)) // len is "nil safe" 11 | if s == nil { // you can compare only a slice to nil 12 | fmt.Println("nil slice") 13 | } 14 | 15 | s2 := []int{1, 2, 3, 4, 5, 6, 7} 16 | fmt.Printf("s2 = %#v\n", s2) 17 | 18 | s3 := s2[1:4] // slicing operation, half-open range 19 | fmt.Printf("s3 = %#v\n", s3) 20 | 21 | // fmt.Println(s2[:100]) // panic 22 | s3 = append(s3, 100) 23 | fmt.Printf("s3 (append) = %#v\n", s3) 24 | fmt.Printf("s2 (append)= %#v\n", s2) // s2 is changed as well! 25 | fmt.Printf("s2: len=%d, cap=%d\n", len(s2), cap(s2)) 26 | fmt.Printf("s3: len=%d, cap=%d\n", len(s3), cap(s3)) 27 | 28 | var s4 []int 29 | // s4 := make([]int, 0, 1_000) // Single allocation 30 | for i := 0; i < 1_000; i++ { 31 | s4 = appendInt(s4, i) 32 | } 33 | fmt.Println("s4", len(s4), cap(s4)) 34 | // s4[1001] = 7 // panic 35 | 36 | fmt.Println(concat([]string{"A", "B"}, []string{"C", "D", "E"})) // [A B C D E] 37 | 38 | vs := []float64{2, 1, 3} 39 | fmt.Println(median(vs)) 40 | vs = []float64{2, 1, 3, 4} 41 | fmt.Println(median(vs)) 42 | fmt.Println(vs) 43 | 44 | fmt.Println(median(nil)) 45 | } 46 | 47 | func median(values []float64) (float64, error) { 48 | if len(values) == 0 { 49 | return 0, fmt.Errorf("median of empty slice") 50 | } 51 | // Copy in order not to change values 52 | nums := make([]float64, len(values)) 53 | copy(nums, values) 54 | 55 | sort.Float64s(nums) 56 | i := len(nums) / 2 57 | // if len(nums)&1 == 1 { 58 | if len(nums)%2 == 1 { 59 | return nums[i], nil 60 | } 61 | 62 | v := (nums[i-1] + nums[i]) / 2 63 | return v, nil 64 | } 65 | 66 | func concat(s1, s2 []string) []string { 67 | s := make([]string, len(s1)+len(s2)) 68 | copy(s, s1) 69 | copy(s[len(s1):], s2) 70 | return s 71 | 72 | // return append(s1, s2...) 73 | } 74 | 75 | func appendInt(s []int, v int) []int { 76 | i := len(s) 77 | 78 | if len(s) < cap(s) { // enough space in underlying array 79 | s = s[:len(s)+1] 80 | } else { // need to re-allocate and copy 81 | fmt.Printf("reallocate: %d->%d\n", len(s), 2*len(s)+1) 82 | s2 := make([]int, 2*len(s)+1) 83 | copy(s2, s) 84 | s = s2[:len(s)+1] 85 | } 86 | 87 | s[i] = v 88 | return s 89 | } 90 | -------------------------------------------------------------------------------- /taxi/taxi_check.go: -------------------------------------------------------------------------------- 1 | /* 2 | Write a function that gets an index file with names of files and sha256 3 | signatures in the following format 4 | 0c4ccc63a912bbd6d45174251415c089522e5c0e75286794ab1f86cb8e2561fd taxi-01.csv 5 | f427b5880e9164ec1e6cda53aa4b2d1f1e470da973e5b51748c806ea5c57cbdf taxi-02.csv 6 | 4e251e9e98c5cb7be8b34adfcb46cc806a4ef5ec8c95ba9aac5ff81449fc630c taxi-03.csv 7 | ... 8 | 9 | You should compute concurrently sha256 signatures of these files and see if 10 | they math the ones in the index file. 11 | 12 | - Print the number of processed files 13 | - If there's a mismatch, print the offending file(s) and exit the program with 14 | non-zero value 15 | 16 | Get taxi-sha256.zip from the web site and open it. The index file is sha256sum.txt 17 | */ 18 | package main 19 | 20 | import ( 21 | "bufio" 22 | "compress/bzip2" 23 | "crypto/sha256" 24 | "fmt" 25 | "io" 26 | "log" 27 | "os" 28 | "path" 29 | "strings" 30 | "time" 31 | ) 32 | 33 | func fileSig(path string) (string, error) { 34 | file, err := os.Open(path) 35 | if err != nil { 36 | return "", err 37 | } 38 | defer file.Close() 39 | 40 | hash := sha256.New() 41 | _, err = io.Copy(hash, bzip2.NewReader(file)) 42 | if err != nil { 43 | return "", err 44 | } 45 | 46 | return fmt.Sprintf("%x", hash.Sum(nil)), nil 47 | } 48 | 49 | // Parse signature file. Return map of path->signature 50 | func parseSigFile(r io.Reader) (map[string]string, error) { 51 | sigs := make(map[string]string) 52 | scanner := bufio.NewScanner(r) 53 | for scanner.Scan() { 54 | // Line example 55 | // 6c6427da7893932731901035edbb9214 nasa-00.log 56 | fields := strings.Fields(scanner.Text()) 57 | if len(fields) != 2 { 58 | // TODO: line number 59 | return nil, fmt.Errorf("bad line: %q", scanner.Text()) 60 | } 61 | sigs[fields[1]] = fields[0] 62 | } 63 | 64 | if err := scanner.Err(); err != nil { 65 | return nil, err 66 | } 67 | 68 | return sigs, nil 69 | } 70 | 71 | func main() { 72 | rootDir := "/tmp/taxi" // Change to where to unzipped taxi-sha256.zip 73 | file, err := os.Open(path.Join(rootDir, "sha256sum.txt")) 74 | if err != nil { 75 | log.Fatalf("error: %s", err) 76 | } 77 | defer file.Close() 78 | 79 | sigs, err := parseSigFile(file) 80 | if err != nil { 81 | log.Fatalf("error: %s", err) 82 | } 83 | 84 | start := time.Now() 85 | ok := true 86 | ch := make(chan result) 87 | for name, signature := range sigs { 88 | fileName := path.Join(rootDir, name) + ".bz2" 89 | // sig, err := fileSig(fileName) 90 | go sigWorker(fileName, signature, ch) 91 | } 92 | 93 | for range sigs { 94 | r := <-ch 95 | if r.err != nil { 96 | fmt.Fprintf(os.Stderr, "error: %s - %s\n", r.fileName, err) 97 | ok = false 98 | continue 99 | } 100 | 101 | if !r.match { 102 | ok = false 103 | fmt.Printf("error: %s mismatch\n", r.fileName) 104 | } 105 | } 106 | 107 | duration := time.Since(start) 108 | fmt.Printf("processed %d files in %v\n", len(sigs), duration) 109 | if !ok { 110 | os.Exit(1) 111 | } 112 | } 113 | 114 | func sigWorker(fileName, signature string, ch chan<- result) { 115 | r := result{fileName: fileName} 116 | sig, err := fileSig(fileName) 117 | if err != nil { 118 | r.err = err 119 | } else { 120 | r.match = sig == signature 121 | } 122 | ch <- r 123 | } 124 | 125 | type result struct { 126 | fileName string 127 | err error 128 | match bool 129 | } 130 | --------------------------------------------------------------------------------