import * as utils from "./utils.js";
import * as misc from "./miscellaneous.js";
/**
* Compute the length of a vector-like object.
*
* For Array and TypedArrays, this just returns the `length` property directly.
*
* Custom classes should provide a `_bioconductor_LENGTH` method to describe their length.
* This method should accept no arguments.
*
* @param {*} x - Some vector-like object.
* @return {number} Length of the object.
*/
export function LENGTH(x) {
if ("_bioconductor_LENGTH" in x) {
return x._bioconductor_LENGTH();
}
if (!utils.isArrayLike(x)) {
throw new Error("no method for 'LENGTH' in '" + x.constructor.name + "' instance");
}
return x.length;
}
/**
* Slice a vector-like object.
*
* For Array and TypedArrays, this just uses `slice()` or `subarray()`.
*
* Custom classes should provide a `_bioconductor_SLICE` method to create a slice.
* This method should accept the same arguments as `SLICE` except for `x`.
*
* @param {*} x - Some vector-like object.
* @param {Object|Array|TypedArray} i - An Array or TypedArray of integer indices specifying the slice of `x` to retain.
*
* Alternatively, an object containing `start` and `end`, where the slice is defined as the sequence of consecutive integers in `[start, end)`.
* @param {Object} [options={}] - Optional parameters.
* @param {boolean} [options.allowView=false] - Whether a view can be created to mimic the slice operation.
* Whether this is actually done depends on the method, but may improve efficiency by avoiding unnecessary copies.
*
* @return {*} A vector-like object, typically of the same class as `x`, containing data for the specified slice.
*
* If `allowInPlace = true`, `x` _may_ be modified in place, and the return value _may_ be a reference to `x`.
*/
export function SLICE(x, i, { allowView = false } = {}) {
if ("_bioconductor_SLICE" in x) {
let output = new x.constructor;
x._bioconductor_SLICE(output, i, { allowView });
return output;
}
if (!utils.isArrayLike(x)) {
throw new Error("no method for 'SLICE' in '" + x.constructor.name + "' instance");
}
if (i.constructor == Object) {
if (allowView && ArrayBuffer.isView(x)) {
return x.subarray(i.start, i.end);
} else {
return x.slice(i.start, i.end);
}
} else {
let output = new x.constructor(i.length);
i.forEach((y, j) => {
output[j] = x[y];
});
return output;
}
}
/**
* Combine multiple vector-like objects.
*
* For Array and TypedArrays, the combined array is of a class that avoids information loss.
*
* Custom classes should provide a `_bioconductor_COMBINE` method to define the combining operation.
* This method should accept the same arguments as `COMBINE`.
*
* @param {Array} objects - Array of vector-like objects to be combined.
* It is assumed that the objects are of the same class, or at least compatible with each other -
* for custom classes, the definition of "compatibility" depends on the `_bioconductor_COMBINE` method of the first element of `objects`.
*
* @return {*} A vector-like object containing the concatenated data from the input objects.
* - If the first entry of `objects` is an instance of a custom class, the return value should be of the same class.
* - If all `objects` are TypedArrays of the same class, the return value will be a TypedArray of that class.
* - If any of the `objects` are Arrays, the return value will be an Array.
* - If any of the `objects` are 64-bit TypedArrays of different classes, the return value will be an Array.
* - Otherwise, for any other classes of TypedArrays in `objects`, the return value will be a Float64Array.
*/
export function COMBINE(objects) {
let x = objects[0];
if ("_bioconductor_COMBINE" in x) {
let output = new x.constructor;
x._bioconductor_COMBINE(output, objects);
return output;
}
if (!utils.isArrayLike(x)) {
throw new Error("no method for 'COMBINE' in '" + x.constructor.name + "' instance");
}
// It is assumed that every 'y' is of some compatible Array-like type as well.
let total_LENGTH = 0;
let constructor = x.constructor;
for (const obj of objects) {
total_LENGTH += obj.length;
constructor = utils.chooseArrayConstructors(constructor, obj.constructor);
}
let output = new constructor(total_LENGTH);
let position = 0;
for (const obj of objects) {
if ("set" in output) {
output.set(obj, position);
position += obj.length;
} else {
obj.forEach(x => {
output[position] = x;
position++;
});
}
}
return output;
}
/**
* Clone a vector-like object.
*
* For TypedArrays, this just uses `slice()`.
* For Arrays, this creates a copy and runs `CLONE` on each element in the copy.
*
* Custom classes should provide a `_bioconductor_CLONE` method to define the cloning operation.
* This method should accept the same arguments as `COMBINE` except for `x`.
*
* @param {*} x - Some vector-like object.
* @param {Object} [options={}] - Optional parameters.
* @param {boolean} [options.deepCopy=true] - Whether to create a deep copy.
* The exact interpretation of `deepCopy=false` is left to each method, but generally speaking,
* any setter (`$`-marked) functions operating on the copy should not alter `x`.
*
* @return {*} A clone of `x`, i.e., the return value and `x` should not compare equal.
* If `deepCopy=true`, all internal components are also cloned.
*/
export function CLONE(x, { deepCopy = true } = {}) {
if (x instanceof Object) {
let options = { deepCopy };
if ("_bioconductor_CLONE" in x) {
let output = new x.constructor;
x._bioconductor_CLONE(output, options);
return output;
}
if (utils.isArrayLike(x)) {
if (x.constructor == Array) {
return x.map(y => CLONE(y, options));
} else if (deepCopy) {
return x.slice();
} else {
return x.subarray();
}
}
if (x.constructor == Object) {
if (deepCopy) {
let output = {};
for (const [k, v] of Object.entries(x)) {
output[k] = CLONE(v);
}
return output;
} else {
return { ...x };
}
}
if (x.constructor == Map) {
let output = new Map;
for (const [k, v] of x) {
output.set(k, deepCopy ? CLONE(v) : v);
}
return output;
}
if (x.constructor == Set) {
let output = new Set;
for (const k of x) {
output.add(deepCopy ? CLONE(k) : k);
}
return output;
}
throw new Error("unknown CLONE operation for instance of class '" + x.constructor.name + "'");
}
// Immutable atomics should be all that's left.
return x;
}
/**
* Split a vector-like object along its length according to the levels of a factor of the same length.
* This works automatically for all classes for which there is a {@linkcode SLICE} method,
* but custom classes may also choose to define their own `_bioconductor_SPLIT` method.
*
* @param {*} x - Some vector-like object.
* @param {Array|TypedArray} factor - Array containing the factor to use for splitting.
* This should have the same length as `x`.
*
* Alternatively, the output of {@linkcode presplitFactor} can be supplied.
*
* @return {Object} An object containing one key per level of `factor`,
* where the value is the slice of `x` corresponding to the indices of that level in `factor`.
*/
export function SPLIT(x, factor) {
if (factor.constructor != Object) {
factor = misc.presplitFactor(factor);
}
if ("_bioconductor_SPLIT" in x) {
return x._bioconductor_SPLIT(factor);
}
let output = {};
for (const [k, v] of Object.entries(factor)) {
output[k] = SLICE(x, v);
}
return output;
}
/**
* Return the number of rows for a two-dimensional object.
* Custom classes should provide a `_bioconductor_NUMBER_OF_ROWS` method, accepting no arguments.
*
* @param {*} x - Some two-dimensional object.
* @return {number} Number of rows.
*/
export function NUMBER_OF_ROWS(x) {
if (!("_bioconductor_NUMBER_OF_ROWS" in x)) {
throw new Error("no 'NUMBER_OF_ROWS' method available for '" + x.constructor.name + "' instance");
}
return x._bioconductor_NUMBER_OF_ROWS();
}
/**
* Return the number of columns for a two-dimensional object.
* Custom classes should provide a `_bioconductor_NUMBER_OF_COLUMNS` method, accepting no arguments.
*
* @param {*} x - Some two-dimensional object.
* @return {number} Number of columns.
*/
export function NUMBER_OF_COLUMNS(x) {
if (!("_bioconductor_NUMBER_OF_COLUMNS" in x)) {
throw new Error("no 'NUMBER_OF_COLUMNS' method available for '" + x.constructor.name + "' instance");
}
return x._bioconductor_NUMBER_OF_COLUMNS();
}
/**
* Slice a two-dimensional object by its rows and/or columns.
*
* Custom classes should provide a `_bioconductor_SLICE_2D` method, accepting the same arguments as this generic but with `x` replaced by an "empty" instance of the same class.
* Each method should then fill the empty instance with the sliced contents of `x`.
*
* @param {*} x - Some two-dimensional object.
* @param {?(Object|Array|TypedArray)} rows - An Array or TypedArray of integer indices specifying the row-wise slice of `x` to retain.
*
* Alternatively, an object containing `start` and `end`, where the slice is defined as the sequence of consecutive integers in `[start, end)`.
*
* Alternatively `null`, to indicate that no slicing is to be performed on the rows.
* @param {?(Object|Array|TypedArray)} columns - An Array or TypedArray of integer indices specifying the column-wise slice of `x` to retain.
*
* Alternatively, an object containing `start` and `end`, where the slice is defined as the sequence of consecutive integers in `[start, end)`.
*
* Alternatively `null`, to indicate that no slicing is to be performed on the columns.
* @param {Object} [options={}] - Optional parameters.
* @param {boolean} [options.allowView=false] - Whether a view can be created to mimic the slice operation.
* Whether this is actually done depends on the method, but may improve efficiency by avoiding unnecessary copies.
*
* @return {*} A two-dimensional object, typically of the same class as `x`, containing data for the specified slice.
*/
export function SLICE_2D(x, rows, columns, { allowView = false } = {}) {
if (!("_bioconductor_SLICE_2D" in x)) {
throw new Error("no 'SLICE_2D' method available for '" + x.constructor.name + "' instance");
}
let output = new x.constructor;
x._bioconductor_SLICE_2D(output, rows, columns, { allowView });
return output;
}
/**
* Combine multiple two-dimensional objects by row.
* Custom classes should provide a `_bioconductor_COMBINE_ROWS` method to define the combining operation.
* This method should accept:
* - an "empty" instance of the class of the first object, to be populated with data.
* - an array of objects to be combined, like `objects`.
*
* @param {Array} objects - Array of two-dimensional objects to be combined by row.
* It is assumed that the objects are of the same class, or at least compatible with each other -
* for custom classes, the definition of "compatibility" depends on the `_bioconductor_COMBINE_ROWS` method of the first element of `objects`.
*
* @return {*} A two-dimensional object containing the row-wise concatenated data from the input objects, typically of the same class as the first entry of `objects`.
*/
export function COMBINE_ROWS(objects) {
let x = objects[0];
if (!("_bioconductor_COMBINE_ROWS" in x)) {
throw new Error("no 'COMBINE_ROWS' method available for '" + x.constructor.name + "' instance");
}
let output = new x.constructor;
x._bioconductor_COMBINE_ROWS(output, objects);
return output;
}
/**
* Combine multiple two-dimensional objects by column.
* Custom classes should provide a `_bioconductor_COMBINE_COLUMNS` method to define the combining operation.
* This method should accept:
* - an "empty" instance of the class of the first object, to be populated with data.
* - an array of objects to be combined, like `objects`.
*
* @param {Array} objects - Array of two-dimensional objects to be combined by column.
* It is assumed that the objects are of the same class, or at least compatible with each other -
* for custom classes, the definition of "compatibility" depends on the `_bioconductor_COMBINE_COLUMNS` method of the first element of `objects`.
*
* @return {*} A two-dimensional object containing the column-wise concatenated data from the input objects, typically of the same class as the first entry of `objects`.
*/
export function COMBINE_COLUMNS(objects) {
let x = objects[0];
if (!("_bioconductor_COMBINE_COLUMNS" in x)) {
throw new Error("no 'COMBINE_COLUMNS' method available for '" + x.constructor.name + "' instance");
}
let output = new x.constructor;
x._bioconductor_COMBINE_COLUMNS(output, objects);
return output;
}