import type { Option } from "./option.ts"; export function first(arr: readonly T[]): Option { return arr[0] ?? null; } export function last(arr: readonly T[]): Option { return arr[arr.length - 1] ?? null; } export function at(arr: readonly T[], index: number): Option { if (index < 0) index = arr.length + index; return arr[index] ?? null; } export function find( arr: readonly T[], predicate: (t: T) => boolean, ): Option { return arr.find(predicate) ?? null; } export function findMap( arr: readonly T[], fn: (t: T) => Option, ): Option { for (const item of arr) { const result = fn(item); if (result != null) return result; } return null; } export function findIndex( arr: readonly T[], predicate: (t: T) => boolean, ): Option { const index = arr.findIndex(predicate); return index >= 0 ? index : null; } export function partition( arr: readonly T[], predicate: (t: T) => boolean, ): [T[], T[]] { const pass: T[] = []; const fail: T[] = []; for (const item of arr) { if (predicate(item)) { pass.push(item); } else { fail.push(item); } } return [pass, fail]; } export function groupBy( arr: readonly T[], keyFn: (t: T) => K, ): Record { const result = {} as Record; for (const item of arr) { const key = keyFn(item); if (!result[key]) { result[key] = []; } result[key].push(item); } return result; } export function unique(arr: readonly T[]): T[] { return [...new Set(arr)]; } export function uniqueBy(arr: readonly T[], keyFn: (t: T) => K): T[] { const seen = new Set(); const result: T[] = []; for (const item of arr) { const key = keyFn(item); if (!seen.has(key)) { seen.add(key); result.push(item); } } return result; } export function sortBy( arr: readonly T[], keyFn: (t: T) => number | string, ): T[] { return [...arr].sort((a, b) => { const ka = keyFn(a); const kb = keyFn(b); if (ka < kb) return -1; if (ka > kb) return 1; return 0; }); } export function sortByDesc( arr: readonly T[], keyFn: (t: T) => number | string, ): T[] { return [...arr].sort((a, b) => { const ka = keyFn(a); const kb = keyFn(b); if (ka > kb) return -1; if (ka < kb) return 1; return 0; }); } export function chunk(arr: readonly T[], size: number): T[][] { const result: T[][] = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; } export function zip(a: readonly T[], b: readonly U[]): [T, U][] { const length = Math.min(a.length, b.length); const result: [T, U][] = []; for (let i = 0; i < length; i++) { result.push([a[i], b[i]]); } return result; } export function zipWith( a: readonly T[], b: readonly U[], fn: (t: T, u: U) => R, ): R[] { const length = Math.min(a.length, b.length); const result: R[] = []; for (let i = 0; i < length; i++) { result.push(fn(a[i], b[i])); } return result; } export function intersperse(arr: readonly T[], separator: T): T[] { if (arr.length <= 1) return [...arr]; const result: T[] = [arr[0]]; for (let i = 1; i < arr.length; i++) { result.push(separator, arr[i]); } return result; } export function range(start: number, end: number): number[] { const result: number[] = []; for (let i = start; i < end; i++) { result.push(i); } return result; } export function isEmpty(arr: readonly T[]): boolean { return arr.length === 0; } export function isNonEmpty(arr: readonly T[]): arr is [T, ...T[]] { return arr.length > 0; } export function sum(arr: readonly number[]): number { return arr.reduce((acc, n) => acc + n, 0); } export function sumBy(arr: readonly T[], fn: (t: T) => number): number { return arr.reduce((acc, t) => acc + fn(t), 0); } export function maxBy(arr: readonly T[], fn: (t: T) => number): Option { if (arr.length === 0) return null; let max = arr[0]; let maxValue = fn(max); for (let i = 1; i < arr.length; i++) { const value = fn(arr[i]); if (value > maxValue) { max = arr[i]; maxValue = value; } } return max; } export function minBy(arr: readonly T[], fn: (t: T) => number): Option { if (arr.length === 0) return null; let min = arr[0]; let minValue = fn(min); for (let i = 1; i < arr.length; i++) { const value = fn(arr[i]); if (value < minValue) { min = arr[i]; minValue = value; } } return min; }