Compare commits

..

11 Commits

34 changed files with 2528 additions and 23 deletions

View File

@@ -7,25 +7,25 @@
"vue": { "vue": {
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js", "src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js", "file": "vue.js",
"fileHash": "9f6650f2", "fileHash": "6eb7142a",
"needsInterop": false "needsInterop": false
}, },
"vitepress > @vue/devtools-api": { "vitepress > @vue/devtools-api": {
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js", "src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js", "file": "vitepress___@vue_devtools-api.js",
"fileHash": "87d887f1", "fileHash": "f77c0dac",
"needsInterop": false "needsInterop": false
}, },
"vitepress > @vueuse/core": { "vitepress > @vueuse/core": {
"src": "../../../../node_modules/@vueuse/core/index.mjs", "src": "../../../../node_modules/@vueuse/core/index.mjs",
"file": "vitepress___@vueuse_core.js", "file": "vitepress___@vueuse_core.js",
"fileHash": "3179a954", "fileHash": "4488c276",
"needsInterop": false "needsInterop": false
}, },
"@theme/index": { "@theme/index": {
"src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js", "src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js",
"file": "@theme_index.js", "file": "@theme_index.js",
"fileHash": "1bd77338", "fileHash": "f16ffef2",
"needsInterop": false "needsInterop": false
} }
}, },

View File

