std

Browse the standard library for Andromeda

/**
 * @fileoverview Data manipulation and array utilities
 * @module data
 */

/**
 * Swaps two elements in an array.
 *
 * @example
 * ```ts
 * const arr = [1, 2, 3, 4];
 * swap(arr, 0, 3);
 * console.log(arr); // [4, 2, 3, 1]
 * ```
 */
export function swap<T>(array: T[], a: number, b: number): void {
  const temp = array[a];
  array[a] = array[b]!;
  array[b] = temp!;
}

/**
 * Splits an array into chunks of specified size.
 *
 * @example
 * ```ts
 * chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
 * ```
 */
export function chunk<T>(array: T[], size: number): T[][] {
  if (size <= 0) throw new Error("Chunk size must be positive");
  const result: T[][] = [];
  for (let i = 0; i < array.length; i += size) {
    result.push(array.slice(i, i + size));
  }
  return result;
}

/**
 * Flattens a nested array to a specified depth.
 *
 * @example
 * ```ts
 * flatten([[1, 2], [3, [4, 5]]], 1); // [1, 2, 3, [4, 5]]
 * flatten([[1, 2], [3, [4, 5]]], 2); // [1, 2, 3, 4, 5]
 * ```
 */
export function flatten<T>(array: unknown[], depth = 1): T[] {
  const result: T[] = [];

  function flattenHelper(arr: unknown[], currentDepth: number): void {
    for (const item of arr) {
      if (Array.isArray(item) && currentDepth > 0) {
        flattenHelper(item, currentDepth - 1);
      } else {
        result.push(item as T);
      }
    }
  }

  flattenHelper(array, depth);
  return result;
}

/**
 * Returns unique elements from an array.
 *
 * @example
 * ```ts
 * unique([1, 2, 2, 3, 3, 3]); // [1, 2, 3]
 * unique(['a', 'b', 'a', 'c']); // ['a', 'b', 'c']
 * ```
 */
export function unique<T>(array: T[]): T[] {
  return [...new Set(array)];
}

/**
 * Groups array elements by a key function.
 *
 * @example
 * ```ts
 * const people = [
 *   { name: 'Alice', age: 25 },
 *   { name: 'Bob', age: 30 },
 *   { name: 'Carol', age: 25 }
 * ];
 * groupBy(people, p => p.age);
 * // Map(2) { 25 => [Alice, Carol], 30 => [Bob] }
 * ```
 */
export function groupBy<T, K>(array: T[], keyFn: (item: T) => K): Map<K, T[]> {
  const groups = new Map<K, T[]>();
  for (const item of array) {
    const key = keyFn(item);
    const group = groups.get(key) || [];
    group.push(item);
    groups.set(key, group);
  }
  return groups;
}

/**
 * Sorts array by a key function.
 *
 * @example
 * ```ts
 * const people = [
 *   { name: 'Carol', age: 25 },
 *   { name: 'Alice', age: 30 },
 *   { name: 'Bob', age: 20 }
 * ];
 * sortBy(people, p => p.age); // [Bob(20), Carol(25), Alice(30)]
 * sortBy(people, p => p.name); // [Alice, Bob, Carol]
 * ```
 */
export function sortBy<T, K>(array: T[], keyFn: (item: T) => K): T[] {
  return [...array].sort((a, b) => {
    const keyA = keyFn(a);
    const keyB = keyFn(b);
    if (keyA < keyB) return -1;
    if (keyA > keyB) return 1;
    return 0;
  });
}

/**
 * Removes element at specified index and returns it.
 *
 * @example
 * ```ts
 * const arr = [1, 2, 3, 4];
 * const removed = removeAt(arr, 1); // removed = 2, arr = [1, 3, 4]
 * ```
 */
export function removeAt<T>(array: T[], index: number): T | undefined {
  if (index < 0 || index >= array.length) return undefined;
  return array.splice(index, 1)[0];
}

/**
 * Inserts element at specified index.
 *
 * @example
 * ```ts
 * const arr = [1, 3, 4];
 * insertAt(arr, 1, 2); // arr = [1, 2, 3, 4]
 * ```
 */
export function insertAt<T>(array: T[], index: number, item: T): void {
  array.splice(index, 0, item);
}

