TypeScript utility library providing common mathematical and array manipulation functions.
pnpm add coastalAdjusts two angles to minimize interpolation distance.
import { adjustAngle } from 'coastal'
adjustAngle(350, 10) // [350, 10] - no adjustment needed
adjustAngle(10, 350) // [370, 350] - adjusts first angleLinear interpolation between two values.
import { lerp } from 'coastal'
lerp(0, 100, 0.5) // 50
lerp(0, 100, 0.25) // 25
lerp(-10, 10, 0.5) // 0Linear interpolation between two angles, taking the shortest path.
import { lerpAngle } from 'coastal'
lerpAngle(350, 10, 0.5) // 0 - takes shortest path across 0°
lerpAngle(90, 270, 0.5) // 180Linear interpolation between two arrays element-wise.
import { lerpArray } from 'coastal'
lerpArray([0, 0], [2, 4], 0.5) // [1, 2]
lerpArray([1, 2, 3], [4, 5], 0.5) // [2.5, 3.5] - uses minimum length
lerpArray([], [1, 2], 0.5) // [] - empty array when either input is emptyEdge cases:
- Empty arrays: Returns empty array when either input array is empty
- Different lengths: Uses minimum length of the two input arrays
Normalizes an array of numbers so they sum to 1.
import { normalize } from 'coastal'
normalize([2, 2]) // [0.5, 0.5]
normalize([1, 2, 3]) // [0.16667, 0.33333, 0.5]
normalize([]) // [] - empty array
normalize([1, -1]) // [Infinity, -Infinity] - zero sum caseEdge cases:
- Empty array: Returns empty array
- Zero sum: Returns array with Infinity/-Infinity values (division by zero)
- All zeros: Returns array with NaN values
Normalizes an angle to the range [0, 360).
import { normalizeAngle } from 'coastal'
normalizeAngle(450) // 90
normalizeAngle(-90) // 270
normalizeAngle(360) // 0Calculates the sum of all numbers in an array.
import { sum } from 'coastal'
sum([1, 2, 3, 4]) // 10
sum([]) // 0Szudzik pairing function - maps two non-negative integers to a unique non-negative integer.
import { szudzik } from 'coastal'
szudzik(1, 1) // 3
szudzik(5, 3) // 33
szudzik(-1, 0) // -1 - deterministic but not bijective for negative inputs
szudzik(1.5, 0) // 3.75 - deterministic but not bijective for non-integersEdge cases:
- Negative inputs: Produces deterministic results but pairing is not guaranteed to be unique or reversible
- Non-integer inputs: Produces deterministic results but pairing is not guaranteed to be unique or reversible
- Intended use: Non-negative integers for mathematically correct bijective pairing
Constrains a number to fall within specified bounds.
import { clamp } from 'coastal'
clamp(15, 0, 10) // 10
clamp(-5, 0, 10) // 0
clamp(5, 0, 10) // 5Removes elements from an array in-place where the predicate returns true.
import { remove } from 'coastal'
const numbers = [1, 2, 3, 4, 5]
remove(numbers, (x) => x % 2 === 0) // removes even numbers
console.log(numbers) // [1, 3, 5]
// Predicate receives value, index, and array
const items = ['a', 'b', 'c', 'd']
remove(items, (value, index) => index % 2 === 0) // removes elements at even indices
console.log(items) // ['b', 'd']Updates an existing array element or inserts a new one based on a predicate match.
import { upsert } from 'coastal'
// Insert when no match found
const users = [{ id: 1, name: 'Alice' }]
upsert(users, { id: 2, name: 'Bob' }, (user) => user.id === 2)
console.log(users) // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
// Update when match found
const products = [{ id: 1, price: 100 }]
upsert(products, { id: 1, price: 150 }, (product) => product.id === 1)
console.log(products) // [{ id: 1, price: 150 }]
// Works with primitives
const numbers = [1, 2, 3]
upsert(numbers, 99, (n) => n > 5) // no match, appends
console.log(numbers) // [1, 2, 3, 99]
const tags = ['urgent', 'bug']
upsert(tags, 'fixed', (tag) => tag === 'bug') // match found, replaces
console.log(tags) // ['urgent', 'fixed']Behavior:
- Inserts at end of array when predicate finds no match
- Replaces first matching element when predicate finds a match
- Mutates the original array
Maps over an array using dynamically-sized chunks, where each chunk size is determined by examining the current element and its position. Similar to Array.map() but operates on variable-length chunks instead of individual elements.
import { mapChunkBy } from 'coastal'
// Fixed chunk size
const numbers = [1, 2, 3, 4, 5, 6]
mapChunkBy(
numbers,
() => 2,
(chunk, index) => ({ index, sum: chunk.reduce((a, b) => a + b, 0) }),
)
// [{ index: 0, sum: 3 }, { index: 2, sum: 7 }, { index: 4, sum: 11 }]
// Dynamic sizing based on content
const words = ['hi', 'hello', 'a', 'world', 'test']
mapChunkBy(
words,
(word) => (word.length > 3 ? 2 : 1),
(chunk, index) => ({ startIndex: index, text: chunk.join(' ') }),
)
// [{ startIndex: 0, text: 'hi' }, { startIndex: 1, text: 'hello a' }, { startIndex: 3, text: 'world test' }]
// Text processing with sentence detection
const words = ['Hello', 'world!', 'How', 'are', 'you?', 'Fine.']
mapChunkBy(
words,
(_, index) => {
// Take words until punctuation or max 3 words
let count = 1
for (let i = index; i < Math.min(index + 3, words.length); i++) {
if (words[i].includes('!') || words[i].includes('?') || words[i].includes('.')) {
return i - index + 1
}
if (i > index) count++
}
return count
},
(chunk) => chunk.join(' '),
)
// ['Hello world!', 'How are you?', 'Fine.']Edge cases:
- Empty array: Returns empty array
- Chunk size exceeds remaining elements: Uses all remaining elements
- Invalid chunk size: Throws Error for non-positive integers, decimals, NaN, or Infinity
- Source array is not modified
A direct address table providing O(1) lookup time for non-negative integer keys. The table is immutable after construction.
import { DirectAddressTable } from 'coastal'
// Basic usage
const table = new DirectAddressTable([0, 2, 5], ['a', 'b', 'c'])
table.get(0) // 'a'
table.get(1) // undefined
table.get(2) // 'b'
table.get(5) // 'c'
// Duplicate keys - last value wins
const table2 = new DirectAddressTable([1, 2, 1], ['first', 'second', 'third'])
table2.get(1) // 'third'
// Invalid keys throw errors during construction
try {
new DirectAddressTable([-1, 0], ['a', 'b']) // throws Error
} catch (e) {
console.log(e.message) // "Invalid key: -1. Keys must be non-negative integers"
}
try {
new DirectAddressTable([1.5, 2], ['a', 'b']) // throws Error
} catch (e) {
console.log(e.message) // "Invalid key: 1.5. Keys must be non-negative integers"
}Performance characteristics:
- Time complexity: O(1) lookup
- Space complexity: O(max_key) where max_key is the largest key value
- Memory usage scales with the largest key, not the number of stored values
Edge cases:
- Empty key arrays: Throws RangeError
- Mismatched array lengths: Keys without corresponding values are assigned undefined
- Invalid keys: Throws Error for negative numbers or non-integers
- Large key values: Uses memory proportional to the largest key value
- Duplicate keys: Last value overwrites previous values
function adjustAngle ↗
Adjusts two angles to minimize interpolation distance (shortest arc)
Mathematically proven to produce minimal interpolation distance with |adjusted_b - adjusted_a| ≤ 180°. Properly manages wrap-around at 0°/360° boundary and handles 180° ambiguity with positive direction tie-breaking rule for deterministic behavior. Essential for consistent interpolation in graphics, robotics, and navigation systems.
adjustAngle: (a: number, b: number) => [number, number]| Parameter | Type | Description |
|---|---|---|
a |
number |
First angle in degrees |
b |
number |
Second angle in degrees |
Tuple of adjusted angles that minimize interpolation distance
function clamp ↗
Constrains a number to fall within specified bounds.
Uses nested Math functions for optimal performance compared to conditional branches.
clamp: (value: number, min: number, max: number) => number| Parameter | Type | Description |
|---|---|---|
value |
number |
The number to constrain |
min |
number |
The lower bound (inclusive, must be ≤ max) |
max |
number |
The upper bound (inclusive, must be ≥ min) |
The constrained number, or the appropriate bound if outside range
Error when min > max
function lerp ↗
Linear interpolation between two values
lerp: (v0: number, v1: number, t: number) => number| Parameter | Type | Description |
|---|---|---|
v0 |
number |
Start value |
v1 |
number |
End value |
t |
number |
Interpolation factor (0 = v0, 1 = v1) |
Interpolated value
function lerpAngle ↗
Linear interpolation between two angles using shortest path
Uses shortest path interpolation via adjustAngle to ensure optimal angular transitions. Satisfies symmetry property: lerpAngle(a,b,t) = lerpAngle(b,a,1-t). Consistent 180° tie-breaking ensures predictable results and handles all edge cases including floating-point precision boundaries. Suitable for production use in graphics, robotics, and navigation systems.
lerpAngle: (a: number, b: number, t: number) => number| Parameter | Type | Description |
|---|---|---|
a |
number |
Start angle in degrees |
b |
number |
End angle in degrees |
t |
number |
Interpolation factor (0 = a, 1 = b) |
Interpolated angle normalized to [0, 360) range
function lerpArray ↗
Linear interpolation between two arrays element-wise
lerpArray: (a: number[], b: number[], t: number) => number[]| Parameter | Type | Description |
|---|---|---|
a |
number[] |
First array |
b |
number[] |
Second array |
t |
number |
Interpolation factor (0 = first array, 1 = second array) |
New array with interpolated values. Length equals minimum of input array lengths. Returns empty array when either input array is empty.
function mapChunkBy ↗
Maps over an array using dynamically-sized chunks, where each chunk size is determined by examining the current element and its position. Similar to Array.map() but operates on variable-length chunks of the array instead of individual elements.
export declare function mapChunkBy<T, R>(
array: T[],
getChunkSize: (currentValue: T, index: number) => number,
transform: (chunk: T[], startIndex: number) => R,
): R[]| Parameter | Description |
|---|---|
T |
The type of elements in the input array |
R |
The type of elements in the returned array |
| Parameter | Type | Description |
|---|---|---|
array |
T[] |
The input array to process |
getChunkSize |
(currentValue: T, index: number) => number |
Function that determines the size of the next chunk. Receives the current element and its index, returns the number of elements to include in this chunk. Must return a positive integer. |
transform |
(chunk: T[], startIndex: number) => R |
Function that transforms each chunk and returns a result value. Receives the chunk array and the starting index of the chunk. |
Array of transformed results, where each element is the result of calling the transform function on a dynamically-sized chunk.
Error when getChunkSize returns a value that is not a positive integer
function normalize ↗
Normalizes an array of numbers so they sum to 1
normalize: (values: number[]) => number[]| Parameter | Type | Description |
|---|---|---|
values |
number[] |
Array of numbers to normalize |
Normalized array where all values sum to 1. Returns empty array for empty input. For arrays with zero sum, returns array containing Infinity or NaN values.
function normalizeAngle ↗
Normalizes an angle to the range [0, 360)
normalizeAngle: (angle: number) => number| Parameter | Type | Description |
|---|---|---|
angle |
number |
Angle in degrees |
Normalized angle between 0 and 360 degrees
function remove ↗
Mutates an array by removing all elements where the predicate returns true. Elements are evaluated from right to left to maintain correct indices during removal.
remove: <T>(array: T[], predicate: (value: T, index: number, array: T[]) => boolean) => void| Parameter | Description |
|---|---|
T |
The type of array elements |
| Parameter | Type | Description |
|---|---|---|
array |
T[] |
The array to modify in-place |
predicate |
(value: T, index: number, array: T[]) => boolean |
Function invoked for each element. Return true to remove the element. |
function sum ↗
Calculates the sum of all numbers in an array
sum: (values: number[]) => number| Parameter | Type | Description |
|---|---|---|
values |
number[] |
Array of numbers to sum |
Sum of all values in the array
function szudzik ↗
Szudzik pairing function - maps two non-negative integers to a unique non-negative integer
szudzik: (x: number, y: number) => number| Parameter | Type | Description |
|---|---|---|
x |
number |
First integer (intended for non-negative integers) |
y |
number |
Second integer (intended for non-negative integers) |
Unique integer representing the pair. For negative or non-integer inputs, produces deterministic results but not guaranteed to be unique or reversible.
function upsert ↗
Updates an existing array element or inserts a new one based on a predicate match. Mutates the original array.
upsert: <T>(array: T[], value: T, predicate: (item: T) => boolean) => void| Parameter | Description |
|---|---|
T |
The type of elements in the array |
| Parameter | Type | Description |
|---|---|---|
array |
T[] |
The array to modify |
value |
T |
The value to insert or use as replacement |
predicate |
(item: T) => boolean |
Function that returns true for the element to replace |
class DirectAddressTable ↗
A direct address table data structure providing O(1) lookup time for non-negative integer keys. Uses an array for storage where keys serve as indices, enabling constant-time access. The table is immutable after construction and creates sparse arrays when keys have gaps.
export declare class DirectAddressTable<T>| Parameter | Description |
|---|---|
T |
The type of values stored in the table |
Creates a new direct address table from parallel arrays of keys and values. Space complexity is O(max_key) where max_key is the largest key value.
constructor(keys: number[], values: T[]);| Parameter | Type | Description |
|---|---|---|
keys |
number[] |
Array of non-negative integer keys |
values |
T[] |
Array of values corresponding to each key. Keys without corresponding values are assigned undefined. |
- RangeError When keys array is empty
- Error When keys contain negative numbers or non-integer values
- Duplicate keys: Last value overwrites previous values for the same key - Memory usage scales with the largest key value, not the number of stored values - Invalid keys are rejected during construction
Retrieves the value associated with the specified key in O(1) time.
get(key: number): T | undefined;| Parameter | Type | Description |
|---|---|---|
key |
number |
The numeric key to look up |
The value associated with the key, or undefined if the key doesn't exist or was never assigned a value
- Returns undefined for keys outside the initialized range - No runtime validation is performed on the key parameter