@@ -17,6 +17,8 @@ export default {
"/aws/": require("../aws/sidebar.json"), "/aws/": require("../aws/sidebar.json"),
"/go": require("../go/sidebar.json"), "/go": require("../go/sidebar.json"),
"/react": require("../react/sidebar.json"), "/react": require("../react/sidebar.json"),
"/vue": require("../vue/sidebar.json"),
"/java": require("../java/sidebar.json"),
"/": [ "/": [
{ {
text: "Home", text: "Home",

View File

@@ -0,0 +1,41 @@
# Context
Context is a way of passing information or enforcing timeouts when calling functions throughout your application as a single requests goes from layer to layer. This is usually passed as the first parameter in your functions if being used.
```go
// create an empty context
ctx := context.Background()
// append value to context
ctx = context.WithValue(ctx, "key", "value")
value := ctx.Value("key")
```
## Timeouts
```go
ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
defer cancel() // can call before timeout
go longRunningFunc(ctx)
select {
case <-ctx.Done():
fms.Println("done blocking main thread also!")
}
time.Sleep(2*time.Second)
func longRunningFunc(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("timed out")
return
default:
fmt.Println("still processing")
}
time.Sleep(1*time.Second)
}
}
```

555
docs/go/advanced/db.md Normal file
View File

@@ -0,0 +1,555 @@
# Databases
## Postgres
```bash
go get github.com/jackc/pgconn
go get github.com/jackc/pgx/v4
go get github.com/jackc/pgx/v4/stdlib
```
```go
// models.go
package data
import (
"context"
"database/sql"
"errors"
"log"
"time"
"golang.org/x/crypto/bcrypt"
)
const dbTimeout = time.Second * 3
var db *sql.DB
type PostgresRepository struct {
Conn *sql.DB
}
func NewPostgresRepository(conn *sql.DB) *PostgresRepository {
db = conn
return &PostgresRepository{
Conn: conn,
}
}
type User struct {
ID int `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Password string `json:"-"`
Active int `json:"active"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// GetAll returns a slice of all users, sorted by last name
func (u *PostgresRepository) GetAll() ([]*User, error) {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at
from users order by last_name`
rows, err := u.Conn.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var users []*User
for rows.Next() {
var user User
err := rows.Scan(
&user.ID,
&user.Email,
&user.FirstName,
&user.LastName,
&user.Password,
&user.Active,
&user.CreatedAt,
&user.UpdatedAt,
)
if err != nil {
log.Println("Error scanning", err)
return nil, err
}
users = append(users, &user)
}
return users, nil
}
// GetByEmail returns one user by email
func (u *PostgresRepository) GetByEmail(email string) (*User, error) {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where email = $1`
var user User
row := db.QueryRowContext(ctx, query, email)
err := row.Scan(
&user.ID,
&user.Email,
&user.FirstName,
&user.LastName,
&user.Password,
&user.Active,
&user.CreatedAt,
&user.UpdatedAt,
)
if err != nil {
return nil, err
}
return &user, nil
}
// GetOne returns one user by id
func (u *PostgresRepository) GetOne(id int) (*User, error) {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where id = $1`
var user User
row := db.QueryRowContext(ctx, query, id)
err := row.Scan(
&user.ID,
&user.Email,
&user.FirstName,
&user.LastName,
&user.Password,
&user.Active,
&user.CreatedAt,
&user.UpdatedAt,
)
if err != nil {
return nil, err
}
return &user, nil
}
// Update updates one user in the database, using the information
// stored in the receiver u
func (u *PostgresRepository) Update(user User) error {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
stmt := `update users set
email = $1,
first_name = $2,
last_name = $3,
user_active = $4,
updated_at = $5
where id = $6
`
_, err := db.ExecContext(ctx, stmt,
user.Email,
user.FirstName,
user.LastName,
user.Active,
time.Now(),
user.ID,
)
if err != nil {
return err
}
return nil
}
// DeleteByID deletes one user from the database, by ID
func (u *PostgresRepository) DeleteByID(id int) error {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
stmt := `delete from users where id = $1`
_, err := db.ExecContext(ctx, stmt, id)
if err != nil {
return err
}
return nil
}
// Insert inserts a new user into the database, and returns the ID of the newly inserted row
func (u *PostgresRepository) Insert(user User) (int, error) {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
if err != nil {
return 0, err
}
var newID int
stmt := `insert into users (email, first_name, last_name, password, user_active, created_at, updated_at)
values ($1, $2, $3, $4, $5, $6, $7) returning id`
err = db.QueryRowContext(ctx, stmt,
user.Email,
user.FirstName,
user.LastName,
hashedPassword,
user.Active,
time.Now(),
time.Now(),
).Scan(&newID)
if err != nil {
return 0, err
}
return newID, nil
}
```
```go
// main.go, instantiate connection and repo
package main
import (
"authentication/data"
_ "github.com/jackc/pgconn"
_ "github.com/jackc/pgx/v4"
_ "github.com/jackc/pgx/v4/stdlib"
)
func connectToDB() *sql.DB {
dsn := os.Getenv("DSN")
for {
conn, err := openDB(dsn)
if err != nil {
log.Println("DB not ready yet...")
counts++
} else {
log.Println("Connected to DB!")
return conn
}
if counts > 10 {
log.Println(err)
return nil
}
log.Println("Backing off for 2 seconds...")
time.Sleep(time.Second * 2)
continue
}
}
func (app *Config) setupRepo(conn *sql.DB) {
db := data.NewPostgresRepository(conn)
app.Repo = db
}
```
### Testing
It is best to use a repository pattern when dealing with databases, because it allows you to implement a mock repo that implements all the same functions as the real repo with mock return values.
```go
// repository.go
package data
type Repository interface {
GetAll() ([]*User, error)
GetByEmail(email string) (*User, error)
GetOne(id int) (*User, error)
Insert(user User) (int, error)
Update(user User) error
DeleteByID(id int) error
ResetPassword(password string, user User) error
PasswordMatches(password string, user User) (bool, error)
}
```
```go
package data
import (
"database/sql"
"time"
)
type PostgresTestRepository struct {
Conn *sql.DB
}
func NewPostgresTestRepository(conn *sql.DB) *PostgresTestRepository {
return &PostgresTestRepository{
Conn: conn,
}
}
// Implement all methods of Repository interface
func (repo *PostgresTestRepository) GetAll() ([]*User, error) {
users := []*User{}
return users, nil
}
```
```go
// setup_test.go
package main
import (
"authentication/data"
"os"
"testing"
)
var testApp Config
func TestMain(m *testing.M) {
repo := data.NewPostgresTestRepository(nil)
testApp.Repo = repo
os.Exit(m.Run())
}
```
## Mongo
```bash
go get go.mongodb.org/mongo-driver/mongo
go get go.mongodb.org/mongo-driver/mongo/options
```
```go
// models.go
package data
import (
"context"
"log"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var client *mongo.Client
func New(mongo *mongo.Client) Models {
client = mongo
return Models{
LogEntry: LogEntry{},
}
}
type Models struct {
LogEntry LogEntry
}
type LogEntry struct {
ID string `bson:"_id,omitempty" json:"id,omitempty"`
Name string `bson:"name" json:"name"`
Data string `bson:"data" json:"data"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
UpdatedAt time.Time `bson:"updated_at" json:"updated_at"`
}
func (l *LogEntry) Insert(entry LogEntry) error {
collection := client.Database("logs").Collection("logs")
_, err := collection.InsertOne(context.TODO(), LogEntry{
Name: entry.Name,
Data: entry.Data,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
if err != nil {
log.Println("Error insertint into logs: ", err)
return err
}
return nil
}
func (l *LogEntry) All() ([]*LogEntry, error) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
collection := client.Database("logs").Collection("logs")
opts := options.Find()
opts.SetSort(bson.D{{"created_at", -1}})
cursor, err := collection.Find(context.TODO(), bson.D{}, opts)
if err != nil {
log.Println("Finding all docs error: ", err)
return nil, err
}
defer cursor.Close(ctx)
var logs []*LogEntry
for cursor.Next(ctx) {
var item LogEntry
err := cursor.Decode(&item)
if err != nil {
log.Println("Error decoding log into slice: ", err)
return nil, err
} else {
logs = append(logs, &item)
}
}
return logs, nil
}
func (l *LogEntry) GetOne(id string) (*LogEntry, error) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
collection := client.Database("logs").Collection("logs")
docId, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
var entry LogEntry
err = collection.FindOne(ctx, bson.M{"_id": docId}).Decode(&entry)
if err != nil {
return nil, err
}
return &entry, nil
}
func (l *LogEntry) DropCollections() error {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
collection := client.Database("logs").Collection("logs")
if err := collection.Drop(ctx); err != nil {
return err
}
return nil
}
func (l *LogEntry) Update() (*mongo.UpdateResult, error) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
collection := client.Database("logs").Collection("logs")
docId, err := primitive.ObjectIDFromHex(l.ID)
if err != nil {
return nil, err
}
result, err := collection.UpdateOne(
ctx,
bson.M{"_id": docId},
bson.D{
{"$set", bson.D{
{"name", l.Name},
{"data", l.Data},
{"updated_at", time.Now()},
}},
},
)
if err != nil {
return nil, err
}
return result, err
}
```
```go
// main.go
package main
import (
"context"
"fmt"
"log"
"logger/data"
"net"
"net/http"
"net/rpc"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
const (
mongoURL = "mongodb://mongo:27017"
)
var client *mongo.Client
type Config struct {
Models data.Models
}
func main() {
// connect to mongo
mongoClient, err := connectToMongo()
if err != nil {
log.Panic(err)
}
client = mongoClient
// create a context to disconnect
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// close connection
defer func() {
if err = client.Disconnect(ctx); err != nil {
panic(err)
}
}()
app := Config{
Models: data.New(client),
}
}
func connectToMongo() (*mongo.Client, error) {
// create connection options
clientOptions := options.Client().ApplyURI(mongoURL)
clientOptions.SetAuth(options.Credential{
Username: "admin",
Password: "password",
})
c, err := mongo.Connect(context.TODO(), clientOptions)
if err != nil {
log.Println("Error connecting: %s", err)
return nil, err
}
return c, nil
}
```

62
docs/go/advanced/flags.md Normal file
View File

@@ -0,0 +1,62 @@
# Flags
Flags allow you to define command line arguments to your applications. This gives you some more useful features over `os.Args` such as help strings and validation.
```go
var age = flag.Int("age", 20, "age of user") // arg name, default, help
fmt.Println(*age) // gives us a pointer to the value
// or
var age int
flag.IntVar(&age, "age", 20, "age of user")
fmt.Println(age)
// with structs
type ServerConfig struct {
port int
env string
}
var config ServerConfig
flag.IntVar(&config.port, "port", 8000, "server port")
flag.StringVar(&config.env, "env", "dev", "environment")
flag.Parse()
```
## Custom Flags
Normally flags can only be strings, ints, or booleans, but you can create your own custom flag types by implementing the `Value` interface which has two functions, `String` and `Set`.
```go
type AddrVal struct {
addr *string
}
func (a AddrVal) String() string {
if a.addr == nil {
return ""
}
return *a.addr
}
func (a AddrVal) Set(s string) error {
if err := validateAddr(s); err != nil {
return err
}
*a.addr = s
return nil
}
func validateAddr(addr string) error {
// validate address
return nil
}
func main() {
addr := ":8080" // default value
flag.Var(AddrVal{&addr}, "addr", "address to listen on")
flag.Parse()
fmt.Println("address: ", addr)
}
```

212
docs/go/advanced/grpc.md Normal file
View File

@@ -0,0 +1,212 @@
# gRPC
GRPC is an enhancement to RPC in that both sides don't have to be written in Go, because there are libraries for a lot of other languages. It also defines it's payload types in separate proto files so they can be shared among teams.
Documentation: https://grpc.io/docs/languages/go/basics/
## Install GRPC
```bash
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.27
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
```
## Proto Files
```proto
// log.proto
syntax = "proto3";
package logs;
option go_package = "/logs";
message Log{
string name = 1;
string data = 2;
}
message LogRequest{
Log logEntry = 1;
}
message LogResponse{
string result = 1;
}
service LogService{
rpc WriteLog(LogRequest) returns (LogResponse);
}
```
## Compile Protos
Proto compiler link: https://grpc.io/docs/protoc-installation/. This should be stored somewhere in your path, preferably in your `~/go/bin` directory.
In directory with your proto file, run the below command. This will generate
```bash
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative logs.proto
```
## Listener
### Install dependencies
```bash
go get google.golang.org/gprc
go get google.golang.org/protobuf
```
### Implement handlers
```go
// grpc.go
package main
import (
"context"
"fmt"
"log"
"logger/data" // our db models
"logger/logs" // our proto files
"net"
"google.golang.org/grpc"
)
type LogServer struct {
logs.UnimplementedLogServiceServer
Models data.Models // db writer
}
// Using code generated from proto
func (l *LogServer) WriteLog(ctx context.Context, req *logs.LogRequest) (*logs.LogResponse, error) {
input := req.GetLogEntry() // auto generated function
logEntry := data.LogEntry{
Name: input.Name,
Data: input.Data,
}
err := l.Models.LogEntry.Insert(logEntry)
if err != nil {
resp := &logs.LogResponse{
Result: "failed",
}
return resp, err
}
resp := &logs.LogResponse{
Result: "logged!",
}
return resp, nil
}
func (app *Config) gRPCListen() {
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", grpcPort))
if err != nil {
log.Fatalf("failed to listen to gRPC: %v", err)
}
s := grpc.NewServer()
logs.RegisterLogServiceServer(s, &LogServer{
Models: app.Models,
})
log.Printf("gRPC server listening on port %s", grpcPort)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve gRPC: %v", err)
}
}
```
### Instantiate server
```go
// grpc.go
func (app *Config) gRPCListen() {
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", grpcPort))
if err != nil {
log.Fatalf("failed to listen to gRPC: %v", err)
}
s := grpc.NewServer()
// auto generated function
logs.RegisterLogServiceServer(s, &LogServer{
Models: app.Models,
})
log.Printf("gRPC server listening on port %s", grpcPort)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve gRPC: %v", err)
}
}
```
```go
// main.go
go app.grpcLisen()
```
## Sender
The sender must also have access to the proto files generated by the listener. It would be a good idea to store these in a separate repo so that multiple teams can access them from a central source of truth.
```go
import (
"context"
"log" // proto
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func (app *Config) logItemViaGRPC(w http.ResponseWriter, r *http.Request) {
var requestPayload RequestPayload
err := app.readJSON(w, r, &requestPayload)
if err != nil {
app.errorJSON(w, err)
return
}
// need to pass some sort of credentials to the server
conn, err := grpc.Dial("logger:50001", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
if err != nil {
app.errorJSON(w, err)
return
}
defer conn.Close()
// generated in proto
client := logs.NewLogServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_, err = client.WriteLog(ctx, &logs.LogRequest{
LogEntry: &logs.Log{
Name: requestPayload.Log.Name,
Data: requestPayload.Log.Data,
},
})
if err != nil {
app.errorJSON(w, err)
return
}
payload := jsonResponse{
Error: false,
Message: "logged via gRPC",
}
app.writeJSON(w, http.StatusAccepted, payload)
}
```

357
docs/go/advanced/http.md Normal file
View File

@@ -0,0 +1,357 @@
# HTTP
The below snippets are using the `chi` package for Go.
### To Install
```bash
go get github.com/go-chi/chi/v5
go get github.com/go-chi/chi/v5/middleware
go get github.com/go-chi/cors
```
## Basic Implementation
```go
// main.go
package main
import (
"fmt"
"log"
"net/http"
"os"
"time"
_ "github.com/jackc/pgconn"
_ "github.com/jackc/pgx/v4"
_ "github.com/jackc/pgx/v4/stdlib"
)
const webPort = "80"
var counts int64
type Config struct {
Client *http.Client
}
func main() {
app := Config{
Client: &http.Client{},
}
srv := &http.Server{
Addr: fmt.Sprintf(":%s", webPort),
Handler: app.routes(),
}
err := srv.ListenAndServe()
if err != nil {
log.Panic(err)
}
}
```
```go
// routes.go
package main
import (
"net/http"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/cors"
)
func (app *Config) routes() http.Handler {
mux := chi.NewRouter()
mux.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"https://*", "http://*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300,
}))
mux.Use(middleware.Heartbeat("/ping"))
mux.Post("/authenticate", app.Authenticate)
return mux
}
```
```go
// handlers.go
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
)
func (app *Config) Authenticate(w http.ResponseWriter, r *http.Request) {
var requestPayload struct {
Email string `json:"email"`
Password string `json:"password"`
}
err := app.readJSON(w, r, &requestPayload)
if err != nil {
app.errorJSON(w, err, http.StatusBadRequest)
return
}
// validate user against db
user, err := app.Repo.GetByEmail(requestPayload.Email)
if err != nil {
app.errorJSON(w, errors.New("invalid credentials"), http.StatusBadRequest)
return
}
valid, err := app.Repo.PasswordMatches(requestPayload.Password, *user)
if err != nil || !valid {
app.errorJSON(w, errors.New("invalid credentials"), http.StatusBadRequest)
return
}
// log authentication
err = app.logRequest("authentication", fmt.Sprintf("%s logged in", user.Email))
if err != nil {
app.errorJSON(w, err)
return
}
log.Println("Logged authentication request")
payload := jsonResponse{
Error: false,
Message: fmt.Sprintf("Logged in user %s", requestPayload.Email),
Data: user,
}
app.writeJSON(w, http.StatusAccepted, payload)
}
func (app *Config) logRequest(name, data string) error {
var entry struct {
Name string `json:"name"`
Data string `json:"data"`
}
entry.Name = name
entry.Data = data
jsonData, _ := json.MarshalIndent(entry, "", "\t")
logURL := "http://logger/log"
request, err := http.NewRequest("POST", logURL, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
_, err = app.Client.Do(request)
if err != nil {
return err
}
return nil
}
```
```go
// helpers.go
package main
import (
"encoding/json"
"errors"
"io"
"net/http"
)
type jsonResponse struct {
Error bool `json:"error"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}
func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data any) error {
maxBytes := 1048576 // one MB
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
dec := json.NewDecoder(r.Body)
err := dec.Decode(data)
if err != nil {
return err
}
err = dec.Decode(&struct{}{})
if err != io.EOF {
return errors.New("Body must have only a single json value")
}
return nil
}
func (app *Config) writeJSON(w http.ResponseWriter, status int, data any, headers ...http.Header) error {
out, err := json.Marshal(data)
if err != nil {
return err
}
if len(headers) > 0 {
for key, val := range headers[0] {
w.Header()[key] = val
}
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, err = w.Write(out)
if err != nil {
return err
}
return nil
}
func (app *Config) errorJSON(w http.ResponseWriter, err error, status ...int) error {
statusCode := http.StatusBadRequest
if len(status) > 0 {
statusCode = status[0]
}
var payload jsonResponse
payload.Error = true
payload.Message = err.Error()
return app.writeJSON(w, statusCode, payload)
}
```
## Adding Custom Types to Session
```go
// main.go, before creating session
gob.Register(data.User{})
// to add type to session
app.Session.Put(r.Context(), "user", user) // user *data.User
```
## Testing
```go
// handlers_test.go
package main
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
)
type RoundTripFunc func(req *http.Request) *http.Response
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req), nil
}
func NewTestClient(fn RoundTripFunc) *http.Client {
return &http.Client{
Transport: fn,
}
}
func Test_Authenticate(t *testing.T) {
jsonToReturn := `
{
"error": false,
"message": "some message"
}
`
client := NewTestClient(func(req *http.Request) *http.Response {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString(jsonToReturn)),
Header: make(http.Header),
}
})
testApp.Client = client
postBody := map[string]interface{}{
"email": "me@here.com",
"password": "verysecret",
}
body, _ := json.Marshal(postBody)
req, _ := http.NewRequest("POST", "/authenticate", bytes.NewReader(body))
rr := httptest.NewRecorder()
handler := http.HandlerFunc(testApp.Authenticate)
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusAccepted {
t.Errorf("handler returned wrong status code: got %v want %v",
rr.Code, http.StatusAccepted)
}
}
```
```go
// routes_test.go
package main
import (
"net/http"
"testing"
"github.com/go-chi/chi"
)
func Test_routes_exist(t *testing.T) {
testApp := Config{}
testRoutes := testApp.routes()
chiRoutes := testRoutes.(chi.Router)
routes := []string{"/authenticate"}
for _, route := range routes {
routeExists(t, chiRoutes, route)
}
}
func routeExists(t *testing.T, routes chi.Router, route string) {
found := false
_ = chi.Walk(routes, func(method string, foundRoute string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
if route == foundRoute {
found = true
}
return nil
})
if !found {
t.Errorf("route %s not found", route)
}
}
```

97
docs/go/advanced/rpc.md Normal file
View File

@@ -0,0 +1,97 @@
# RPC
RPC stands for Remote Procedure Calls, and allows for external Go microservices to call functions in our application directly.
## Listener
To do this, you need to define a RPCServer dummy type, and payload to receive, and a function that gets a pointer to a response string and returns an error.
```go
type RPCServer struct{}
type RPCPayload struct {
Name string
Data string
}
func (r *RPCServer) LogInfo(payload RPCPayload, resp *string) error {
collection := client.Database("logs").Collection("logs")
_, err := collection.InsertOne(context.TODO(), data.LogEntry{
Name: payload.Name,
Data: payload.Data,
})
if err != nil {
log.Println("error writing to mongo ", err)
return err
}
*resp = "processed payload via RPC: " + payload.Name
return nil
}
```
To instantiate the RPCServer, use the below syntax in your main application file. Basically you define a function that creates the connection and listens on a port. Then, you register your RPC handler type and serve the RPC server.
```go
func (app *Config) rpcListen() error {
log.Println("Starting RPC server on port " + RPC_PORT)
listen, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", RPC_PORT))
if err != nil {
return err
}
defer listen.Close()
for {
rpcConn, err := listen.Accept()
if err != nil {
log.Println("Error accepting connection: ", err)
continue
}
go rpc.ServeConn(rpcConn)
}
}
// in function where you start your app...
err = rpc.Register(new(RPCServer))
go app.rpcListen()
```
## Sender
To send messages via RPC, you need to create a type for the payload that exactly matches that of the receiver.
```go
type RPCPayload struct {
Name string
Data string
}
func (app *Config) logItemViaRPC(r http.ResponseWriter, l LogPayload) {
client, err := rpc.Dial("tcp", "logger:5001")
if err != nil {
app.errorJSON(r, err)
return
}
payload := RPCPayload{
Name: l.Name,
Data: l.Data,
}
var resp string
err = client.Call("RPCServer.LogInfo", payload, &resp)
if err != nil {
app.errorJSON(r, err)
return
}
returnPayload := jsonResponse{
Error: false,
Message: resp,
}
app.writeJSON(r, http.StatusAccepted, returnPayload)
}
```

View File

@@ -1 +1,11 @@
# Go # Go
Go is a statically-typed, compiled, and open source programming language created by Google.
## Creating a New Project
```bash
mkdir <project_name>
cd <project_name>
go mod init gitlab.com/djdietrick/<project_name>
```

View File

@@ -15,6 +15,14 @@
}, },
{ {
"text": "Go Advanced", "text": "Go Advanced",
"items": [{ "text": "Testing", "link": "/go/advanced/testing" }] "items": [
{"text": "Context", "link": "/go/advanced/context"},
{"text": "Flags", "link": "/go/advanced/flags"},
{ "text": "Testing", "link": "/go/advanced/testing" },
{"text": "HTTP", "link": "/go/advanced/http"},
{"text": "RPC", "link": "/go/advanced/rpc"},
{"text": "gRPC", "link": "/go/advanced/grpc"},
{"text": "Databases", "link": "/go/advanced/db"}
]
} }
] ]

View File

@@ -4,15 +4,15 @@ A graph is a collection of nodes called **vertices** that are connected to one a
## Direction ## Direction
Direction relates to the way vertices are connected with one another. If one vertex points to another vertex, but that other vertex does not necessarily point back, then we have a **directed graph**. If the connection goes both ways, then it is an **undirected graph** or a **bi-directional** graph. Direction relates to the way vertices are connected with one another. If one vertex points to another vertex, but that other vertex does not necessarily point back, then we have a **directed graph**. If the connection goes both ways, then it is an **undirected graph** or a **bi-directional** graph.
## Connection ## Connection
A graph is considered **connected** when you can reach any vertex from any other vertex, or that there are no sets of vertices that are disconnected from the other vertices. A graph is considered **strongly connected** if all of the connections are bi-directional. Otherwise it is **weakly connected**. A graph is considered **connected** when you can reach any vertex from any other vertex, or that there are no sets of vertices that are disconnected from the other vertices. A graph is considered **strongly connected** if all of the connections are bi-directional. Otherwise it is **weakly connected**.
## Cycles ## Cycles
A cycle is when three or more vertices connect to form a closed loop. A graph that has no cycles is called a **acyclic graph**. A graph with at least one cycle is a **cyclic graph**. When traversing cyclic graphs you need to be careful to not get into an infinite loop, and instead mark nodes as visited. A cycle is when three or more vertices connect to form a closed loop. A graph that has no cycles is called a **acyclic graph**. A graph with at least one cycle is a **cyclic graph**. When traversing cyclic graphs you need to be careful to not get into an infinite loop, and instead mark nodes as visited.
## Respresenting in code ## Respresenting in code
@@ -31,3 +31,136 @@ The space complexity of a graph is O(v+e) where v is the number of vertices and
- Depth First Search Traversing: O(v+e) - Depth First Search Traversing: O(v+e)
- Bredth First Search Traversing: O(v+e) - Bredth First Search Traversing: O(v+e)
## Searching
To search a graph, you can use either DFS or BFS.
```python
def DFS(graph, start):
visited = set() # keep track of the visited nodes
stack = [start] # use a stack to keep track of nodes to visit next
while stack:
node = stack.pop() # get the next node to visit
if node not in visited:
visited.add(node) # mark the node as visited
print(node, end=' ') # visit the node (print its value in this case)
stack.extend(graph[node] - visited) # add the node's neighbors to the stack, able to remove seen nodes in python for efficiency
def BFS(graph, start):
visited = set() # Keep track of the nodes that we've visited
queue = [start] # Use a queue to implement the BFS
while queue:
node = queue.pop() # Dequeue a node from front of queue
if node not in visited:
visited.add(node) # Mark the node as visited
print(node, end=' ') # Visit the node (print its value in this case)
queue.extend(graph[node]) # Enqueue all neighbours
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E'],
}
DFS(graph, 'A') # Output: A C F E B D
BFS(graph, 'A') # Output: A B C D E F
```
## Finding Paths
The below algorithms find all possible paths from the start to goal. This is assuming all edges have equal weights. If you need to find the shortest path with weighted graphs, you can add the weight to the tuple stored in the stack and find the shortest after all paths have been found.
```python
def dfs_paths(graph, start, goal):
stack = [(start, [start])]
while stack:
(vertex, path) = stack.pop()
# Again we are using python to subtract nodes already in the path
# You could also check if next is not in the current path instead
for next in graph[vertex] - set(path):
# if next in path: continue
if next == goal:
yield path + [next]
else:
stack.append((next, path + [next]))
# Will always have shortest path first (if all edges are equal)
def bfs_paths(graph, start, goal):
queue = [(start, [start])]
while queue:
(vertex, path) = queue.pop(0)
for next in graph[vertex] - set(path):
# if next in path: continue
if next == goal:
yield path + [next]
else:
queue.append((next, path + [next]))
```
### Finding Shortest Path (Djikstra's Algorithm)
```python
from collections import defaultdict
class Graph():
def __init__(self):
"""
self.edges is a dict of all possible next nodes
e.g. {'X': ['A', 'B', 'C', 'E'], ...}
self.weights has all the weights between two nodes,
with the two nodes as a tuple as the key
e.g. {('X', 'A'): 7, ('X', 'B'): 2, ...}
"""
self.edges = defaultdict(list)
self.weights = {}
def add_edge(self, from_node, to_node, weight):
# Note: assumes edges are bi-directional
self.edges[from_node].append(to_node)
self.edges[to_node].append(from_node)
self.weights[(from_node, to_node)] = weight
self.weights[(to_node, from_node)] = weight
def dijsktra(graph, initial, end):
# shortest paths is a dict of nodes
# whose value is a tuple of (previous node, weight)
shortest_paths = {initial: (None, 0)}
current_node = initial
visited = set()
while current_node != end:
visited.add(current_node)
destinations = graph.edges[current_node]
weight_to_current_node = shortest_paths[current_node][1]
for next_node in destinations:
weight = graph.weights[(current_node, next_node)] + weight_to_current_node
if next_node not in shortest_paths:
shortest_paths[next_node] = (current_node, weight)
else:
current_shortest_weight = shortest_paths[next_node][1]
if current_shortest_weight > weight:
shortest_paths[next_node] = (current_node, weight)
next_destinations = {node: shortest_paths[node] for node in shortest_paths if node not in visited}
if not next_destinations:
return "Route Not Possible"
# next node is the destination with the lowest weight
current_node = min(next_destinations, key=lambda k: next_destinations[k][1])
# Work back through destinations in shortest path
path = []
while current_node is not None:
path.append(current_node)
next_node = shortest_paths[current_node][0]
current_node = next_node
# Reverse path
path = path[::-1]
return path
```

18
docs/interview/ds/heap.md Normal file
View File

@@ -0,0 +1,18 @@
# Heap
A heap is a specialized tree-based data structure that satisfies the heap property: in a max-heap, for any given node I, the value of I is greater than or equal to the values of its children, while a min-heap has the value of I less than or equal to its children. Heaps are commonly implemented as arrays.
Common applications include:
- Priority queues
- Heap sort algorithm
- Finding kth largest/smallest elements
- Implementing efficient graph algorithms (Dijkstra's)
## Time Complexities
- Insert element: O(log n)
- Delete element: O(log n)
- Get min/max element: O(1)
- Build heap from array: O(n)
- Heapify (fix heap property): O(log n)
- Search for element: O(n)

View File

@@ -0,0 +1,170 @@
# Patterns
## Prefix Sum
Useful for finding the sum of elements in a subarray. In this pattern, you would store the sum of the elements up the that point in an array.
```python
array = [1,2,3,4,5,6,7]
prefix_sum = [1,3,6,10,15,21,28]
sum[i,j] = prefix_sum[j] - prefix_sum[i-1]
```
## Two Pointers
Can be helpful in array problems where you iterate from the beginning and end of the array, moving the pointers towards each other until they meet. You can also start from the middle and work outwards.
## Sliding Window
Helpful for finding subarrays that meet a criteria. You can check sums of subarrays by creating a window of length k, check the currently selected elements, and then when moving the window subtract the left-most element and add the new right-most element.
```python
def max_subarray(arr, k):
n = len(arr)
window_sum = sum(arr[:k])
max_sum = window_sum
max_start_index = 0
for i in range(n-k):
window_sum = window_sum - arr[i] + arr[i+k]
if window_sum > max_sum:
max_sum = window_sum
max_start_index = i + 1
return arr[max_start_index:max_start_index + k], max_sum
```
## Fast and Slow Pointers
Useful for working with arrays and linked lists. Slow pointer moves 1 node per loop, and the faster moves 2 nodes per loop. This can be used when trying to find a cycle in a linked list, or if trying to find the middle of a linked-list, since when the fast pointer is at the end, the slow pointer is in the middle.
## Linked List In-Place Reversal
You can do this by creating 3 pointers: `prev`, `curr`, and `next`.
```python
def reverse_linked_list(head):
prev = None
curr = head
while curr is not None:
next = curr.next
curr.next = prev
prev = curr
curr = next
return prev
```
## Monotonic Stack
Useful for finding the next greatest or next smaller element in an array.
```python
# Find the next greater element for each item in the array
def next_greater_element(arr):
n = len(arr)
stack = []
result = [-1] * n
for i in range(n):
while stack and arr[i] > arr[stack[-1]]:
result[stack.pop()] = arr[i]
stack.append(i)
return result
arr = [1,4,6,3,2,7]
result = next_greater_element(arr) # [4,6,7,7,7,-1]
```
## Top K Elements
This pattern helps find the top K elements in a dataset that satisfy a criteria (greatest, least, etc). This can be done by using a min/max-heap, which only has K slots, where the top element is the next one to check against. Since popping and inserting in a heap is O(logn), then the time complexity of this is O(n\*logk)
## Overlapping Intervals
This pattern can be used when solving problems involving integers or ranges where they may overlap and finding intersections. This is done by sorting all intervals by the start, and then checking if the interval overlaps with the previous interval (start of next element is greater than end of previous element).
```python
# Merging Intervals
[[1,3], [2,6], [8,10], [15,18]]
[[1,6], [8,10], [15,28]]
# Interval intersection
[[0,2], [5,10], [13,23], [24,25]], [[1,5], [8,12], [15,24], [25,26]]
[[1,2], [5,5], [8,10], [15,23], [24,24], [25,25]]
# Insert interval
[[1,2], [3,5], [6,7], [8,10], [12,16]], [4,8]
[[1,2], [3,10], [12,16]]
```
## Modified Binary Search
This pattern is helpful when solving problems where you need to find elements in an imperfectly sorted array (nearly sorted, rotated sorted, unknown length, containing duplicates). It can also be helpful finding the first or last occurance of an element, finding the square root of a number, and finding a peak element.
```python
def search_rotated_array(nums, target):
left, right = 0, len(nums)-1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
if nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
print(search_rotated_array([4,5,6,7,0,1,2]))
```
## Binary Tree Traversal
Traversing a tree can use either BFS or DFS, but which one you use can depend on what the use case is.
- To retrieve values, use DFS inorder traversal.
- To create a copy of a tree, use DFS preorder traversal.
- To process child nots before the parent (like deleting a tree), use DFS postorder traversal.
- If you need to explore all nodes at current level before moving on, use BFS.
## Matrix Traversal
Matrix traversal can be seen as graph problems, so you can use DFS or BFS to solve a lot of these problems.
```python
# Number of Islands problem
def count_islands(grid):
if not grid or not grid[0]: return 0
rows, cols = len(grid), len(grid[0])
islands = 0
def dfs(i,j):
if i < 0 or i >= rows or j < 0 or j >= cols or grid[i][j] != '1':
return
grid[i][j] = '0'
dfs(i+1, j)
dfs(i-1, j)
dfs(i, j+1)
dfs(i, j-1)
for i in range(rows):
for j in range(cols):
if grid[i][j] == "1":
dfs(i,j)
islands += 1
return islands
```
## Backtracking
This can be used to find all potential solution paths and backtracking the paths that do not lead to a solution. This can be helpful for finding permutations, combinations, and all possible paths from start to end points.

View File

@@ -1,21 +1,57 @@
# Trees # Trees
When thinking about trees, think **recursion**. Trees are a type of graph where child nodes consist of their own **subtrees**. Trees have a **root** node at the top. The edges of a tree are typically **directional** towards a node's children, and are **acyclic**. Each child node can only have one parent. The space complexity of trees are O(n) because you have n nodes and constant edges based on n. When thinking about trees, think **recursion**. Trees are a type of graph where child nodes consist of their own **subtrees**. Trees have a **root** node at the top. The edges of a tree are typically **directional** towards a node's children, and are **acyclic**. Each child node can only have one parent. The space complexity of trees are O(n) because you have n nodes and constant edges based on n.
## Binary Tree ## Binary Tree
Each node in a binary tree has at most 2 children. Many operations on a binary tree have logarithmic time complexity. **K-ary trees** is a way of describing trees where nodes have at most k children. Each node in a binary tree has at most 2 children. Many operations on a binary tree have logarithmic time complexity. **K-ary trees** is a way of describing trees where nodes have at most k children.
### Types of Binary Trees ### Types of Binary Trees
**Perfect Binary Trees** are where all the interior nodes have two children, and the leaf (bottom) nodes have the same depth. **Perfect Binary Trees** are where all the interior nodes have two children, and the leaf (bottom) nodes have the same depth.
**Complete Binary Trees** are where all interior nodes have two children, but the leaf nodes aren't all at the same depth. Essentially, the bottom level is not fully filled, however it must be filled from left to right. **Complete Binary Trees** are where all interior nodes have two children, but the leaf nodes aren't all at the same depth. Essentially, the bottom level is not fully filled, however it must be filled from left to right.
**Balanced Binary Trees** are where all left and right subtrees depth differs by no more than 1. This is what allows trees to have a O(log(n)) search complexity. **Balanced Binary Trees** are where all left and right subtrees depth differs by no more than 1. This is what allows trees to have a O(log(n)) search complexity.
**Full Binary Trees** are where all child nodes have either zero or two child nodes. No child nodes have only one child. **Full Binary Trees** are where all child nodes have either zero or two child nodes. No child nodes have only one child.
### Binary Search Tree ### Binary Search Tree
A binary search tree or BST is a tree where the left child node is always less than the parent, and the right child node is always greater. This allows for very fast searching since it is easy to make a decision on which path to check down next. A binary search tree or BST is a tree where the left child node is always less than the parent, and the right child node is always greater. This allows for very fast searching since it is easy to make a decision on which path to check down next.
## Searching
There are two main methods for searching trees: **Breadth First Search** and **Depth First Search**. In BFS, we traverse the tree level by level, and in DFS we traverse sub-tree by sub-tree, going down to the bottom and working our way up. BFS uses a queue (FIFO) and DFS uses a stack (LIFO). Within DFS we can traverse in order (Left-Root-Right), preorder (Root-Left-Right), or postorder (Left-Right-Root).
```python
def BFS(root):
if root is None:
return
queue = [root]
while queue:
node = queue.pop() # dequeue a node from the front of the queue
print(node.value, end=' ') # visit the node (print its value in this case)
# Can also be done with n children if not binary tree
# enqueue left child
if node.left:
queue.append(node.left)
# enqueue right child
if node.right:
queue.append(node.right)
def DFS(node):
if node is None:
return
# This example is pre-order, but just rearrange the order in which you check to make it in-order or post-order
# Visit the node (print its value in this case)
print(node.value, end=' ')
# Recursively call DFS on the left child
DFS(node.left)
# Recursively call DFS on the right child
DFS(node.right)
```

View File

@@ -0,0 +1,181 @@
# JavaScript
## Arrays
### Creating
```javascript
let a = [];
let a = [1, 2, 3];
let a = Array(3).fill(0); // [0,0,0]
let a = Array.from({ length: 5 }, (_, i) => i + 1); // [1,2,3,4,5]
let a = [...Array(5).keys()]; // [0,1,2,3,4]
```
### Accessing
```javascript
const a = [1, 2, 3];
console.log(a[0]); // 1
console.log(a.at(-1)); // 3 (last element)
```
### Manipulating
#### Adding
```javascript
const a = [];
a.push(1); // Add to end
a.unshift(2); // Add to beginning
a.splice(1, 0, 3); // Insert 3 at index 1
[...a, 4]; // Create new array with 4 added
```
#### Removing
```javascript
const arr = ['bread', 'butter', 'eggs', 'milk'];
arr.pop(); // returns and removes 'milk'
arr.shift(); // returns and removes 'bread'
arr.splice(1, 1); // removes 'butter'
```
#### Slicing
```javascript
const a = ['spam', 'egg', 'bacon', 'tomato', 'ham', 'lobster'];
a.slice(2, 5); // ['bacon', 'tomato', 'ham']
a.slice(-3); // ['tomato', 'ham', 'lobster']
a.slice(2); // ['bacon', 'tomato', 'ham', 'lobster']
[...a]; // Create a copy of an array
```
#### Sorting
```javascript
const nums = [3, 1, 3, 2, 5];
nums.sort(); // [1, 2, 3, 3, 5]
nums.reverse(); // [5, 3, 3, 2, 1]
[...nums].sort((a, b) => b - a); // Sort descending, creates new array
const items = [
{ name: 'john', grade: 'A', age: 15 },
{ name: 'jane', grade: 'B', age: 12 },
{ name: 'dave', grade: 'B', age: 10 }
];
items.sort((a, b) => a.age - b.age);
```
#### Filtering
```javascript
const odds = [1, 2, 3, 4, 5].filter(x => x % 2 === 1);
const evens = Array.from({ length: 20 }, (_, i) => i + 1).filter(x => x % 2 === 0);
```
## Strings
Strings in JavaScript are immutable but have many built-in methods.
### Helper Functions
```javascript
const str = 'spam';
'I saw spamalot!'.includes(str); // true
'I saw The Holy Grail!'.includes(str); // false
'12345'.repeat(5); // '1234512345123451234512345'
['John', 'Peter', 'Vicky'].join('#'); // 'John#Peter#Vicky'
'Hello, world!'.endsWith('!'); // true
'Hello, world!'.startsWith('H'); // true
'Hello, world!'.substring(0, 5); // 'Hello'
'Hello, world!'.slice(-6); // 'world!'
```
## Objects
### Creating
```javascript
const obj = {};
const obj = { color: 'green', points: 5 };
const obj = Object.create(null);
const obj = new Object();
```
### Accessing
```javascript
obj.color;
obj['color'];
obj?.points; // Optional chaining
const { color, points } = obj; // Destructuring
obj.age = 30;
Object.assign(obj, { height: 180 }); // Merge properties
```
### Removing
```javascript
delete obj.age;
```
### Iterating
```javascript
Object.keys(obj).forEach(key => {
// do something
});
Object.values(obj).forEach(value => {
// do something
});
Object.entries(obj).forEach(([key, value]) => {
// do something
});
```
### Object Methods
```javascript
const keys = Object.keys(obj);
const values = Object.values(obj);
const entries = Object.entries(obj);
const merged = Object.assign({}, obj1, obj2);
const clone = { ...obj };
// Prevent modifications
Object.freeze(obj);
Object.seal(obj);
```
## Sets and Maps
### Set
```javascript
const set = new Set();
set.add(1);
set.has(1); // true
set.delete(1);
set.clear();
const unique = [...new Set([1, 2, 2, 3, 3])]; // [1, 2, 3]
```
### Map
```javascript
const map = new Map();
map.set('key', 'value');
map.get('key');
map.has('key');
map.delete('key');
map.clear();
// Object to Map
const map = new Map(Object.entries(obj));
// Map to Object
const obj = Object.fromEntries(map);
```

View File

@@ -0,0 +1,209 @@
# Python
## Arrays
### Creating
```python
a = []
a = [1,2,3]
a = list(range(1,11)) # 1 ... 10
a = [x for x in [3, 4, 5, 6, 7] if x > 5]
a = [0] * 3 # [0,0,0]
```
### Accessing
```python
a = [1,2,3]
print(a[0]) # 1
print(a[-1]) # 3
```
### Manipulating
#### Adding
```python
a = []
a.append(1)
a.extend([9, 11, 13])
a += [6,7,8]
a.insert(0, 2) # Insert 2 at index 0
```
#### Removing
```python
li = ['bread', 'butter', 'eggs', 'milk']
li.pop() # returns and removes 'milk'
li.pop(2) # returns and removes 'eggs'
del li[0] # removes 'bread'
li.remove('butter')
```
#### Slicing
```python
# a_list[start:end]
# a_list[start:end:step]
a = ['spam', 'egg', 'bacon', 'tomato', 'ham', 'lobster']
a[2:5] # ['bacon', 'tomato', 'ham']
a[-3:] # ['tomato', 'ham', 'lobster']
a[-5:-2] # ['egg', 'bacon', 'tomato']
a[:4] # ['spam', 'egg', 'bacon', 'tomato']
a[2:] # ['bacon', 'tomato', 'ham', 'lobster']
a[0:6:2] # ['spam', 'bacon', 'ham']
a[6:0:-2] # ['lobster', 'tomato', 'egg']
a[::-1] # ['lobster', 'ham', 'tomato', 'bacon', 'egg', 'spam']
a[:] # Create a copy of an array
```
#### Sorting
```python
li = [3, 1, 3, 2, 5]
li.sort() # [1, 2, 3, 3, 5]
li.reverse() # [5, 3, 3, 2, 1]
sorted(li) # returns a sorted copy of li
li = [
('john', 'A', 15),
('jane', 'B', 12),
('dave', 'B', 10)
]
sorted(li, key=lambda x: x[2])
sorted(li, cmp=lambda x,y: 1 if x<y else -1) # Negative return means second number is "less than"
```
#### Filtering
```python
a = list(filter(lambda x : x % 2 == 1, range(1, 20)))
```
## Strings
Strings act a lot like arrays/lists in Python, so many of the methods still apply.
### Helper Functions
```python
s = 'spam'
s in 'I saw spamalot!' # True
s not in 'I saw The Holy Grail!' # True
s = '12345' * 5 # '1234512345123451234512345'
"#".join(["John", "Peter", "Vicky"]) # 'John#Peter#Vicky'
"Hello, world!".endswith("!") # True
"Hello, world!".startswith("H") # True
```
## Dictionaries
### Creating
```python
d = {}
d = {'color': 'green', 'points', 5}
```
### Accessing
```python
d['color']
d.get('points') # Returns None or value of points
d.get('points', 0) # Default value
d['age'] = 30
```
### Removing
```python
del d['age']
```
### Iterating
```python
for key, value in d.items():
# do something
for key in d.keys():
# do something
for value in d.values():
# do something
```
### Zipping
```python
group_1 = ['kai', 'abe', 'ada', 'gus', 'zoe']
group_2 = ['jen', 'eva', 'dan', 'isa', 'meg']
pairings = {name:name_2 for name, name_2 in zip(group_1, group_2)}
# {'kai': 'jen', 'abe': 'eva', 'ada': 'dan', 'gus': 'isa', 'zoe': 'meg'}
```
### Sorting
#### By Key
```python
my_dict = {'b': 2, 'a': 1, 'c': 3}
sorted_dict_by_keys = sorted(my_dict.items())
# [('a', 1), ('b', 2), ('c', 3)]
```
#### By Value
`dict.items()` returns a tuple of the key value pairs for each iteration, so we can use sorted and use the value of the pair to return a list of the sorted pairs.
```python
my_dict = {'b': 2, 'a': 1, 'c': 3}
sorted_dict_by_values = sorted(my_dict.items(), key=lambda item: item[1])
# [('a', 1), ('b', 2), ('c', 3)]
```
#### OrderedDict
OrderedDicts preserve the order in which the items were added, whereas normal dicts do not guarantee the order upon iteration. So you can sort the dictionary by key or value, and then pass that into OrderedDict to have a dict-like structure that preserves the inserted order.
```python
from collections import OrderedDict
my_dict = {'b': 2, 'a': 4, 'c': 3}
sorted_dict_by_keys = OrderedDict(sorted(my_dict.items()))
for key, value in sorted_dict_by_keys:
# do something
```
## Heap
`heapq` is a module that implements a heap structure on a list of items. It's helpful for finding kth largest/smallest elements and priority queues.
```python
import heapq
li = [10, 20, 15, 30, 40]
heapq.heapify(li)
# [10, 20, 15, 30, 40]
# Appending an element
heapq.heappush(h, 5)
# Pop the smallest element from the heap
min = heapq.heappop(h)
# Push a new element (5), then pop the smallest element and return
min = heapq.heappushpop(h, 5)
# Pop the smallest element, and then push new element (5)
min = heapq.heapreplace(h, 5)
# Find the 3 largest elements
maxi = heapq.nlargest(3, h)
# [40, 30, 20]
# Find the 3 smallest elements
min = heapq.nsmallest(3, h)
# [10, 15, 20]
# Merge heaps
h3 = list(heapq.merge(h1, h2))
```

View File

@@ -17,7 +17,9 @@
{"text": "Stacks and Queues", "link": "/interview/ds/stackqueue"}, {"text": "Stacks and Queues", "link": "/interview/ds/stackqueue"},
{"text": "Strings", "link": "/interview/ds/string"}, {"text": "Strings", "link": "/interview/ds/string"},
{"text": "Graphs", "link": "/interview/ds/graph"}, {"text": "Graphs", "link": "/interview/ds/graph"},
{"text": "Trees", "link": "/interview/ds/tree"} {"text": "Trees", "link": "/interview/ds/tree"},
{"text": "Heap", "link": "/interview/ds/heap"},
{"text": "Patterns", "link": "/interview/ds/patterns"}
] ]
}, },
{ {
@@ -48,5 +50,12 @@
{"text": "Facade", "link": "/interview/patterns/structure/facade"} {"text": "Facade", "link": "/interview/patterns/structure/facade"}
]} ]}
] ]
},
{
"text": "Languages Specific",
"items": [
{"text": "Python", "link": "/interview/languages/python"},
{"text": "Javascript", "link": "/interview/languages/javascript"}
]
} }
] ]

View File

@@ -0,0 +1,73 @@
# Classes
## Generics
Generics allow you to create a class that can hold many different kinds of data types, and specify an upper bound for what kind of interfaces are allowed to be stored and manipulated.
```java
```
## Nested Classes
You can also have classes defined within another classes. If the nested class is static, then it can be instantiated from the class itself just like any other static methods. Otherwise, you need an instance of the class to access the nested class.
### Static
```java
public class Employee {
public static class EmployeeComparator <T extends Employee> implements Comparator<Employee> {
private String sortType;
public EmployeeComparator(String sortType) {
this.sortType = sortType;
}
@Override
public int compare(Employee o1, Employee o2) {
if (sortType == "yearStarted") {
// sort by year started
}
// able to access private variables in Employee, and vice versa
return o1.name.compareTo(o2.name);
}
}
private int employeeId;
private String name;
private int yearStarted;
}
public static void main() {
List<Employee> employees = new ArrayList<>();
employees.sort(new Employee.EmployeeComparator<>());
}
```
### Non-static
```java
public class StoreEmployee extends Employee {
private String store;
public class StoreComparator <T extends Employee> implements Comparator<StoreEmployee> {
public int compare(StoreEmployee o1, StoreEmployee o2) {
int result = o1.store.compareTo(o2.store);
if (result == 0) {
return new Employee.EmployeeComparator<>("yearStarted").compare(o1, o2);
}
return result;
}
}
}
public static void main() {
List<StoreEmployee> storeEmployees = new ArrayList<>();
var genericEmployee = new StoreEmployee();
var comparator = genericEmployee.new StoreComparator<>();
var comparator = new StoreEmployee().new StoreComparator<>();
storeEmployees.sort(comparator);
}
```

View File

@@ -0,0 +1,51 @@
# Collections
Java has a class called `java.util.Collections` that defines all of the built in data types, but also provides some helper methods.
## Helper Methods
```java
public static void main() {
Card[] cardArray = new Card[13];
Card aceOfHearts = Card.getFaceCard(Card.Suit.HEART, 'A');
Arrays.fill(cardArray, aceOfHearts);
Card.printDeck(Arrays.asList(cardArray), "Aces of Hearts", 1);
List<Card> cards = new ArrayList<>(52);
// doesn't add any cards because capacity is 52 but size is 0
Collections.fill(cards, aceOfHearts);
System.out.println(cards);
System.out.println("cards.size() = " + cards.size());
// Returns a list with n copies
List<Card> acesOfHearts = Collections.nCopies(13, aceOfHearts);
Card.printDeck(acesOfHearts, "Aces of Hearts", 1);
Card kingOfClubs = Card.getFaceCard(Card.Suit.CLUB, 'K');
List<Card> kingsOfClubs = Collections.nCopies(13, kingOfClubs);
Card.printDeck(kingsOfClubs, "Kings of Clubs", 1);
// adds elements to empty lists
Collections.addAll(cards, cardArray);
Collections.addAll(cards, cardArray);
Card.printDeck(cards, "Card Collection with Aces added", 2);
// copyies over elements at the same index
// in this example we have 26 cards already, so only the first 13 get overwritten
Collections.copy(cards, kingsOfClubs);
Card.printDeck(cards, "Card Collection with Kings copied", 2);
Collections.shuffle(cards);
Collections.reverse(cards);
var sortingAlgorithm = Comparator.comparing(Card::rank)
.thenComparing(Card::suit);
Collections.sort(cards, sortingAlgorithm);
List<Card> subList = new ArrayList<>(cards.subList(0,4));
int subListIndex = Collections.indexOfSubList(cards, subList);
// Returns true if no elements in second list are found in first list
boolean disjoint = Collections.disjoint(cards, subList);
}
```

View File

@@ -0,0 +1,28 @@
# Control Flow
## Loops
### For Loop
```java
for(int i = 0; i < 10; i++) {
// do something
}
List<String> myStrings = new ArrayList<>(List.of("hello", "world"));
for (String s : myStrings) {
// do something
}
for (var s : myStrings) {
// do something
}
```
#### Interable Interfaces
Many collections implement the `Iterable` interface, which has a method for each.
```java
List<String> words = new ArrayList<>(List.of("hello", "world"));
words.forEach((s) -> System.out.println(s));
```

View File

@@ -0,0 +1,36 @@
# Interfaces
## Common Interfaces to Implement
### Comparator
Compares two instances of a class.
```java
class StudentGPAComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return (o1.gpa + o1.name).compareTo(o2.gpa + o2.name);
}
}
public static void main() {
Comparator<Student> gpaSorter = new StudentGPAComparator();
Arrays.sort(students, gpaSorter);
Arrays.sort(students, gpaSorter.reversed());
}
```
### Comparable
Similar to Comparator, except this is implemented directly on the classes you are comparing. Without specificing a type, you will receive an `Object` to your `compareTo` function.
```java
class Student implements Comparable<Student> {
@Override
public int compareTo(Student o) {
return this.name.compareTo(o.name);
}
}
```

View File

@@ -0,0 +1,94 @@
# Lambdas
In order to pass a lambda function as an argument, it needs to be passed to a place that expects an interface that is a **functional interface**, or it only has one abstract method. This way the compiler can be sure that the function passed is the correct one for that interface.
The lambda function can also use variables in the calling scope, as long as the variables are final or effectively final.
```java
@FunctionalInterface
public interface Operation<T> {
T operate(T a, T b);
}
public static <T> T calculator(Operation<T> function, T v1, T v2) {
T result = function.operate(v1, v2);
return result;
}
public static void main() {
List<Person> people = new ArrayList<>();
people.sort((o1, o2) -> o1.lastName.compareTo(o2.lastName));
int result = calculator((a,b) -> a + b, 5, 2);
}
```
## Common Functional Interfaces
These are found in the `java.util.function` package. There are four basic types of functional interfaces.
- Consumer -> `void accept(T t, U u?)` - Executes code without returning data (BiConsumer takes 2 args)
- Function -> `R apply(T t, U u?)` - Return a result of an operation or function
- Predicate -> `boolean test(T t, U u?)` - Test if a condition is true or false
- Supplier -> `T get()` - Return an instance of something
### Consumer
A Consumer interface takes one or two arguments and performs some operation on them without returning anything.
```java
public static <T> void processPoint(T t1, T t2, BiConsumer<T,T> consumer) {
consumer.accept(t1, t2);
}
public static void main() {
BiConsumer<Double, Double> p1 = (lat, lng) -> System.out.printf("[lat:%.3f, long:%.3f]%n", lat, lng)
processPoint(30.245, 50.343, p1);
}
```
### Predicate
```java
public static void main() {
List<String> names = new ArrayList<>(List.of("Harry", "Ron", "Hermoine"))
names.removeIf(s -> s.startsWith("H"));
}
```
### Function
Similar to the Operation interface example above, this interface takes a lambda function and two values, performing some operation on the two with the `apply` method.
#### Operator
Operators have the same return and argument types, so only one type `T`. There are both `UnaryOperators` and `BiOperators`.
```java
public static <T> T calculator(BinaryOperator<T> function, T v1, T v2) {
T result = function.apply(v1, v2);
return result;
}
```
#### Function
Functions can have different types for the return values and all of its arguments. There are both `Functions` and `BiFunctions`.
## Method References
Method references allow you to pass a static method instead of a lambda function, and java can infer how to pass the arguments and what the return value should be. You pass a method reference by using the class name, two colons, and then the function name.
```java
public static <T> T calculator(BinaryOperator<T> function, T v1, T v2) {
T result = function.apply(v1, v2);
return result;
}
public static void main() {
calculator(Integer::sum, 2, 5);
calculator(Double::sum, 7.5, 2.5);
}
```

View File

@@ -0,0 +1 @@
# Types

1
docs/java/index.md Normal file
View File

@@ -0,0 +1 @@
# Java

14
docs/java/sidebar.json Normal file
View File

@@ -0,0 +1,14 @@
[
{
"text": "Basics",
"items": [
{ "text": "Introduction", "link": "/java/" },
{ "text": "Types", "link": "/java/basics/types" },
{ "text": "Control Flow", "link": "/java/basics/controlflow" },
{ "text": "Classes", "link": "/java/basics/classes" },
{ "text": "Interfaces", "link": "/java/basics/interfaces" },
{ "text": "Collections", "link": "/java/basics/collections" },
{ "text": "Lambdas", "link": "/java/basics/lambda" }
]
}
]

View File

@@ -5,3 +5,5 @@
[Rust](/rust/) [Rust](/rust/)
[Go](/go/) [Go](/go/)
[Java](/java/)

View File

@@ -0,0 +1,27 @@
# Webscraping
Python is an amazing language to use for webscraping purposes. One of the ways to do this is with the BeautifulSoup (or bs4) package.
```python
import requests
import bs4
result = requests.get('https://en.wikipedia.org/wiki/Jonas_Salk')
soup = bs4.BeautifulSoup(result.text,"lxml")
print(soup.select('title')[0].getText())
# soup.select('.some_class')
# soup.select('#some_id')
# Images
pic_element = soup.select('.thumbimage')[0]
print(pic_element['src'])
image_link = requests.get('http:' + pic_element['src'])
# Write image to file
f = open('my_img.jpg', 'wb')
f.write(image_link.content)
f.close()
```

View File

@@ -25,6 +25,7 @@
{"text": "Regex", "link": "/python/advanced/regex"}, {"text": "Regex", "link": "/python/advanced/regex"},
{"text": "Testing", "link": "/python/advanced/tests"}, {"text": "Testing", "link": "/python/advanced/tests"},
{"text": "Timing", "link": "/python/advanced/timing"}, {"text": "Timing", "link": "/python/advanced/timing"},
{"text": "Webscraping", "link": "/python/advanced/webscraping"},
{"text": "Zipping", "link": "/python/advanced/zip"} {"text": "Zipping", "link": "/python/advanced/zip"}
] ]
} }

View File

@@ -7,6 +7,11 @@ Test functions are defined with the `#[test]` attribute. Within the tests, we u
For unit tests, its best to write your tests in the file that it is testing but contain it with a `mod tests` with the `cfg(test)` attribute. This will exclude it from the final executable or library. Unit tests also allow you to test private functions, meaning ones that are not `pub`. For unit tests, its best to write your tests in the file that it is testing but contain it with a `mod tests` with the `cfg(test)` attribute. This will exclude it from the final executable or library. Unit tests also allow you to test private functions, meaning ones that are not `pub`.
```rust ```rust
#[allow(dead_code)]
pub fn greeting(_name: &str) -> String {
String::from("Hello!")
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; // Use this to call functions in the current file use super::*; // Use this to call functions in the current file
@@ -33,7 +38,31 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn greater_than_100() { fn greater_than_100() {
// Something illegal panic!("I'm supposed to panic")
}
#[test]
#[should_panic(expected = "Expected text")]
fn expect_panic_message() {
panic!("Expected text");
}
// Ignore test
// Can still be run on demand
#[test]
#[ignore]
fn expensive_test() {
// code that takes an hour to run
}
// Return a result to allow for the ? operator to be used to cause test failures
#[test]
fn result_test() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("two plus two does not equal four"))
}
} }
} }
``` ```

