quantile.js

import quantileSorted from "./quantile_sorted.js";
import quickselect from "./quickselect.js";

/**
 * [分位数](https://en.wikipedia.org/wiki/Quantile)计算函数:
 * 本实现基于总体分位数算法,假设已知完整数据集。遵循维基百科
 * [总体分位数](http://en.wikipedia.org/wiki/Quantile#Quantiles_of_a_population)算法。
 *
 * @param {Array<number>} x 数值型数据数组(需包含至少一个元素)
 * @param {Array<number> | number} p 分位点或分位点数组(取值范围[0,1])
 * @returns {number} 分位数值或分位数数组
 * @example
 * quantile([3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20], 0.5); // => 9
 */
function quantile(x, p) {
    const copy = x.slice(); // 创建数据副本避免修改原数组

    if (Array.isArray(p)) {
        // 多分位点优化处理:重新排列数组元素实现高效选择
        multiQuantileSelect(copy, p);
        // 初始化结果数组
        const results = [];
        // 遍历每个分位点进行计算
        for (let i = 0; i < p.length; i++) {
            results[i] = quantileSorted(copy, p[i]);
        }
        return results;
    } else {
        // 单分位点快速选择
        const idx = quantileIndex(copy.length, p);
        quantileSelect(copy, idx, 0, copy.length - 1);
        return quantileSorted(copy, p);
    }
}

/**
 * 分位数选择函数(基于快速选择算法)
 * @param {Array} arr 目标数组
 * @param {number} k 目标索引
 * @param {number} left 左边界
 * @param {number} right 右边界
 */
function quantileSelect(arr, k, left, right) {
    if (k % 1 === 0) {
        // 整数索引直接快速选择
        quickselect(arr, k, left, right);
    } else {
        // 非整数索引处理相邻两个元素
        k = Math.floor(k);
        quickselect(arr, k, left, right);
        quickselect(arr, k + 1, k + 1, right);
    }
}

/**
 * 多分位点选择优化算法
 * @param {Array} arr 目标数组
 * @param {Array} p 分位点数组
 */
function multiQuantileSelect(arr, p) {
    const indices = [0]; // 初始化索引集合
    for (let i = 0; i < p.length; i++) {
        indices.push(quantileIndex(arr.length, p[i]));
    }
    indices.push(arr.length - 1);
    indices.sort(compare); // 索引排序

    const stack = [0, indices.length - 1]; // 使用栈结构进行分段处理

    while (stack.length) {
        const r = Math.ceil(stack.pop());
        const l = Math.floor(stack.pop());
        if (r - l <= 1) continue;

        const m = Math.floor((l + r) / 2);
        // 对中间分位点进行选择操作
        quantileSelect(
            arr,
            indices[m],
            Math.floor(indices[l]),
            Math.ceil(indices[r])
        );

        stack.push(l, m, m, r); // 压入新的处理区间
    }
}

// 数值比较函数
function compare(a, b) {
    return a - b;
}

/**
 * 分位数索引计算函数
 * @param {number} len 数组长度
 * @param {number} p 分位点
 * @returns {number} 计算后的索引值
 */
function quantileIndex(len, p) {
    const idx = len * p;
    // 边界条件处理
    if (p === 1) {
        return len - 1; // 末位索引
    } else if (p === 0) {
        return 0; // 首位索引
    } else if (idx % 1 !== 0) {
        return Math.ceil(idx) - 1; // 非整数索引处理
    } else if (len % 2 === 0) {
        return idx - 0.5; // 偶数长度中间位置
    } else {
        return idx; // 奇数长度直接返回
    }
}

export default quantile;