Published
-
Simple Commands
The working set — things I look up often enough to stop looking up. Grouped so I can find them again.
Regression evaluation metrics
Three metrics for regression problems. All of them are loss functions, so the goal is to minimise them.
MAE = 1/n · Σ |yᵢ - ŷᵢ|
MSE = 1/n · Σ (yᵢ - ŷᵢ)²
RMSE = √( 1/n · Σ (yᵢ - ŷᵢ)² )
- Mean Absolute Error — the easiest to understand, because it is the average error.
- Mean Squared Error — more popular than MAE, because it punishes larger errors, which tends to be useful in the real world.
- Root Mean Squared Error — more popular still, because it is interpretable in the units of
y.
JavaScript — arrays
Every line below assumes nums is back to its starting value.
const nums = [3, 4, 5, 6]
nums.at(1) // 4 — index from the start, or from the end with a negative index
nums.at(-1) // 6 — the last item, without the length-1 dance
nums.pop() // 6 — removes and returns the last item; nums becomes [3, 4, 5]
nums.push(7) // 5 — appends and returns the new length; nums becomes [3, 4, 5, 6, 7]
nums.shift() // 3 — removes and returns the first item; nums becomes [4, 5, 6]
nums.unshift(1) // 5 — prepends and returns the new length; nums becomes [1, 3, 4, 5, 6]
nums.fill(1) // [1, 1, 1, 1] — overwrites every slot in place
nums.join('-') // '3-4-5-6' — stringify with a separator
nums.reverse() // [6, 5, 4, 3] — reverses in place
nums.toReversed() // [6, 5, 4, 3] — same, but nums keeps its order
nums.slice(0, 2) // [3, 4] — copy from start up to (not including) end
nums.includes(5) // true — is the value in there
nums.indexOf(5) // 2 — where it is, or -1 if it isn't
nums.map((num) => num + 6) // [9, 10, 11, 12] — one output per input
nums.find((num) => num > 4) // 5 — the first match itself
nums.findIndex((num) => num > 4) // 2 — the first match's position
nums.filter((num) => num > 4) // [5, 6] — every match
nums.every((num) => num > 5) // false — do all of them pass
nums.some((num) => num % 2 === 0) // true — does at least one pass
nums.reduce((acc, num) => acc + num, 0) // 18 — fold the array into one value
Array.from([1, 2, 3], (x) => x + x) // [2, 4, 6] — build an array and map it in one pass
;[1, 2, [3], [4, 5], 6, []].flatMap((num) => num) // [1, 2, 3, 4, 5, 6] — map, then flatten one level
Sum a property across an array of objects
Two ways; reduce is the one to reach for.
let sum = 0
incomes.forEach((income) => {
sum += income.total
})
const sum2 = incomes.reduce((accumulator, income) => {
return accumulator + income.total
}, 0)
Count occurrences into an object
Reduce into an object when you want a tally keyed by value.
const pets = ['dog', 'chicken', 'cat', 'dog', 'chicken', 'chicken', 'rabbit']
const petCounts = pets.reduce(function (obj, pet) {
if (!obj[pet]) {
obj[pet] = 1
} else {
obj[pet]++
}
return obj
}, {})
/*
Output:
{
dog: 2,
chicken: 3,
cat: 1,
rabbit: 1
}
*/
Reshape each object in an array
map with a fresh container per item, when the output shape differs from the input.
const myUsers = [
{ name: 'shark', likes: 'ocean' },
{ name: 'turtle', likes: 'pond' },
{ name: 'otter', likes: 'fish biscuits' }
]
const usersByLikes = myUsers.map((item) => {
const container = {}
container[item.name] = item.likes
container.age = item.name.length * 10
return container
})
// Output
// [
// {shark: "ocean", age: 50},
// {turtle: "pond", age: 60},
// {otter: "fish biscuits", age: 50}
// ]
Group an array of objects
Object.groupBy buckets by whatever the callback returns — a property, or a computed label.
const inventory = [
{ name: 'asparagus', type: 'vegetables', quantity: 5 },
{ name: 'bananas', type: 'fruit', quantity: 0 },
{ name: 'goat', type: 'meat', quantity: 23 },
{ name: 'cherries', type: 'fruit', quantity: 5 },
{ name: 'fish', type: 'meat', quantity: 22 }
]
const result = Object.groupBy(inventory, ({ type }) => type)
/* Result is:
{
vegetables: [
{ name: 'asparagus', type: 'vegetables', quantity: 5 },
],
fruit: [
{ name: "bananas", type: "fruit", quantity: 0 },
{ name: "cherries", type: "fruit", quantity: 5 }
],
meat: [
{ name: "goat", type: "meat", quantity: 23 },
{ name: "fish", type: "meat", quantity: 22 }
]
}
*/
// Groups can be inferred from values rather than read off a property
function myCallback({ quantity }) {
return quantity > 5 ? 'ok' : 'restock'
}
const result2 = Object.groupBy(inventory, myCallback)
/* Result is:
{
restock: [
{ name: "asparagus", type: "vegetables", quantity: 5 },
{ name: "bananas", type: "fruit", quantity: 0 },
{ name: "cherries", type: "fruit", quantity: 5 }
],
ok: [
{ name: "goat", type: "meat", quantity: 23 },
{ name: "fish", type: "meat", quantity: 22 }
]
}
*/
Last item in an array
const lastUser = users[users.length - 1]
// or
const lastUser2 = users.at(-1)
Call an array method on something that isn’t an array
Borrow the method with call when the object is only array-like.
const arrayLike = {
length: 3,
0: 2,
1: 7.3,
2: 4
}
console.log(Array.prototype.find.call(arrayLike, (x) => !Number.isInteger(x))) // 7.3
Sum N arrays of different lengths
Pads the short ones with zeros rather than dropping them.
function sumArrays(...arrays) {
const n = arrays.reduce((max, xs) => Math.max(max, xs.length), 0)
const result = Array.from({ length: n })
return result.map((_, i) => arrays.map((xs) => xs[i] || 0).reduce((sum, x) => sum + x, 0))
}
// sumArrays([0, 1, 2], [1, 2, 3, 4], [1, 2])
JavaScript — Set and Map
Set: unique values, and fast membership checks
const numbers = [1, 2, 3, 4, 5, 5]
const uniqueNumbers = new Set(numbers) // {1, 2, 3, 4, 5} — dedupes on construction
const mySet1 = new Set()
mySet1.add(1) // Set(1) { 1 }
mySet1.add(5) // Set(2) { 1, 5 }
mySet1.add(5) // Set(2) { 1, 5 } — adding a duplicate is a no-op
mySet1.add('some text') // Set(3) { 1, 5, 'some text' }
const o = { a: 1, b: 2 }
mySet1.add(o)
mySet1.add({ a: 1, b: 2 }) // a different object reference, so this is a second entry
mySet1.has(1) // true
mySet1.has(3) // false, since 3 has not been added to the set
mySet1.has(Math.sqrt(25)) // true — membership is by value for primitives
mySet1.has('Some Text'.toLowerCase()) // true
mySet1.has(o) // true — and by reference for objects
uniqueNumbers.delete(1) // true if it was there
Map: key-value pairs that remember insertion order
Like an object, but any value can be a key and the size is a property away.
const map1 = new Map()
map1.set('a', 1) // set by key
map1.set('b', 2)
map1.set('c', 3)
map1.get('a') // 1 — read by key
map1.size // 3 — the length
map1.delete('b') // true — it was there
map1.delete('Raymond') // false — it wasn't
map1.set('Jessie', { phone: '213-555-1234', address: '123 N 1st Ave' })
map1.has('Jessie') // true — check before you read
map1.get('Hilary') // undefined
Iterate a Map
entries() returns an iterator of [key, value] pairs in insertion order, so it drops straight into a for...of.
const map2 = new Map()
map2.set('0', 'foo')
map2.set(1, 'bar')
const iterator1 = map2.entries()
console.log(iterator1.next().value) // ["0", "foo"] — each next() call moves on by one
console.log(iterator1.next().value) // [1, "bar"]
// Frequency map: the first key that only appeared once
for (const [key, value] of frequencies.entries()) {
if (value == 1) return key
}
JavaScript — loops and short-hands
Long-hand and short-hand side by side, so the short one is obviously the same thing.
// Iterate with the index
array.forEach(function (arrayItem, index) {
console.log(arrayItem, index)
})
// Check a value against a list instead of chaining ||
const isVowel = (letter) => ['a', 'e', 'i', 'o', 'u'].includes(letter)
// For of (arrays)
const arr = [1, 2, 3, 4, 5]
// Long-hand
for (let i = 0; i < arr.length; i++) {
const element = arr[i]
// ...
}
// Short-hand
for (const element of arr) {
// ...
}
// For in (objects)
const obj = {
a: 1,
b: 2,
c: 3
}
// Long-hand
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const value = obj[key]
// ...
}
// Short-hand
for (const key in obj) {
const value = obj[key]
// ...
}
// Picking between two calls with the same signature
function f1() {
// ...
}
function f2() {
// ...
}
// Long-hand
if (condition) {
f1()
} else {
f2()
}
// Short-hand
;(condition ? f1 : f2)()
JavaScript — async
Two independent awaits run one after the other. Promise.all runs them at the same time.
// Sequential — the second request doesn't start until the first finishes
const user = await getUser()
const products = await getProducts()
// Concurrent — both in flight, resolves when the slower one lands
const [user, products] = await Promise.all([getUser(), getProducts()])
Reading a URL in the browser
location breaks the current URL into its parts.
location.protocol // 'https:'
location.hostname // 'example.org'
location.port // '8080', or '' on the default port
location.pathname // '/posts/commands'
location.search // '?q=term' — the query string
location.hash // '#section' — the fragment identifier
location.href // the complete URL
TypeScript — Pick and Omit
Derive a type from another instead of writing it twice. Pick names what to keep, Omit names what to drop.
type User = {
id: string
name: string
age: number
}
type NewUser = Pick<User, 'name' | 'age'>
type NewUser = Omit<User, 'id'>
const newUser: NewUser = {
name: 'Shanice',
age: 28
}
TypeScript — debounce
Waits for the calls to stop before firing once. Typed so the wrapper keeps the original signature.
function debounce<T extends (...args: ChangeEvent<HTMLInputElement>[]) => void>(
func: T,
timeout = 300
): (...args: Parameters<T>) => void {
let timer: NodeJS.Timeout
return (...args: Parameters<T>) => {
clearTimeout(timer)
timer = setTimeout(() => {
func(...args)
}, timeout)
}
}
React — replace conditional rendering with a lookup
A map from role to component, instead of a chain of ternaries. ?? covers the unknown role.
const RoleViews = {
GUEST: GuestView,
ADMIN: AdminView,
CONTRIBUTOR: ContributorVIew
};
const UserViews = ({role}) => {
const CurrentViews = RoleViews[role] ?? DefaultView;
return (
<div>
<CurrentView/>
</div>
)
}
React — scroll to top on route change
Render it inside the router, above <Routes>, and every navigation starts at the top.
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
export default function ScrollToTop() {
const { pathname } = useLocation()
useEffect(() => {
window.scrollTo(0, 0)
}, [pathname])
return null
}
// <ScrollToTop />
// <Routes>
Redux — DevTools with TypeScript
The compose extension isn’t on the Window type, so either cast it or declare it.
// Cast at the point of use
const composeEnhancers =
(window['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] as typeof compose) || compose
// Or declare it once and use it everywhere
declare global {
interface Window {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose
}
}
export default function configureStore(initialState: any) {
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
return createStore(
rootReducer,
initialState,
composeEnhancers(applyMiddleware(authMiddleware, logger, thunk))
)
}
CSS — three ways to centre a div
<article>
<div class="classic">Worrap</div>
</article>
/* Classic — absolute position, then pull back by half its own size */
.classic {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* Flex — centre on both axes */
.classic {
display: flex;
align-items: center;
justify-content: center;
}
/* Grid — the short one */
.classic {
display: grid;
place-items: center;
}
Git — refusing to merge unrelated histories
Two repos with no common commit. Force the merge through once, when you know the histories really are separate.
git merge --allow-unrelated-histories