View File

@@ -91,7 +91,7 @@ fn some_function<T, U>(t: &T, u: &U) -> i32
## Returning types that implement traits ## Returning types that implement traits
You can only use a trait as a return value if you are returning a single type. You can only use a trait as a return value if you are returning a single type. For example, you can not have one path that returns a NewsArticle and one that returns a Tweet.
```rust ```rust
fn returns_summarizable() -> impl Summary { fn returns_summarizable() -> impl Summary {

View File

@@ -17,8 +17,11 @@ if num < 3 {
```rust ```rust
let config_max = Some(3u8); let config_max = Some(3u8);
let age: Result<u8, _> = "34".parse();
if let Some(max) = config_max { if let Some(max) = config_max {
println!("The maximum is configured to be {}", max); println!("The maximum is configured to be {}", max);
} else if let Ok(age) = age {
println!("Age is {}", age);
} }
``` ```
@@ -107,10 +110,25 @@ fn value_in_cents(coin: Coin) -> u8 {
} }
fn plus_one(x: Option<i32>) -> Option<i32> { fn plus_one(x: Option<i32>) -> Option<i32> {
match x { match x {
None => None, None => None,
Some(i) => Some(i + 1), Some(i) => Some(i + 1),
}
} }
}
// Or
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
// Range
let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
``` ```

View File

@@ -82,6 +82,21 @@ let _two = b[1];
// let n = a[10]; // let n = a[10];
``` ```
## Result
Results allow you to handle when something returns either a value or error. If it has a valid value, it will be of type `Ok<T>`, otherwise it will be of type `Err<E>`. See the `errors` section for more into on Results.
## Option
Options are similar to Results in that they can contain a conditional type. It can be either of type `None` or type `Some<T>`.
```rust
enum Option<T> {
None,
Some(T),
}
```
## Type Aliases ## Type Aliases
```rust ```rust

7
docs/vue/index.md Normal file
View File

@@ -0,0 +1,7 @@
# Vue
## Creating a New Project
```bash
npm create vue@latest
```

8
docs/vue/sidebar.json Normal file
View File

@@ -0,0 +1,8 @@
[
{
"text": "Vue Basics",
"items": [
{ "text": "Introduction", "link": "/vue/" }
]
}
]