/**
 * Rotates array elements to the left by n positions.
 *
 * @example
 * ```ts
 * rotateLeft([1, 2, 3, 4, 5], 2); // [3, 4, 5, 1, 2]
 * ```
 */
export function rotateLeft<T>(array: T[], n: number): T[] {
  const len = array.length;
  if (len === 0) return array;
  n = n % len;
  if (n === 0) return [...array];
  return [...array.slice(n), ...array.slice(0, n)];
}

/**
 * Rotates array elements to the right by n positions.
 *
 * @example
 * ```ts
 * rotateRight([1, 2, 3, 4, 5], 2); // [4, 5, 1, 2, 3]
 * ```
 */
export function rotateRight<T>(array: T[], n: number): T[] {
  const len = array.length;
  if (len === 0) return array;
  n = n % len;
  if (n === 0) return [...array];
  return [...array.slice(-n), ...array.slice(0, -n)];
}

/**
 * Performs binary search on a sorted array.
 *
 * @example
 * ```ts
 * binarySearch([1, 3, 5, 7, 9], 5); // 2
 * binarySearch([1, 3, 5, 7, 9], 6); // -1
 * ```
 */
export function binarySearch<T>(
  array: T[],
  target: T,
  compareFn?: (a: T, b: T) => number,
): number {
  const compare = compareFn || ((a, b) => a < b ? -1 : a > b ? 1 : 0);
  let left = 0;
  let right = array.length - 1;

  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    const comparison = compare(array[mid]!, target);

    if (comparison === 0) return mid;
    if (comparison < 0) left = mid + 1;
    else right = mid - 1;
  }

  return -1;
}

/**
 * Partitions array into two arrays based on predicate.
 *
 * @example
 * ```ts
 * const [evens, odds] = partition([1, 2, 3, 4, 5], x => x % 2 === 0);
 * // evens = [2, 4], odds = [1, 3, 5]
 * ```
 */
export function partition<T>(
  array: T[],
  predicate: (item: T) => boolean,
): [T[], T[]] {
  const truthy: T[] = [];
  const falsy: T[] = [];

  for (const item of array) {
    if (predicate(item)) {
      truthy.push(item);
    } else {
      falsy.push(item);
    }
  }

  return [truthy, falsy];
}

/**
 * Finds the intersection of multiple arrays.
 *
 * @example
 * ```ts
 * intersect([1, 2, 3], [2, 3, 4], [3, 4, 5]); // [3]
 * ```
 */
export function intersect<T>(...arrays: T[][]): T[] {
  if (arrays.length === 0) return [];
  if (arrays.length === 1) return unique(arrays[0]!);

  const sets = arrays.map((arr) => new Set(arr));
  const [first, ...rest] = sets;

  return Array.from(first!).filter((item) =>
    rest.every((set) => set.has(item))
  );
}

/**
 * Finds the difference between arrays (elements in first array but not in others).
 *
 * @example
 * ```ts
 * difference([1, 2, 3, 4], [2, 3], [3, 4]); // [1]
 * ```
 */
export function difference<T>(array: T[], ...others: T[][]): T[] {
  const othersSet = new Set(others.flat());
  return array.filter((item) => !othersSet.has(item));
}

/**
 * Performs a deep equality check between two values.
 *
 * @example
 * ```ts
 * deepEqual([1, [2, 3]], [1, [2, 3]]); // true
 * deepEqual({a: {b: 1}}, {a: {b: 1}}); // true
 * ```
 */
export function deepEqual(a: unknown, b: unknown): boolean {
  if (a === b) return true;

  if (a === null || b === null) return false;
  if (typeof a !== typeof b) return false;
  if (typeof a !== "object") return false;

  if (Array.isArray(a) !== Array.isArray(b)) return false;

  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) return false;
    for (let i = 0; i < a.length; i++) {
      if (!deepEqual(a[i], b[i])) return false;
    }
    return true;
  }

  const keysA = Object.keys(a as object);
  const keysB = Object.keys(b as object);

  if (keysA.length !== keysB.length) return false;

  for (const key of keysA) {
    if (!keysB.includes(key)) return false;
    if (
      !deepEqual(
        (a as Record<string, unknown>)[key],
        (b as Record<string, unknown>)[key],
      )
    ) return false;
  }

  return true;
}