mirror of
https://gitlab.com/djdietrick/docs
synced 2026-05-03 05:00:54 -04:00
Compare commits
9 Commits
894a15525a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c601784b5c | |||
| cfaeadf198 | |||
| 6ff585e133 | |||
| f619093d76 | |||
| c2366ac927 | |||
| 65ead5a6f2 | |||
| f69fcfc50b | |||
| 2644b07951 | |||
| 9458db3381 |
46
docs/.vitepress/cache/deps/@theme_index.js
vendored
46
docs/.vitepress/cache/deps/@theme_index.js
vendored
@@ -9,31 +9,31 @@ import {
|
|||||||
} from "./chunk-3YS4HNIT.js";
|
} from "./chunk-3YS4HNIT.js";
|
||||||
|
|
||||||
// node_modules/vitepress/dist/client/theme-default/index.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
|
// 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 "/Users/djdietrick/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 "/Users/djdietrick/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 "/Users/djdietrick/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 "/Users/djdietrick/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 "/Users/djdietrick/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 "/Users/djdietrick/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 "/Users/djdietrick/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 "/Users/djdietrick/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 VPBadge from "/Users/djdietrick/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 Layout from "/Users/djdietrick/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 default2 } from "/Users/djdietrick/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 default3 } from "/Users/djdietrick/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 default4 } from "/Users/djdietrick/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 default5 } from "/Users/djdietrick/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 default6 } from "/Users/djdietrick/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 default7 } from "/Users/djdietrick/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 default8 } from "/Users/djdietrick/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 default9 } from "/Users/djdietrick/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 default10 } from "/Users/djdietrick/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 default11 } from "/Users/djdietrick/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 default12 } from "/Users/djdietrick/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 { 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
|
// node_modules/vitepress/dist/client/shared.js
|
||||||
var inBrowser = typeof document !== "undefined";
|
var inBrowser = typeof document !== "undefined";
|
||||||
|
|||||||
16
docs/.vitepress/cache/deps/_metadata.json
vendored
16
docs/.vitepress/cache/deps/_metadata.json
vendored
@@ -1,31 +1,31 @@
|
|||||||
{
|
{
|
||||||
"hash": "3c715ca3",
|
"hash": "b31dd5f2",
|
||||||
"configHash": "9ebaad5b",
|
"configHash": "9af315cb",
|
||||||
"lockfileHash": "ebeae15b",
|
"lockfileHash": "951d497c",
|
||||||
"browserHash": "0b0ad317",
|
"browserHash": "14d9b0dd",
|
||||||
"optimized": {
|
"optimized": {
|
||||||
"vue": {
|
"vue": {
|
||||||
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
|
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
|
||||||
"file": "vue.js",
|
"file": "vue.js",
|
||||||
"fileHash": "7cf78117",
|
"fileHash": "6eb7142a",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vitepress > @vue/devtools-api": {
|
"vitepress > @vue/devtools-api": {
|
||||||
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
|
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
|
||||||
"file": "vitepress___@vue_devtools-api.js",
|
"file": "vitepress___@vue_devtools-api.js",
|
||||||
"fileHash": "98f5c738",
|
"fileHash": "f77c0dac",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vitepress > @vueuse/core": {
|
"vitepress > @vueuse/core": {
|
||||||
"src": "../../../../node_modules/@vueuse/core/index.mjs",
|
"src": "../../../../node_modules/@vueuse/core/index.mjs",
|
||||||
"file": "vitepress___@vueuse_core.js",
|
"file": "vitepress___@vueuse_core.js",
|
||||||
"fileHash": "2d266902",
|
"fileHash": "4488c276",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"@theme/index": {
|
"@theme/index": {
|
||||||
"src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js",
|
"src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js",
|
||||||
"file": "@theme_index.js",
|
"file": "@theme_index.js",
|
||||||
"fileHash": "2eb1f2ce",
|
"fileHash": "f16ffef2",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export default {
|
|||||||
"/aws/": require("../aws/sidebar.json"),
|
"/aws/": require("../aws/sidebar.json"),
|
||||||
"/go": require("../go/sidebar.json"),
|
"/go": require("../go/sidebar.json"),
|
||||||
"/react": require("../react/sidebar.json"),
|
"/react": require("../react/sidebar.json"),
|
||||||
|
"/vue": require("../vue/sidebar.json"),
|
||||||
"/java": require("../java/sidebar.json"),
|
"/java": require("../java/sidebar.json"),
|
||||||
"/": [
|
"/": [
|
||||||
{
|
{
|
||||||
|
|||||||
41
docs/go/advanced/context.md
Normal file
41
docs/go/advanced/context.md
Normal 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
555
docs/go/advanced/db.md
Normal 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
62
docs/go/advanced/flags.md
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
@@ -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
357
docs/go/advanced/http.md
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -1 +1,11 @@
|
|||||||
# Go
|
# Go
|
||||||
|
|
||||||
|
Go is a statically-typed, compiled, and open source programming language created by Google.
|
||||||
|
|
||||||
|
## Creating a New Project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir <project_name>
|
||||||
|
cd <project_name>
|
||||||
|
go mod init gitlab.com/djdietrick/<project_name>
|
||||||
|
```
|
||||||
|
|||||||
@@ -16,9 +16,13 @@
|
|||||||
{
|
{
|
||||||
"text": "Go Advanced",
|
"text": "Go Advanced",
|
||||||
"items": [
|
"items": [
|
||||||
|
{"text": "Context", "link": "/go/advanced/context"},
|
||||||
|
{"text": "Flags", "link": "/go/advanced/flags"},
|
||||||
{ "text": "Testing", "link": "/go/advanced/testing" },
|
{ "text": "Testing", "link": "/go/advanced/testing" },
|
||||||
|
{"text": "HTTP", "link": "/go/advanced/http"},
|
||||||
{"text": "RPC", "link": "/go/advanced/rpc"},
|
{"text": "RPC", "link": "/go/advanced/rpc"},
|
||||||
{"text": "gRPC", "link": "/go/advanced/grpc"}
|
{"text": "gRPC", "link": "/go/advanced/grpc"},
|
||||||
|
{"text": "Databases", "link": "/go/advanced/db"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -31,3 +31,136 @@ The space complexity of a graph is O(v+e) where v is the number of vertices and
|
|||||||
|
|
||||||
- Depth First Search Traversing: O(v+e)
|
- Depth First Search Traversing: O(v+e)
|
||||||
- Bredth First Search Traversing: O(v+e)
|
- Bredth First Search Traversing: O(v+e)
|
||||||
|
|
||||||
|
## Searching
|
||||||
|
|
||||||
|
To search a graph, you can use either DFS or BFS.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def DFS(graph, start):
|
||||||
|
visited = set() # keep track of the visited nodes
|
||||||
|
stack = [start] # use a stack to keep track of nodes to visit next
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
node = stack.pop() # get the next node to visit
|
||||||
|
if node not in visited:
|
||||||
|
visited.add(node) # mark the node as visited
|
||||||
|
print(node, end=' ') # visit the node (print its value in this case)
|
||||||
|
stack.extend(graph[node] - visited) # add the node's neighbors to the stack, able to remove seen nodes in python for efficiency
|
||||||
|
|
||||||
|
def BFS(graph, start):
|
||||||
|
visited = set() # Keep track of the nodes that we've visited
|
||||||
|
queue = [start] # Use a queue to implement the BFS
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
node = queue.pop() # Dequeue a node from front of queue
|
||||||
|
if node not in visited:
|
||||||
|
visited.add(node) # Mark the node as visited
|
||||||
|
print(node, end=' ') # Visit the node (print its value in this case)
|
||||||
|
queue.extend(graph[node]) # Enqueue all neighbours
|
||||||
|
|
||||||
|
graph = {
|
||||||
|
'A': ['B', 'C'],
|
||||||
|
'B': ['A', 'D', 'E'],
|
||||||
|
'C': ['A', 'F'],
|
||||||
|
'D': ['B'],
|
||||||
|
'E': ['B', 'F'],
|
||||||
|
'F': ['C', 'E'],
|
||||||
|
}
|
||||||
|
|
||||||
|
DFS(graph, 'A') # Output: A C F E B D
|
||||||
|
BFS(graph, 'A') # Output: A B C D E F
|
||||||
|
```
|
||||||
|
|
||||||
|
## Finding Paths
|
||||||
|
|
||||||
|
The below algorithms find all possible paths from the start to goal. This is assuming all edges have equal weights. If you need to find the shortest path with weighted graphs, you can add the weight to the tuple stored in the stack and find the shortest after all paths have been found.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def dfs_paths(graph, start, goal):
|
||||||
|
stack = [(start, [start])]
|
||||||
|
while stack:
|
||||||
|
(vertex, path) = stack.pop()
|
||||||
|
# Again we are using python to subtract nodes already in the path
|
||||||
|
# You could also check if next is not in the current path instead
|
||||||
|
for next in graph[vertex] - set(path):
|
||||||
|
# if next in path: continue
|
||||||
|
if next == goal:
|
||||||
|
yield path + [next]
|
||||||
|
else:
|
||||||
|
stack.append((next, path + [next]))
|
||||||
|
|
||||||
|
# Will always have shortest path first (if all edges are equal)
|
||||||
|
def bfs_paths(graph, start, goal):
|
||||||
|
queue = [(start, [start])]
|
||||||
|
while queue:
|
||||||
|
(vertex, path) = queue.pop(0)
|
||||||
|
for next in graph[vertex] - set(path):
|
||||||
|
# if next in path: continue
|
||||||
|
if next == goal:
|
||||||
|
yield path + [next]
|
||||||
|
else:
|
||||||
|
queue.append((next, path + [next]))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Finding Shortest Path (Djikstra's Algorithm)
|
||||||
|
|
||||||
|
```python
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
class Graph():
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
self.edges is a dict of all possible next nodes
|
||||||
|
e.g. {'X': ['A', 'B', 'C', 'E'], ...}
|
||||||
|
self.weights has all the weights between two nodes,
|
||||||
|
with the two nodes as a tuple as the key
|
||||||
|
e.g. {('X', 'A'): 7, ('X', 'B'): 2, ...}
|
||||||
|
"""
|
||||||
|
self.edges = defaultdict(list)
|
||||||
|
self.weights = {}
|
||||||
|
|
||||||
|
def add_edge(self, from_node, to_node, weight):
|
||||||
|
# Note: assumes edges are bi-directional
|
||||||
|
self.edges[from_node].append(to_node)
|
||||||
|
self.edges[to_node].append(from_node)
|
||||||
|
self.weights[(from_node, to_node)] = weight
|
||||||
|
self.weights[(to_node, from_node)] = weight
|
||||||
|
|
||||||
|
def dijsktra(graph, initial, end):
|
||||||
|
# shortest paths is a dict of nodes
|
||||||
|
# whose value is a tuple of (previous node, weight)
|
||||||
|
shortest_paths = {initial: (None, 0)}
|
||||||
|
current_node = initial
|
||||||
|
visited = set()
|
||||||
|
|
||||||
|
while current_node != end:
|
||||||
|
visited.add(current_node)
|
||||||
|
destinations = graph.edges[current_node]
|
||||||
|
weight_to_current_node = shortest_paths[current_node][1]
|
||||||
|
|
||||||
|
for next_node in destinations:
|
||||||
|
weight = graph.weights[(current_node, next_node)] + weight_to_current_node
|
||||||
|
if next_node not in shortest_paths:
|
||||||
|
shortest_paths[next_node] = (current_node, weight)
|
||||||
|
else:
|
||||||
|
current_shortest_weight = shortest_paths[next_node][1]
|
||||||
|
if current_shortest_weight > weight:
|
||||||
|
shortest_paths[next_node] = (current_node, weight)
|
||||||
|
|
||||||
|
next_destinations = {node: shortest_paths[node] for node in shortest_paths if node not in visited}
|
||||||
|
if not next_destinations:
|
||||||
|
return "Route Not Possible"
|
||||||
|
# next node is the destination with the lowest weight
|
||||||
|
current_node = min(next_destinations, key=lambda k: next_destinations[k][1])
|
||||||
|
|
||||||
|
# Work back through destinations in shortest path
|
||||||
|
path = []
|
||||||
|
while current_node is not None:
|
||||||
|
path.append(current_node)
|
||||||
|
next_node = shortest_paths[current_node][0]
|
||||||
|
current_node = next_node
|
||||||
|
# Reverse path
|
||||||
|
path = path[::-1]
|
||||||
|
return path
|
||||||
|
```
|
||||||
|
|||||||
18
docs/interview/ds/heap.md
Normal file
18
docs/interview/ds/heap.md
Normal 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)
|
||||||
170
docs/interview/ds/patterns.md
Normal file
170
docs/interview/ds/patterns.md
Normal 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.
|
||||||
@@ -19,3 +19,39 @@ Each node in a binary tree has at most 2 children. Many operations on a binary
|
|||||||
### Binary Search Tree
|
### Binary Search Tree
|
||||||
|
|
||||||
A binary search tree or BST is a tree where the left child node is always less than the parent, and the right child node is always greater. This allows for very fast searching since it is easy to make a decision on which path to check down next.
|
A binary search tree or BST is a tree where the left child node is always less than the parent, and the right child node is always greater. This allows for very fast searching since it is easy to make a decision on which path to check down next.
|
||||||
|
|
||||||
|
## Searching
|
||||||
|
|
||||||
|
There are two main methods for searching trees: **Breadth First Search** and **Depth First Search**. In BFS, we traverse the tree level by level, and in DFS we traverse sub-tree by sub-tree, going down to the bottom and working our way up. BFS uses a queue (FIFO) and DFS uses a stack (LIFO). Within DFS we can traverse in order (Left-Root-Right), preorder (Root-Left-Right), or postorder (Left-Right-Root).
|
||||||
|
|
||||||
|
```python
|
||||||
|
def BFS(root):
|
||||||
|
if root is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
queue = [root]
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
node = queue.pop() # dequeue a node from the front of the queue
|
||||||
|
print(node.value, end=' ') # visit the node (print its value in this case)
|
||||||
|
|
||||||
|
# Can also be done with n children if not binary tree
|
||||||
|
# enqueue left child
|
||||||
|
if node.left:
|
||||||
|
queue.append(node.left)
|
||||||
|
# enqueue right child
|
||||||
|
if node.right:
|
||||||
|
queue.append(node.right)
|
||||||
|
|
||||||
|
def DFS(node):
|
||||||
|
if node is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# This example is pre-order, but just rearrange the order in which you check to make it in-order or post-order
|
||||||
|
# Visit the node (print its value in this case)
|
||||||
|
print(node.value, end=' ')
|
||||||
|
# Recursively call DFS on the left child
|
||||||
|
DFS(node.left)
|
||||||
|
# Recursively call DFS on the right child
|
||||||
|
DFS(node.right)
|
||||||
|
```
|
||||||
|
|||||||
181
docs/interview/languages/javascript.md
Normal file
181
docs/interview/languages/javascript.md
Normal 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);
|
||||||
|
```
|
||||||
|
|
||||||
209
docs/interview/languages/python.md
Normal file
209
docs/interview/languages/python.md
Normal 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))
|
||||||
|
```
|
||||||
@@ -17,7 +17,9 @@
|
|||||||
{"text": "Stacks and Queues", "link": "/interview/ds/stackqueue"},
|
{"text": "Stacks and Queues", "link": "/interview/ds/stackqueue"},
|
||||||
{"text": "Strings", "link": "/interview/ds/string"},
|
{"text": "Strings", "link": "/interview/ds/string"},
|
||||||
{"text": "Graphs", "link": "/interview/ds/graph"},
|
{"text": "Graphs", "link": "/interview/ds/graph"},
|
||||||
{"text": "Trees", "link": "/interview/ds/tree"}
|
{"text": "Trees", "link": "/interview/ds/tree"},
|
||||||
|
{"text": "Heap", "link": "/interview/ds/heap"},
|
||||||
|
{"text": "Patterns", "link": "/interview/ds/patterns"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -48,5 +50,12 @@
|
|||||||
{"text": "Facade", "link": "/interview/patterns/structure/facade"}
|
{"text": "Facade", "link": "/interview/patterns/structure/facade"}
|
||||||
]}
|
]}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Languages Specific",
|
||||||
|
"items": [
|
||||||
|
{"text": "Python", "link": "/interview/languages/python"},
|
||||||
|
{"text": "Javascript", "link": "/interview/languages/javascript"}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
73
docs/java/basics/classes.md
Normal file
73
docs/java/basics/classes.md
Normal 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);
|
||||||
|
}
|
||||||
|
```
|
||||||
51
docs/java/basics/collections.md
Normal file
51
docs/java/basics/collections.md
Normal 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);
|
||||||
|
}
|
||||||
|
```
|
||||||
28
docs/java/basics/controlflow.md
Normal file
28
docs/java/basics/controlflow.md
Normal 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));
|
||||||
|
```
|
||||||
36
docs/java/basics/interfaces.md
Normal file
36
docs/java/basics/interfaces.md
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
94
docs/java/basics/lambda.md
Normal file
94
docs/java/basics/lambda.md
Normal 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);
|
||||||
|
}
|
||||||
|
```
|
||||||
1
docs/java/basics/types.md
Normal file
1
docs/java/basics/types.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Types
|
||||||
@@ -2,8 +2,13 @@
|
|||||||
{
|
{
|
||||||
"text": "Basics",
|
"text": "Basics",
|
||||||
"items": [
|
"items": [
|
||||||
{ "text": "Introduction", "link": "/java/" }
|
{ "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" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
7
docs/vue/index.md
Normal file
7
docs/vue/index.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Vue
|
||||||
|
|
||||||
|
## Creating a New Project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm create vue@latest
|
||||||
|
```
|
||||||
8
docs/vue/sidebar.json
Normal file
8
docs/vue/sidebar.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"text": "Vue Basics",
|
||||||
|
"items": [
|
||||||
|
{ "text": "Introduction", "link": "/vue/" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user