k-medoids/arrayadapter.mjs

// Array adapters, ported from src/arrayadapter.rs
// All matrices expose: len() -> n, isSquare() -> bool, get(x, y) -> number

/** Wraps a dense 2D array (array of equal-length rows). */
export class DenseMatrix {
  constructor(rows) { this.rows = rows; }
  len() { return this.rows.length; }
  isSquare() {
    const n = this.rows.length;
    for (let i = 0; i < n; i++) { if (this.rows[i].length !== n) return false; }
    return true;
  }
  get(x, y) { return this.rows[x][y]; }
}

/** Lower triangular matrix in serial form (without diagonal). data length = n*(n-1)/2. */
export class LowerTriangle {
  constructor(n, data) { this.n = n; this.data = data; }
  len() { return this.n; }
  isSquare() { return this.data.length === ((this.n * (this.n - 1)) / 2); }
  get(x, y) {
    // products x*(x-1) are always even, so /2 is an exact integer (valid up to 2^53);
    // the original >>1 truncates to int32 and overflows the index for n >= ~46342
    if (x < y) return this.data[((y * (y - 1)) / 2) + x];
    if (x > y) return this.data[((x * (x - 1)) / 2) + y];
    return 0;
  }
}

/** Normalize user input into a matrix object. Idempotent. Accepts a 2D array, DenseMatrix, or LowerTriangle. */
export function arrayAdapter(input) {
  if (input && typeof input.get === 'function' && typeof input.len === 'function') return input;
  if (Array.isArray(input)) return new DenseMatrix(input);
  throw new Error('Unsupported matrix input: provide a 2D array, a DenseMatrix, or a LowerTriangle');
}