Compare commits

..

24 Commits

Author SHA1 Message Date
c601784b5c Started java collections 2025-03-30 16:14:10 -04:00
cfaeadf198 Updated java up to lambdas 2025-03-26 23:00:04 -04:00
6ff585e133 Began java section 2025-03-25 10:23:34 -04:00
f619093d76 Added language specific interview prep sections 2025-02-17 22:29:48 -05:00
c2366ac927 Added create a new project section for go 2025-01-27 18:53:02 -05:00
65ead5a6f2 Updated interview section with some patterns 2025-01-15 17:56:35 -05:00
f69fcfc50b Added sections on go context and flags 2025-01-13 16:21:11 -05:00
2644b07951 Added go http and db pages 2025-01-11 09:29:50 -05:00
9458db3381 Added go rpc and grpc sections 2025-01-10 18:38:39 -05:00
894a15525a Merge branch 'master' of gitlab.com:djdietrick/docs into master 2025-01-10 16:02:54 -05:00
2b8ca93fab Some updates to python and rust from reference project, started java section and go rpc/grpc sections 2025-01-10 16:02:46 -05:00
d14a1a05c8 Added section about binary search in interview prep 2025-01-10 15:02:20 -05:00
8bd8196841 Updated data fetching section of react 2024-12-13 17:04:52 +01:00
6dc2e294b4 Added redux and data fetching sections to react 2024-12-13 11:00:23 +01:00
5ae50d9381 Started react section 2024-12-10 21:47:44 +01:00
2c05f038da Added more go pages 2024-11-30 10:43:05 +01:00
15b2440d19 Fixed collections page of go 2024-11-18 19:41:20 +01:00
d17eb8dfce Started go section 2024-11-18 19:37:04 +01:00
df494fbc70 Cleanup .gitlab-ci.yml 2024-11-18 15:47:01 +01:00
5b0277f294 Updated base for unique domain name 2024-11-18 15:40:06 +01:00
4f4a56713d Updated base for gitlab pages 2024-11-18 15:31:04 +01:00
d572419762 Added pages tag and fixed path of dist 2024-11-18 15:20:00 +01:00
4872f61dd2 Added image of node:18 2024-11-18 15:17:01 +01:00
1dfab518ed Test deploying to gitlab pages 2024-11-18 15:16:16 +01:00
56 changed files with 4908 additions and 92 deletions

View File

@@ -1,41 +1,18 @@
variables:
REPOSITORY_NAME: djdietrick/docs
DOCKER_HOST: tcp://docker:2375
stages:
- build
- deploy
- pages
build:
stage: build
image:
name: amazon/aws-cli
entrypoint: [""]
services:
- docker:dind
before_script:
- amazon-linux-extras install docker
image: node:18
pages:
stage: pages
pages: true
cache:
paths:
- node_modules/
script: |
docker build -t $DOCKER_REGISTRY/$REPOSITORY_NAME:$CI_PIPELINE_ID -t $DOCKER_REGISTRY/$REPOSITORY_NAME:latest .
aws ecr get-login-password | docker login --username AWS --password-stdin $DOCKER_REGISTRY
docker push $DOCKER_REGISTRY/$REPOSITORY_NAME --all-tags
# variables:
# SERVICE_NAME: "docs"
# stages:
# - deploy
# deploy:
# stage: deploy
# only:
# - master # This pipeline stage will run on this branch alone
# image: google/cloud-sdk:latest # We'll use Google Cloud SDK for Cloud Run related commands
# script:
# - echo $GCP_SERVICE_ACCOUNT > gcloud-service-key.json # Save Google cloud contents in a temporary json file
# - gcloud auth activate-service-account --key-file gcloud-service-key.json # Activate your service account
# - gcloud auth configure-docker # Configure docker environment
# - gcloud config set project $GCP_PROJECT_ID #Set the GCP Project ID to the variable name
# - gcloud builds submit --tag gcr.io/$GCP_PROJECT_ID/$SERVICE_NAME #Run the gcloud build command to build our image
# - gcloud run deploy $SERVICE_NAME --image gcr.io/$GCP_PROJECT_ID/$SERVICE_NAME --region=us-east4 --platform managed --allow-unauthenticated # Run the gcloud run deploy command to deploy our new service
npm install
npm run build
mkdir public
cp -R ./docs/.vitepress/dist/* ./public
artifacts:
paths:
- public

View File

@@ -9,31 +9,31 @@ import {
} from "./chunk-3YS4HNIT.js";
// node_modules/vitepress/dist/client/theme-default/index.js
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/fonts.css";
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/fonts.css";
// node_modules/vitepress/dist/client/theme-default/without-fonts.js
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/vars.css";
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/base.css";
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/utils.css";
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css";
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css";
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css";
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css";
import "C:/git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css";
import VPBadge from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
import Layout from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/Layout.vue";
import { default as default2 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
import { default as default3 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue";
import { default as default4 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue";
import { default as default5 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue";
import { default as default6 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue";
import { default as default7 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue";
import { default as default8 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue";
import { default as default9 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue";
import { default as default10 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue";
import { default as default11 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue";
import { default as default12 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue";
import { default as default13 } from "C:/git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue";
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/vars.css";
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/base.css";
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/utils.css";
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css";
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css";
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css";
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css";
import "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css";
import VPBadge from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
import Layout from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/Layout.vue";
import { default as default2 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
import { default as default3 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue";
import { default as default4 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue";
import { default as default5 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue";
import { default as default6 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue";
import { default as default7 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue";
import { default as default8 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue";
import { default as default9 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue";
import { default as default10 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue";
import { default as default11 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue";
import { default as default12 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue";
import { default as default13 } from "/Users/djdietrick/Git/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue";
// node_modules/vitepress/dist/client/shared.js
var inBrowser = typeof document !== "undefined";

View File

@@ -1,31 +1,31 @@
{
"hash": "cb1f9d5c",
"configHash": "9ebaad5b",
"lockfileHash": "6f849b3a",
"browserHash": "c342cb39",
"hash": "b31dd5f2",
"configHash": "9af315cb",
"lockfileHash": "951d497c",
"browserHash": "14d9b0dd",
"optimized": {
"vue": {
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "d958a5e2",
"fileHash": "6eb7142a",
"needsInterop": false
},
"vitepress > @vue/devtools-api": {
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js",
"fileHash": "7d35fe85",
"fileHash": "f77c0dac",
"needsInterop": false
},
"vitepress > @vueuse/core": {
"src": "../../../../node_modules/@vueuse/core/index.mjs",
"file": "vitepress___@vueuse_core.js",
"fileHash": "c964aa0c",
"fileHash": "4488c276",
"needsInterop": false
},
"@theme/index": {
"src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js",
"file": "@theme_index.js",
"fileHash": "8c3760fb",
"fileHash": "f16ffef2",
"needsInterop": false
}
},

View File

@@ -1,6 +1,7 @@
export default {
title: "Docs.Dietrick.Dev",
descript: "A collection of notes and snippets",
base: "/",
themeConfig: {
socialLinks: [
{ icon: "github", link: "https://gitlab.com/djdietrick/docs" },
@@ -14,6 +15,10 @@ export default {
"/interview/": require("../interview/sidebar.json"),
"/server/": require("../server/sidebar.json"),
"/aws/": require("../aws/sidebar.json"),
"/go": require("../go/sidebar.json"),
"/react": require("../react/sidebar.json"),
"/vue": require("../vue/sidebar.json"),
"/java": require("../java/sidebar.json"),
"/": [
{
text: "Home",

View File

@@ -1,3 +1,5 @@
# Frameworks
[Nuxtjs](/nuxt/)
[React](/react/)

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

@@ -0,0 +1,59 @@
# Testing
Test files in go are kept alongside the source code with postfix `_test.go`. Functions beginning with `Test*` that take an argument `(t *testing.T)` will be run as unit tests. A test fails if a condition leading to `t.Error` or `t.Fail` is met.
```bash
# Test everything in current directory
go test .
# Test everything in subdirectories
go test ./...
# See coverage percentage
go test -cover .
# Generate coverage file
go test -coverprofile=coverage.out .
# View code coverage from file
go tool cover -html=coverage.out
# Run individual test
go test -run Test_alpha_isPrime
# Run groups of tests
go test -run Test_alpha
```
## Test cases
Within a test function, you can run multiple related test cases by iterating over a slice of cases with `t.Run`.
```go
func Test_alpha_isPrime(t *testing.T) {
primeTests := []struct {
name string
num int
expected bool
message string
}{
{"prime", 7, true, "7 is prime!"},
{"not prime", 8, false, "8 is not prime, it is divisible by 2"},
{"zero", 0, false, "0 is not prime, by definition!"},
{"one", 1, false, "1 is not prime, by definition!"},
{"negative", -1, false, "Negative numbers are not prime, by definition!"},
}
for _, tt := range primeTests {
t.Run(tt.name, func(t *testing.T) {
actual, msg := isPrime(tt.num)
if actual != tt.expected {
t.Errorf("isPrime(%d): expected %t, actual %t", tt.num, tt.expected, actual)
}
if msg != tt.message {
t.Errorf("isPrime(%d): expected %s, actual %s", tt.num, tt.message, msg)
}
})
}
}
```

View File

@@ -0,0 +1,177 @@
# Collections
## Make
The `make` keyword is used to create slices, maps, and channels that are initialized and ready for use. For example, a slice is a three component object with a pointer to an array, a length, and a capacity. Until these are set, the slice is considered nil. The `make` keyword initialized these variables and makes the slice ready to use. It also returns value of type T instead of a pointer with `new`.
## Arrays
Arrays in Go are fixed length and must be declared at initialization.
```go
var a [10]int
a[0] = 1
```
## Slices
A slice is like an array but it does not have a fixed size. You can further slice a slice be specifying a low and high index, inclusive of the first and exclusive of the last.
```go
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s) // 3 5 7
s = primes[3:] // 7, 11, 13
s = primes[:2] // 2, 3
```
Slices are references to an underlying array, so if you create a slice of an array and edit that slice, you will also edit the underlying array.
```go
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names) // John Paul George Ringo
a := names[0:2]
b := names[1:3]
fmt.Println(a, b) // John Paul, Paul George
b[0] = "XXX"
fmt.Println(a, b) // John XXX, XXX George
fmt.Println(names) // John XXX George Ringo
```
### Length vs Capacity
The length of a slice is how many items are actually stored in the array, and the capacity is how many slots the array has. A slice is considered a nil slice if it has 0 length and capacity and no underlying array.
```go
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// Slice the slice to give it zero length.
s = s[:0]
printSlice(s)
// Extend its length.
s = s[:4]
printSlice(s)
// Drop its first two values.
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
// Output
// len=6 cap=6 [2 3 5 7 11 13]
// len=0 cap=6 []
// len=4 cap=6 [2 3 5 7]
// len=2 cap=4 [5 7]
```
You can create slices with specific length and capacity using `make`.
```go
x := make([]int, 0, 5) // len(x)=0, cap(x)=5
x = x[:cap(x)] // len(x)=5, cap(x)=5
x = x[1:] // len(x)=4, cap(x)=4
a := make([]int, 5)
// a len=5 cap=5 [0 0 0 0 0]
b := make([]int, 0, 5)
// b len=0 cap=5 []
c := b[:2]
// c len=2 cap=5 [0 0]
d := c[2:5]
// d len=3 cap=3 [0 0 0]
```
### Appending
If the underlying array of the slice is too small to accommodate the new elements, the returned slice will point to a newly allocated array.
```go
var s []int
// len=0 cap=0 []
// append works on nil slices.
s = append(s, 0)
// len=1 cap=1 [0]
// The slice grows as needed.
s = append(s, 1)
// len=2 cap=2 [0 1]
// We can add more than one element at a time.
s = append(s, 2, 3, 4)
// len=5 cap=6 [0 1 2 3 4]
```
## Maps
Maps are key-value pairs. They can be created with `make`.
```go
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex // cannot be used
func main() {
m = make(map[string]Vertex) // now can be used
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
// Map literal
var n = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
// Shorthand
var o = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
}
```
### Manipulating the map
```go
m[key] = elem
elem = m[key]
delete(m, key)
// Check if element exists
elem, ok = m[key]
```

View File

@@ -0,0 +1,205 @@
# Cocurrency
One of the strengths of Go is the simplicity of its cocurrency paradigm. A `goroutine` is essentially a thread that gets managed by the runtime.
```go
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
```
## Channels
Channels are typed pipelines that you can send and receive values between goroutines. Sending and receiving through a channel block the thread until the otherside is ready.
```go
ch := make(chan int)
ch <- v // Send v to channel ch.
v := <-ch // Receive from ch, and assign value to v.
```
```go
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
```
### Buffering
Channels can also be buffered to allow senders to continue sending messages to a channel as long as the buffer is not full. Receivers block when the buffer is empty.
```go
ch := make(chan int, 100)
```
### Closing
Channels can be closed to denote when no more values are going to be sent through the channel. Receivers can test for a closed channel by receiving a second parameter from the channel. `ok` will be false if the channel is closed. The sender should always close the channel, as sending on a closed channel will cause a panic.
```go
v, ok := <-ch
close(ch)
```
This can also be used with `range` to automatically receive the values from the channel and break the loop after the channel closes.
```go
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
```
### Select
Select is like a switch statement for channels. It blocks until a condition occurs and then runs that condition. If multiple are ready at the same time it will choose randomly. You can add a default case to do something without blocking.
```go
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func bomb() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
bomb()
}
```
## Mutex
Mutexes can be used to pass variables to multiple goroutines and making sure only one can operate on it at a time.
```go
package main
import (
"fmt"
"sync"
"time"
)
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mu.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mu.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
```

108
docs/go/basics/control.md Normal file
View File

@@ -0,0 +1,108 @@
# Control Flow
For the most part, control flow keywords in Go do not require parenthesis `()` for the conditional statements but do require brackets `{}` to contain the logic.
## For loops
For loops in Go are similar to C++ in that they have an inital, conditional, and post statement in the declaration. Initial and post statements are optional if not needed. In that case, this becomes Go's `while` loop. You can go even further by dropping the condition all together to create an forever loop.
```go
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
// While
sum = 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
// Forever loop
for {
// repeats forever
}
}
```
You can use the `range` keyword to iterate over a slice or map. With a slice, you will get both the index and a copy of the element for each iteration.
```go
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow { // i or v can be ignored with _
fmt.Printf("2**%d = %d\n", i, v)
}
```
## If/Else
If statements in Go can also include short statements to execute before the condition, and variables in these statements are scoped to the if/else block.
```go
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
else if v == lim {
fm.Printf("%g == %g\n", v, lim)
}
else {
fmt.Printf("%g >= %g\n", v, lim)
}
return lim
}
```
## Switch
In Go, only the matching case is run, meaning you do not need to include `break` like you would in C++. The switch case stops evaluating after one of the cases matches, so you can not run multiple cases even if it matches multiple. Switch can also be written with no condition, effectively turning it into a long if/else block.
```go
import (
"fmt"
"runtime"
"time"
)
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.\n", os)
}
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
```
## Defer
Defer can be used to delay a function call until the surrounding function returns. Defers can be stacked, and will execute on a last-in, first-out order.
```go
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
```

113
docs/go/basics/functions.md Normal file
View File

@@ -0,0 +1,113 @@
# Functions
Functions are declared with the `func` keyword. They can have zero or more arguments, and the types of the arguments come after the names with a space. Functions can also return multiple values, and this is how error checking is done in Go. You can even instantiate the return values of the function in the function definition (only use in short functions).
By default, arguments are passed by value. To pass by reference, you need to take a pointer as the argument to modify the reference.
```go
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func swap(x, y string) (string, string) {
return y, x
}
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(add(42, 13))
fmt.Println(swap("foo", "bar"))
fmt.Println(split(17))
}
```
Functions can also be saved as variables and passed to other functions.
```go
package main
import (
"fmt"
"math"
)
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}
```
## Closures
Closures are functions that reference variables outside of the function body. In the below example, the inner function of `adder` has access to its own sum.
```go
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
// 0 0
// 1 -2
// 3 -6
// 6 -12
// 10 -20
// 15 -30
// 21 -42
// 28 -56
// 36 -72
// 45 -90
```
## Generics
Functions can also take generic types with the below syntax. In the below example the generic type must implement the `comparable` constraint, meaning it has the `==` and `!=` operators.
```go
func Index[T comparable](s []T, x T) int {
for i, v := range s {
// v and x are type T, which has the comparable
// constraint, so we can use == here.
if v == x {
return i
}
}
return -1
}
```

View File

@@ -0,0 +1,68 @@
# Packages
Every Go program is made up of packages. When importing packages into a file, it is proper to use factored import statements, meaning combining all of the imports within parenthesis. The `main` package symbolizes code that will be run as a binary. Otherwise the package is a library.
Only names starting with Capital letters are exported from a package (public). Lowercase names will remain private within the package.
```go
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
}
```
## Projects
Projects can be broken up into subpackages so they can be imported separately. The `internal` directoy can not be imported by someone using your package. Binaries should be kept in the `cmd` folder. Within `cmd` all files and any other binary files should include `package main`. In subpackages like `auth`, `token`, `hash`, and `trace` should have their respective package names.
```
project-root-directory/
go.mod
modname.go
modname_test.go
auth/
auth.go
auth_test.go
token/
token.go
token_test.go
hash/
hash.go
internal/
trace/
trace.go
cmd/
prog1/
main.go
prog2/
main.go
```
`go.mod` includes the module name or where the code is located. For example, if the code is kept at `github.com/someuser/modname`, then the file should include:
```
module github.com/someuser/modname
```
In the importing code:
```go
import "github.com/someuser/modname"
import "github.com/someuser/modname/auth"
import "github.com/someuser/modname/auth/token"
import "github.com/someuser/modname/hash"
```
To install the binaries:
```bash
$ go install github.com/someuser/modname/prog1@latest
$ go install github.com/someuser/modname/prog2@latest
```

View File

@@ -0,0 +1,25 @@
# Pointers
Pointers, like C++, hold memory addresses of a value. The zero value of a pointer is `nil`. Pointers are declared with `*T` which is a pointer to type T. The `&` operator creates a pointer to its operand. You can access the pointer value with `*` to get and set.
```go
var p *int
i := 42
p = &i
fmt.Println(*p)
*p = 21
```
## Structs
To access values of a struct you are referencing with a pointer, you can use the shorthand of the `.` operator to access the value.
```go
type Vertex struct {
X int
Y int
}
p := &Vertex{1,2}
fmt.Println(p.Y)
p.X = 4
```

164
docs/go/basics/struct.md Normal file
View File

@@ -0,0 +1,164 @@
# Structs
Structs are collections of fields. They are defined as types and then created using brackets to declare the values. You can also create structs with the `new` keyword which creates a pointer of type T and initializes the object with zero values.
```go
import (
"fmt"
)
type Vertex struct {
X int
Y int
}
v := Vertex{1, 2}
v2 := Vertex{X: 2, Y: 4}
fmt.Println(v.X)
```
## Methods
Go does not have classes. Instead, functions can have `receivers` which relates to an instance of a struct and gets passed to the function. Methods can only be defined with a receiver that is in the same package, including built-in types.
By default, receivers are passed by value meaning any modifications to the receiver will not stick to the initial object. You can pass pointers as receivers to pass by reference and modify the reference. While functions require you to explicitly pass a pointer or a value, methods will automatically call the pointer method on a normal object and vice versa. In the below example, we call Scale on `v` and not `(%v).Scale(10)`. Pointer receivers also don't require you to copy the object which can be expensive for larger objects.
```go
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs()) // 5
v.Scale(10)
fmt.Println(v.Abs()) // 50
}
```
## Interfaces
Interfaces are definitions of structs that all implement the declared methods. One note is that if the type implements the method with a pointer receiver, then the pointer to that class is considered that interface but the underlying type is not (If M took a \*T, then you would not be able to instantiate an instance of T as I).
```go
package main
import "fmt"
type I interface {
M()
}
type T struct {
S string
}
// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
fmt.Println(t.S)
}
func main() {
var i I = T{"hello"}
i.M()
}
```
You can also check that a pointer is nil within the function to gracefully handle nil pointer exceptions at the function level.
```go
type T struct {
S string
}
func (t *T) M() {
if t == nil {
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}
```
### Empty Interfaces
Empty interfaces in Go are essentially an `any` type.
```go
package main
import "fmt"
func main() {
var i interface{}
// (<nil>, <nil>)
i = 42
// (42, int)
i = "hello"
// (hello, string)
}
type any interface{}
```
### Type Assertions
To check if an instance of an interface is actually an instance of an underlying class, you can check with the below syntax.
```go
t := i.(T) // Will panic if not type T
t, ok := i.(T) // Returns T instance and true if that type, or zero-value of T and false if not
// With switch
switch v := i.(type) {
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}
```
### Common Interfaces
#### Stringers
This is an object that has a function to describe itself as a string. Many functions from `fmt` look for this method.
```go
type Stringer interface {
String() string
}
```
#### Errors
Return a string when the error occurs.
```go
type error interface {
Error() string
}
```

View File

@@ -0,0 +1,84 @@
# Variables and Types
Variables are declared using the `var` keyword or using the initial assignment operator `:=` (only inside functions). The type of the variable comes last, and can be inferred if using the assignment operator. Variables can be at the package or function level. You can also declare constants with the `const` keyword, but cannot use the `:=` operator. Numeric constants can be used for high precision.
```go
package main
import "fmt"
var c, python, java bool
func main() {
var i int
j := 3 // int inferred
const k = "Can't change this"
const Big = 1 << 100 // 1 shifted left 100 times
fmt.Println(i, c, python, java)
}
```
## Types
The basic types in go are:
- bool
- string
- int, int8, int16, int32, int64
- uint, uint8, uint16, uint32, uint64, uintptr
- byte (alias for uint8)
- rune (alias for int32, represents a Unicode code point)
- float32, float64
- complex64, complex128
```go
package main
import (
"fmt"
"math/cmplx"
)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main() {
fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe) // bool, false
fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt) // uint64, 18446744073709551615
fmt.Printf("Type: %T Value: %v\n", z, z) // complex128, (2+3i)
}
```
### Default Values (Zero values)
When not given an initial value, the below types default to:
- bool - false
- string - ""
- numeric - 0
### Type Conversion
You can cast variables to different types by specifying the type in the below syntax.
```go
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
```
### Generic Types
Types can also be generic to work with multiple different types of arguments.
```go
// Singly linked list
type Node[T any] struct {
next *Node[T]
val T
}
```

View File

@@ -1 +1,11 @@
# 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

@@ -1,6 +1,28 @@
[
{
"text": "Go Basics",
"items": [{ "text": "Introduction", "link": "/go/" }]
"items": [
{ "text": "Introduction", "link": "/go/" },
{ "text": "Packages", "link": "/go/basics/packages" },
{ "text": "Variables and Types", "link": "/go/basics/variables" },
{ "text": "Functions", "link": "/go/basics/functions" },
{ "text": "Control Flow", "link": "/go/basics/control" },
{ "text": "Collections", "link": "/go/basics/collection" },
{ "text": "Pointers", "link": "/go/basics/pointers" },
{ "text": "Structs", "link": "/go/basics/struct" },
{ "text": "Concurrency", "link": "/go/basics/concurrency" }
]
},
{
"text": "Go Advanced",
"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

@@ -1,8 +1,8 @@
# Arrays
An array is a linear collection of data values that are accessible at numbered indices, starting at 0. Arrays are stored in continguous memory. There are two types of arrays, **static** and **dynamic**. **Static** arrays are fixed length, meaning they will always take up the same amount of memory.
An array is a linear collection of data values that are accessible at numbered indices, starting at 0. Arrays are stored in continguous memory. There are two types of arrays, **static** and **dynamic**. **Static** arrays are fixed length, meaning they will always take up the same amount of memory.
**Dynamic** arrays can change in size, and in statically typed languages like C++ there are called vectors. **Dynamic** arrays allocate double the amount of memory you have specified to account for adding values to it. When you reach a full array, then it allocates a new array with double the size and copies the values over which is an O(n) operation.
**Dynamic** arrays can change in size, and in statically typed languages like C++ there are called vectors. **Dynamic** arrays allocate double the amount of memory you have specified to account for adding values to it. When you reach a full array, then it allocates a new array with double the size and copies the values over which is an O(n) operation.
## Complexity
@@ -16,3 +16,33 @@ An array is a linear collection of data values that are accessible at numbered i
- Removing from end: O(1)
- Copying: O(n)
- Traversing (including mapping, filtering, etc): O(n)
## Common Algorithms
### Binary Search
Binary search is useful when trying to find values in a sorted array. It works by picking a middle point to check, and then dividing the array in half based on whether the value is too big or small.
```python
def binarySearch(nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
if len(nums) == 0:
return -1
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
# End Condition: left > right
return -1
```

View File

@@ -4,15 +4,15 @@ A graph is a collection of nodes called **vertices** that are connected to one a
## 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
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
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
@@ -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)
- 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
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
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
**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.
### 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": "Strings", "link": "/interview/ds/string"},
{"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": "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

@@ -3,3 +3,7 @@
[Python](/python/)
[Rust](/rust/)
[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": "Testing", "link": "/python/advanced/tests"},
{"text": "Timing", "link": "/python/advanced/timing"},
{"text": "Webscraping", "link": "/python/advanced/webscraping"},
{"text": "Zipping", "link": "/python/advanced/zip"}
]
}

99
docs/react/components.md Normal file
View File

@@ -0,0 +1,99 @@
# Components
Components are the building blocks of a React application. They contained logic and templates that are reused throughout your applications. Components are essentially functions that return some JSX that can then be imported and used by parent components.
```tsx
export default function Component() {
return <h1>Some component!</h1>;
}
```
## Props
Props provide a way to pass data from a parent component into a child component to somehow influence their behavior. This is a one way flow, meaning changes to these props only flow from the parent to the child, but not the reverse.
```tsx
// Parent
function Parent() {
return <Child color="red" />;
}
// Child
function Child(props) {
return <h1>{props.color}</h1>;
}
// or
function Child({ color }) {
return <h1>{color}</h1>;
}
```
Props however can also be functions, which is how you would handle passing data from the child back to the parent.
```tsx
function Parent() {
const onSearch = (term) => {
console.log("Searching for", term);
};
return <Child onSubmit={onSearch} />;
}
import { useState } from "react";
function Child({ onSubmit }) {
const [term, setTerm] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(term);
};
return (
<form onSubmit={handleSubmit}>
<input value={term} onChange={(e) => setTerm(e.target.value)} />
</form>
);
}
```
### Children Prop
There is a special prop called `children` which works similarly to slots in Vue. When you use a component and pass in some text or elements within the opening and closing tags, that gets passed as a prop `children` which you can use within your component.
```tsx
function Child({ children }) {
return <h1>{children}</h1>;
}
function Parent() {
return <Child>This is my children!</Child>;
}
```
### With Typescript
When using Typescript, you can define an interface for your props to improve intelisense in your IDE.
```tsx
interface MyProps {
color: string | null; // can be either string or null
shade?: string; // optional prop
}
function Child({ color, shade });
```
## Feeding classNames to child components
To pass classNames to your child components, we can use a package called `classnames` to aggregate multiple classes.
```tsx
import classNames from "classnames";
function Child({ className }) {
const classes = classNames("myclass1 myclass2", className);
return <div className={classes}>My component with custom classes!</div>;
}
```

78
docs/react/context.md Normal file
View File

@@ -0,0 +1,78 @@
# Context
Context is a way of sharing state with many child components without having to pass them via props.
```tsx
// context/foo.ts
import { createContext, useState } from "react";
const MyContext = createContext();
function Provider({ children }) {
const [value, setValue] = useState(0);
const incrementValue = () => {
setValue(value + 1);
};
const valueToShare = {
value,
incrementValue,
};
return (
<MyContext.Provider value={valueToShare}>{children}</MyContext.Provider>
);
}
export { Provider };
export default MyContext;
// In wrapping component...
import { Provider } from "context/foo";
function Parent() {
return (
<Provider>
<Children />
</Provider>
);
}
// In consuming component
import { useContext } from "react";
import { MyContext } from "../context/foo";
function Child() {
const { value, incrementValue } = useContext(MyContext);
}
```
## useCallback
This hook is used to fix some bugs when calling functions stored within contexts from useEffect in a child component. Since useEffect requires the function to be added as a dependency in the second argument, it will rerun that effect whenever that function is called because calling it forces the context to rerender, which in turn creates a new version of that function. So to fix this, we need to use useCallback to create a stable reference to that function.
```tsx
// context/foo.ts
import { createContext, useState, useCallback } from "react";
const MyContext = createContext();
function Provider({ children }) {
const [value, setValue] = useState(0);
const fetchValue = useCallback(() => {
newValue = fetch("localhost:8080");
setValue(newValue);
}, []);
const valueToShare = {
value,
fetchValue,
};
return (
<MyContext.Provider value={valueToShare}>{children}</MyContext.Provider>
);
}
```

364
docs/react/fetching.md Normal file
View File

@@ -0,0 +1,364 @@
# Data Fetching
There are a few different ways to fetch data from a server in React using a Redux store. One note is that we should never make API calls within our reducers.
## React Router
React Router is a viable option for smaller projects that do not require larger state management. This can be accomplished by using the `loader` property on a route which can be used to fetch data.
```ts
// function for executing the query and returning the results
// ex. api/queries/searchPackages.ts
export interface PackageSummary {
name: string;
version: string;
description: string;
keywords?: string[];
}
interface SearchResponse {
objects: {
package: {
name: string;
description: string;
version: string;
keywords: string[];
};
}[];
}
export async function searchPackages(term: string): Promise<PackageSummary[]> {
const res = await fetch(
`https://registry.npmjs.org/-/v1/search?text=${term}&size=10`
);
const data: SearchResponse = await res.json();
return data.objects.map(
({ package: { name, description, version, keywords } }) => {
return {
name,
description,
version,
keywords,
};
}
);
}
```
```ts
// loader function, ex. pages/search/searchLoader.ts
import { searchPackages } from "../../api/queries/searchPackages";
import type { PackageSummary } from "../../api/queries/packageSummary";
export interface SearchLoaderResult {
searchResults: PackageSummary[];
}
export async function searchLoader({
request, // request includes the url we are currently at in our router, can be parsed for query params
params, // named params in the route, ex. /packages/:name would be params.name
}: {
request: Request;
}): Promise<SearchLoaderResult> {
const { searchParams } = new URL(request.url);
const term = searchParams.get("term");
if (!term) {
throw new Error("Search term must be provided");
}
const results = await searchPackages(term);
// Good habit to return object, makes it easier to add more data in the future
return {
searchResults: results,
};
}
```
```ts
// router
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Root from "./pages/Root";
import HomePage from "./pages/home/HomePage";
import { homeLoader } from "./pages/home/homeLoader";
import SearchPage from "./pages/search/SearchPage";
import { searchLoader } from "./pages/search/searchLoader";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
children: [
{
index: true,
element: <HomePage />,
loader: homeLoader,
},
{
path: "/search",
element: <SearchPage />,
loader: searchLoader,
},
],
},
]);
```
```ts
// in component
import { useLoaderData } from "react-router-dom";
import { SearchLoaderResult } from "./searchLoader";
export default function SearchPage() {
const { searchResults } = useLoaderData() as SearchLoaderResult;
// ... use search results
}
```
## Redux Toolkit Queries
RTKQ is a library that allows you to create APIs to define how you want to query and manipulate your data via requests. It also automatically creates some hooks to let you know when data is loading, fetching, and functions to refetch, as well as hooks to automatically refetch data when some piece of data is changed by one of the API endpoints.
```ts
// store/apis/photosApi.ts
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { faker } from "@faker-js/faker";
import Album from "../models/Album";
import Photo from "../models/Photo";
const photosApi = createApi({
// path in the root state variable
reducerPath: "photos",
baseQuery: fetchBaseQuery({
baseUrl: "http://localhost:3005",
}),
tagTypes: ["Photo", "AlbumPhotos"],
endpoints(builder) {
return {
fetchPhotos: builder.query({
// Create a list of tags that will trigger a refetch if any other endpoint invalidates one of the tags
providesTags: (result: Photo[], _, album: Album) => {
const tags = result.map((photo) => {
return { type: "Photo", id: photo.id };
});
// Is is also common to use a tag like { type: "Photo", id: "LIST/ALL/*" } to have an entry to invalidate the entire list
// However, this will cause a refetch of all Photos lists if there are multiple
tags.push({ type: "AlbumPhotos", id: album.id });
return tags;
},
query: (album: Album) => {
return {
url: "/photos",
method: "GET",
// appended onto url
params: {
albumId: album.id,
},
};
},
}),
addPhoto: builder.mutation({
// triggers refetching for this album
invalidatesTags: (_, __, album: Album) => {
return [{ type: "AlbumPhotos", id: album.id }];
},
query: (album: Album) => ({
url: "/photos",
method: "POST",
// request body parameters
body: {
albumId: album.id,
url: faker.image.abstract(150, 150, true),
},
}),
}),
removePhoto: builder.mutation({
invalidatesTags: (_, __, photo: Photo) => {
return [{ type: "Photo", id: photo.id }];
},
query: (photo: Photo) => ({
url: `/photos/${photo.id}`,
method: "DELETE",
}),
}),
};
},
});
// Automatically creates functions to access your endpoints
export const {
useFetchPhotosQuery,
useAddPhotoMutation,
useRemovePhotoMutation,
} = photosApi;
export { photosApi };
```
```ts
// store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import { setupListeners } from "@reduxjs/toolkit/query";
import { photosApi } from "./apis/photosApi";
export const store = configureStore({
reducer: {
[photosApi.reducerPath]: photosApi.reducer,
},
middleware: (getDefaultMiddleware) => {
return getDefaultMiddleware().concat(photosApi.middleware);
},
});
setupListeners(store.dispatch);
export {
useFetchPhotosQuery,
useAddPhotoMutation,
useRemovePhotoMutation,
} from "./apis/photosApi";
```
```tsx
// component
import { useFetchPhotosQuery, useAddPhotoMutation } from "../store";
export default function Comp({ album }) {
// Automatically loads data on component creation
const { data, isLoading, error, isFetching, refetch } =
useFetchPhotosQuery(album);
// Hook to call function when needed
// results contains data, isFetching, error, etc and is updated when function is called and request processes
const [addPhoto, results] = useAddPhotoMutation();
}
```
## Thunks
Thunks are a vanilla way of using Redux Toolkit to send requests and keep track of what stage those requests are in. These can be listened to in a slice's `extraReducers` to perform state updates. Each thunk has a `pending`, `fulfilled`, and `rejected` state.
```ts
// store/thunks/addUser.ts
import { createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
import { faker } from "@faker-js/faker";
const addUser = createAsyncThunk("users/add", async () => {
const response = await axios.post("http://localhost:3005/users", {
name: faker.name.fullName(),
});
return response.data;
});
export { addUser };
```
```ts
// store/slices/user.ts
import { createSlice } from "@reduxjs/toolkit";
import { fetchUsers } from "../thunks/fetchUsers";
const initialState: UserState = {
data: [],
isLoading: false,
error: null,
};
const usersSlice = createSlice({
name: "users",
initialState,
reducers: {
setUsers: (state, action) => {
state.data = action.payload;
},
},
extraReducers: (builder) => {
// fetch users
builder.addCase(fetchUsers.pending, (state, _) => {
state.isLoading = true;
state.error = null;
});
builder.addCase(fetchUsers.fulfilled, (state, action) => {
state.isLoading = false;
state.data = action.payload;
});
builder.addCase(fetchUsers.rejected, (state, action) => {
state.isLoading = false;
state.error = action.error || "An error occurred.";
});
},
});
export const usersReducer = usersSlice.reducer;
```
```ts
// store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import { usersReducer } from "./slices/usersSlice";
export const store = configureStore({
reducer: {
users: usersReducer,
},
});
// Export thunk function from store index
export * from "./thunks/fetchUsers";
export * from "./thunks/addUser";
export * from "./thunks/deleteUser";
```
### Optional Custom Hook
```ts
// hooks/use-thunk.ts
import { AsyncThunk } from "@reduxjs/toolkit";
import { useState, useCallback } from "react";
import { useAppDispatch } from "./store";
// Custom hook for handling loading and error states
export default function useThunk(
thunk: AsyncThunk<any, any | void, {}>
): [(arg?: any) => void, boolean, object | null] {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<object | null>(null);
const dispatch = useAppDispatch();
const runThunk = useCallback(
(arg?: any) => {
setIsLoading(true);
dispatch(thunk(arg))
.unwrap()
.catch((err: object) => setError(err))
.finally(() => setIsLoading(false));
},
[dispatch, thunk]
);
return [runThunk, isLoading, error];
}
```
```ts
// use in component
import useThunk from "../hooks/use-thunk";
import { fetchUsers, addUser } from "../store";
export default function UsersList() {
const [runFetchUsers, isFetching, fetchError] = useThunk(fetchUsers);
const [runCreateUser, isCreating, createError] = useThunk(addUser);
useEffect(() => {
runFetchUsers();
}, [runFetchUsers]);
const handleUserAdd = () => {
runCreateUser();
};
}
```

38
docs/react/index.md Normal file
View File

@@ -0,0 +1,38 @@
# React
React is a Javascript/Typescript framework for creating responsive single page applications in the web.
## Creating an App
```bash
npm create vite@latest <app-name> -- --template=react-ts
cd <app-name>
npm install
npm run dev
```
### Adding Tailwindcss (Optional)
```bash
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
```js
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
```
```css
/* index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
```

86
docs/react/jsx.md Normal file
View File

@@ -0,0 +1,86 @@
# JSX
## Rendering a List
```tsx
import { Fragment } from "react";
function Comp() {
const items = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" },
];
return (
<div>
{items.map((item) => {
return <Fragment key={item.id}>{item.name}</Fragment>;
})}
</div>
);
}
```
## Conditional Rendering
```tsx
function Comp() {
const items = [
{ id: 1, name: "Item 1", show: true },
{ id: 2, name: "Item 2", show: true },
{ id: 3, name: "Item 3", show: false },
];
return (
<div>
{items.map((item) => {
return {item.show && <Fragment key={item.id}>{item.name}</Fragment>};
})}
</div>
);
}
```
## Checking if item is valid
```tsx
function Comp() {
const items: object[] | null = null;
return <div>{items?.length || "No items"}</div>;
}
```
## Two-way data binding
In order to have two-way data binding between the component and an input or another child component, you need to make use of the `onChange` event or some other event callback.
```tsx
import { useState } from "react";
function Comp() {
const [value, setValue] = useState("");
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
```
## Handling number inputs
```tsx
import { useState } from "react";
function Comp() {
const [value, setValue] = useState(0);
const handleChange = (e) => {
// will be NaN if invalid input or empty
newValue = parseInt(e.target.value) || 0;
// do something with new value
};
// need || "" or else it will always have an annoying 0 in the input
return <input type="number" value={value || ""} onChange={handleChange} />;
}
```

184
docs/react/redux.md Normal file
View File

@@ -0,0 +1,184 @@
# Redux
Redux is a centralized state store that can be accessed from any component in your application. It acts as basically a centralized reducer, where it has a state and an dispatch method to update that state. Redux Toolkit is a library that simplifies creating actions to interact with the store, and is the recommended approach going forward.
```ts
// store/slices/song.ts
import { createSlice } from "@reduxjs/toolkit";
import { reset } from "../actions";
const songsSlice = createSlice({
name: "song",
initialState: [],
reducers: {
addSong(state, action) {
state.push(action.payload);
},
removeSong(state, action) {
// action.payload === string, the song we want to remove
const index = state.indexOf(action.payload);
state.splice(index, 1);
},
},
// listen to manual actions not created by this generator
extraReducers(builder) {
builder.addCase(reset, (state, action) => {
return [];
});
},
});
export const { addSong, removeSong } = songsSlice.actions;
// can also export this as the default
export const songsReducer = songsSlice.reducer;
```
```ts
// store/actions.ts
// Declare common actions shared by multiple reducers
import { createAction } from "@reduxjs/toolkit";
export const reset = createAction("app/reset");
```
```ts
// store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import { songsReducer, addSong, removeSong } from "./slices/songsSlice";
import { reset } from "./actions";
const store = configureStore({
reducer: {
songs: songsReducer,
},
});
// Export store and slice actions
export { store, reset, addSong, removeSong };
```
```ts
// index.ts
import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import App from "./App";
import { store } from "./store";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<Provider store={store}>
<App />
</Provider>
);
```
```tsx
// components/songs.tsx
import { useDispatch, useSelector } from "react-redux";
import { createRandomSong } from "../data";
import { addSong, removeSong } from "../store";
function SongPlaylist() {
// object to call actions
const dispatch = useDispatch();
// fetch state
// can also filter/map state within here if you want
const songPlaylist = useSelector((state) => {
return state.songs;
});
// dispatch actions with some payload
const handleSongAdd = (song) => {
dispatch(addSong(song));
};
const handleSongRemove = (song) => {
dispatch(removeSong(song));
};
const renderedSongs = songPlaylist.map((song) => {
return (
<li key={song}>
{song}
<button
onClick={() => handleSongRemove(song)}
className="button is-danger"
>
X
</button>
</li>
);
});
return (
<div className="content">
<div className="table-header">
<h3 className="subtitle is-3">Song Playlist</h3>
<div className="buttons">
<button
onClick={() => handleSongAdd(createRandomSong())}
className="button is-link"
>
+ Add Song to Playlist
</button>
</div>
</div>
<ul>{renderedSongs}</ul>
</div>
);
}
export default SongPlaylist;
```
## Optimize with Typescript
```ts
// store/index.ts
export type AppStore = typeof store;
export type RootState = ReturnType<AppStore["getState"]>;
export type AppDispatch = AppStore["dispatch"];
```
```ts
// custom hook
import type { TypedUseSelectorHook } from "react-redux";
import { useDispatch, useSelector, useStore } from "react-redux";
import type { AppDispatch, AppStore, RootState } from "./store";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();
export const useAppStore = useStore.withTypes<AppStore>();
```
```ts
// typing your slices
import type { RootState } from "../";
interface CounterState {
value: number;
}
// Define the initial state using that type
const initialState: CounterState = {
value: 0,
};
export const counterSlice = createSlice({
name: "counter",
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},
});
// Other code such as selectors can use the imported `RootState` type
export const selectCount = (state: RootState) => state.counter.value;
```

241
docs/react/routing.md Normal file
View File

@@ -0,0 +1,241 @@
# Routing
## Vanilla Routing with Context
```tsx
// context/navigation.ts
import { createContext, useState, useEffect } from "react";
const NavigationContext = createContext();
function NavigationProvider({ children }) {
const [currentPath, setCurrentPath] = useState(window.location.pathname);
// handle front and back arrows
useEffect(() => {
const handler = () => {
setCurrentPath(window.location.pathname);
};
window.addEventListener("popstate", handler);
return () => {
window.removeEventListener("popstate", handler);
};
}, []);
const navigate = (to) => {
window.history.pushState({}, "", to);
setCurrentPath(to);
};
return (
<NavigationContext.Provider value={{ currentPath, navigate }}>
{children}
</NavigationContext.Provider>
);
}
export { NavigationProvider };
export default NavigationContext;
```
```tsx
// Route component for conditionally rendering components
import useNavigation from "../hooks/use-navigation";
function Route({ path, children }) {
const { currentPath } = useNavigation();
if (path === currentPath) {
return children;
}
return null;
}
export default Route;
```
```tsx
// example link component
import classNames from "classnames";
import useNavigation from "../hooks/use-navigation";
function Link({ to, children, className, activeClassName }) {
const { navigate, currentPath } = useNavigation();
const classes = classNames(
"text-blue-500",
className,
currentPath === to && activeClassName // apply styling if currently on this page
);
const handleClick = (event) => {
// handle opening new tab, use default behavior
if (event.metaKey || event.ctrlKey) {
return;
}
event.preventDefault();
navigate(to);
};
return (
<a className={classes} href={to} onClick={handleClick}>
{children}
</a>
);
}
export default Link;
```
```tsx
// Navbar component
import Link from "./Link";
function NavBar() {
const links = [
{ label: "Dropdown", path: "/" },
{ label: "Accordion", path: "/accordion" },
{ label: "Buttons", path: "/buttons" },
];
const renderedLinks = links.map((link) => {
return (
<Link
key={link.label}
to={link.path}
className="mb-3"
activeClassName="font-bold border-l-4 border-blue-500 pl-2"
>
{link.label}
</Link>
);
});
return (
<div className="sticky top-0 overflow-y-scroll flex flex-col items-start">
{renderedLinks}
</div>
);
}
export default NavBar;
```
```tsx
// App.ts
import Sidebar from "./components/Sidebar";
import Route from "./components/Route";
import AccordionPage from "./pages/AccordionPage";
import DropdownPage from "./pages/DropdownPage";
import ButtonPage from "./pages/ButtonPage";
function App() {
return (
<div className="container mx-auto grid grid-cols-6 gap-4 mt-4">
<Sidebar />
<div className="col-span-5">
<Route path="/accordion">
<AccordionPage />
</Route>
<Route path="/">
<DropdownPage />
</Route>
<Route path="/buttons">
<ButtonPage />
</Route>
</div>
</div>
);
}
export default App;
```
## Using React Router library
Documentation [here](https://reactrouter.com/home).
```tsx
// main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import HomePage from "./pages/HomePage";
import NotFoundPage from "./pages/NotFoundPage";
import ProfilePage from "./pages/ProfilePage";
import ProfilesPage from "./pages/ProfilesPage";
import "./index.css";
const router = createBrowserRouter([
{
path: "/",
element: <HomePage />,
errorElement: <NotFoundPage />,
},
{
path: "/profiles",
element: <ProfilesPage />,
children: [
{
path: "/profiles/:profileId",
element: <ProfilePage />,
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
```
```tsx
// ProfilesPage.tsx with child component
import { NavLink, Outlet } from "react-router-dom";
export default function ProfilesPage() {
const profiles = [1, 2, 3, 4, 5];
return (
<div className="flex gap-2">
<div className="flex flex-col gap-2">
{profiles.map((profile) => (
// NavLink allows you to apply styling if the link is active
<NavLink
key={profile}
to={`/profiles/${profile}`}
className={({ isActive }) => {
return isActive ? "text-primary-700" : "";
}}
>
Profile {profile}
</NavLink>
))}
</div>
<!-- Outlet displays child component links if active -->
<Outlet />
</div>
);
}
```
```tsx
// ProfilePage component using query params
import { useParams } from "react-router-dom";
export default function ProfilePage() {
const params = useParams<{ profileId: string }>();
return (
<div>
<h1>Profile Page {params.profileId}</h1>
</div>
);
}
```

15
docs/react/sidebar.json Normal file
View File

@@ -0,0 +1,15 @@
[
{
"text": "React Basics",
"items": [
{ "text": "Introduction", "link": "/react/" },
{ "text": "JSX", "link": "/react/jsx" },
{ "text": "Components", "link": "/react/components" },
{ "text": "State", "link": "/react/state" },
{ "text": "Context", "link": "/react/context" },
{ "text": "Routing", "link": "/react/routing" },
{ "text": "Redux", "link": "/react/redux" },
{ "text": "Data Fetching", "link": "/react/fetching" }
]
}
]

175
docs/react/state.md Normal file
View File

@@ -0,0 +1,175 @@
# State and Events
State allows you to have the user interace with your application, update some data under the hood, and have that reflected in the page.
## Events
Events means how you handle some user actions in your application. A full list of possible events can be found [here](https://react.dev/reference/react-dom/components/common#). Essentially, you define functions that are called when certain actions are performed on elements in your page.
```tsx
function App() {
const handleClick = () => {
console.log("button clicked");
};
return (
<div>
<button onClick={handleClick}>Click me</button>;
<button
onClick={() => {
console.log("Inline function click");
}}
>
Click me too!
</button>
</div>
);
}
```
## useState
State tells your application to rerender the page when something changes in your application.
```tsx
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
};
return <button onClick={handleClick} />;
}
```
## useReducer
A Reducer is a way of combining multiple state variables into one object. This allows you to reduce the number of rerenders if multiple pieces of state are changing at the same time. `useReducer` takes a `reducer` function and an initial state. This `reducer` function will run whenever we call `dispatch`, and the argument to `dispatch` will be passed as an argument `action` along with the current state. The `action` argument is usually an object which tells the reducer what type of update to do. Whatever is returned from this function will be the new state. The `reducer` function cannot include async/await, promises, requests, etc. It is only for updating the state. You should not update the state directly unless you are using Immer, which is a library that automatically returns the new state for you.
```tsx
import produce from "immer";
import { useReducer } from "react";
// Use constants for action to prevent typos
const INCREMENT_COUNT = "increment";
const SET_VALUE_TO_ADD = "change_value_to_add";
const DECREMENT_COUNT = "decrement";
const ADD_VALUE_TO_COUNT = "add_value_to_count";
const reducer = (state, action) => {
switch (action.type) {
case INCREMENT_COUNT:
state.count = state.count + 1;
return;
// If not using immer, you would need to do something like...
return {
...state,
count: state.count + 1,
};
case DECREMENT_COUNT:
state.count = state.count - 1;
return;
case ADD_VALUE_TO_COUNT:
state.count = state.count + state.valueToAdd;
state.valueToAdd = 0;
return;
case SET_VALUE_TO_ADD:
state.valueToAdd = action.payload;
return;
default:
return;
}
};
function CounterPage({ initialCount }) {
// wrapper reducer with produce to use immer
const [state, dispatch] = useReducer(produce(reducer), {
count: initialCount,
valueToAdd: 0,
});
const increment = () => {
dispatch({
type: INCREMENT_COUNT,
});
};
const decrement = () => {
dispatch({
type: DECREMENT_COUNT,
});
};
const handleChange = (event) => {
const value = parseInt(event.target.value) || 0;
dispatch({
type: SET_VALUE_TO_ADD,
payload: value,
});
};
const handleSubmit = (event) => {
event.preventDefault();
dispatch({
type: ADD_VALUE_TO_COUNT,
});
};
}
export default CounterPage;
```
### Binding inputs
```tsx
function Input({ onSubmit }) {
const [text, setText] = useState("");
const handleChange = (event) => {
setText(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
onSubmit(text);
setText("");
};
return (
<div>
<form onSubmit={handleSubmit}>
<input className="input" value={title} onChange={handleChange} />
<button className="button">Submit</button>
</form>
</div>
);
}
```
## useEffect
useEffect is a function that runs when the application is initially rendered and sometimes when it is rerendered. The arrow function provided is always called on the initial render, and if a variable passed in the second argument is changed in the rerender, then the arrow function is called again. If no second argument is passed, then the function is called on every rerender.
```tsx
import { useEffect, useState } from "react";
function Comp() {
useEffect(() => {
console.log("Run on initial render");
}, []);
useEffect(() => {
console.log("Run on every render");
});
const [text, setText] = useState("");
useEffect(() => {
console.log("Run when 'text' is changed");
}, [text]);
}
```
The only thing we can return from useEffect is a function, and this function gets called before the next time the useEffect function is run, so from the second render on.

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`.
```rust
#[allow(dead_code)]
pub fn greeting(_name: &str) -> String {
String::from("Hello!")
}
#[cfg(test)]
mod tests {
use super::*; // Use this to call functions in the current file
@@ -33,7 +38,31 @@ mod tests {
#[test]
#[should_panic]
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
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
fn returns_summarizable() -> impl Summary {

View File

@@ -17,8 +17,11 @@ if num < 3 {
```rust
let config_max = Some(3u8);
let age: Result<u8, _> = "34".parse();
if let Some(max) = config_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> {
match x {
None => None,
Some(i) => Some(i + 1),
}
match x {
None => None,
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];
```
## 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
```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/" }
]
}
]