cerceis-lib
Version:
Contains list of quality of life functions that is written in TypeScript and es6
478 lines (474 loc) • 16.4 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/kmeans/index.ts
var kmeans_exports = {};
__export(kmeans_exports, {
KMeans: () => KMeans,
KMeansND: () => KMeansND
});
module.exports = __toCommonJS(kmeans_exports);
// src/generate/generate.ts
var Generate = {
/**
* Generates a string consist of alphanumeric characters of given length
* @param {number}[len=4] Length of the string
* @returns Alphanumeric string
*/
alphanum(len = 4) {
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (len === 1) return chars[Math.floor(Math.random() * chars.length)];
let result = "";
for (let i = len; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
return result;
},
/**
* Generates Object Id.
* @returns
*/
objectId() {
const timestamp = ((/* @__PURE__ */ new Date()).getTime() / 1e3 | 0).toString(16);
return timestamp + "xxxxxxxxxxxxxxxx".replace(/[x]/g, () => {
return (Math.random() * 16 | 0).toString(16);
}).toLowerCase();
},
/**
* Generates random integer in a given range of [).
* Includes Min, exclude Max.
* @param min
* @param max
* @returns number
*/
int(min, max) {
let result = 0;
result = Math.floor(Math.random() * (max - min)) + min;
return result;
},
/**
* Generate random number.
* @param min
* @param max
* @returns
*/
random(min, max) {
return Math.random() * (max - min) + min;
},
/**
* Generates an array with random integer as elemnt of desired length.
* @param len @required Length of the array.
* @returns Array of a given length
*/
array(len, ops = {}) {
const defaultOps = {
min: 0,
max: 11
};
const mergedOps = { ...defaultOps, ...ops };
const rs = [];
for (let i = 0; i < len; i++)
rs.push(this.int(mergedOps.min ?? 0, mergedOps.max ?? 11));
return rs;
},
/**
* Generates random alphabate of specified length.
* @param {number} [len=5] Length of the string. default = 5,
* @param {AlphabateOptions} [options] Options
*/
alphabate(len = 5, options = {}) {
const op = {
lowercase: true,
uppercase: true
};
Object.assign(op, options);
let result = "";
const lowerCaseCharacters = "abcdefghijklmnopqrstuvwxyz";
const upperCaseCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let characterPool = "";
if (op.lowercase) characterPool += lowerCaseCharacters;
if (op.uppercase) characterPool += upperCaseCharacters;
const charactersLength = characterPool.length;
for (let i = 0; i < len; i++) {
result += characterPool.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
},
/**
* @returns Current Date Time with a format of "YYYY-MM-DD""
*/
currentDate() {
const t = /* @__PURE__ */ new Date();
return `${t.getFullYear()}-${t.getMonth() + 1}-${t.getDate()}`;
},
/**
* @returns Current Date Time with a format of "HH:mm:ss""
*/
currentTime() {
const t = /* @__PURE__ */ new Date();
return `${String(t.getHours()).padStart(2, "0")}:${String(t.getMinutes()).padStart(2, "0")}:${String(t.getSeconds()).padStart(2, "0")}`;
},
/**
* @returns Current Date Time with a format of "YYYY-MM-DD HH:mm:ss""
*/
currentDateTime() {
const t = /* @__PURE__ */ new Date();
return `${t.getFullYear()}-${t.getMonth() + 1}-${t.getDate()} ${String(t.getHours()).padStart(2, "0")}:${String(t.getMinutes()).padStart(2, "0")}:${String(t.getSeconds()).padStart(2, "0")}`;
},
/**
* Generates and return list of date of specified day.
* Ex: All Sunday and Monday of 2022-01-01 to 2022-03-01
* @param f From : YYYY-MM-DD
* @param t To : YYYY-MM-DD
* @param days number[] 0 ~ 6 Sunday ~ Saturday.
*/
listOfDateOfDays(f, t, days) {
let fromDate = new Date(f);
const toData = new Date(t);
let fromISO = fromDate.toISOString();
const toISO = toData.toISOString();
const rs = [];
while (true) {
if (fromISO > toISO) break;
if (days.includes(fromDate.getDay()))
rs.push(fromISO.slice(0, 10));
fromDate.setDate(fromDate.getDate() + 1);
fromISO = fromDate.toISOString();
}
return rs;
},
/**
* Fast UUID generator, RFC4122 version 4 compliant.
* Copied and rewritted from the below. Thanks for such
* elegant code.
* @author Jeff Ward (jcward.com).
* @license MIT license
* @link http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136
**/
uuidv4() {
const lut = [];
for (let i = 0; i < 256; i++) {
lut[i] = (i < 16 ? "0" : "") + i.toString(16);
}
const d0 = Math.random() * 4294967295 | 0;
const d1 = Math.random() * 4294967295 | 0;
const d2 = Math.random() * 4294967295 | 0;
const d3 = Math.random() * 4294967295 | 0;
return lut[d0 & 255] + lut[d0 >> 8 & 255] + lut[d0 >> 16 & 255] + lut[d0 >> 24 & 255] + "-" + lut[d1 & 255] + lut[d1 >> 8 & 255] + "-" + lut[d1 >> 16 & 15 | 64] + lut[d1 >> 24 & 255] + "-" + lut[d2 & 63 | 128] + lut[d2 >> 8 & 255] + "-" + lut[d2 >> 16 & 255] + lut[d2 >> 24 & 255] + lut[d3 & 255] + lut[d3 >> 8 & 255] + lut[d3 >> 16 & 255] + lut[d3 >> 24 & 255];
}
};
// src/array/index.ts
var FromArray = {
/**
* Returns one or more random elements from an array.
* @param arr Input array.
* @param noOfResult Number of results to return (default 1).
* @param returnIndex Return index positions instead of values.
*/
getRandom(arr, noOfResult = 1, returnIndex = false) {
if (!Array.isArray(arr)) throw new Error("Input must be an array");
if (returnIndex) {
const result2 = [];
for (let i = 0; i < noOfResult; i++)
result2.push(Math.floor(Math.random() * arr.length));
return result2;
}
const result = [];
for (let i = 0; i < noOfResult; i++)
result.push(arr[Math.floor(Math.random() * arr.length)]);
return result;
},
/**
* Find and return the largest N numbers.
* @param numbers Array of numbers.
* @param n Number of results to return (default 1).
* @param returnIndex Return indices instead of values.
*/
getLargest(numbers, n = 1, returnIndex = false) {
const results = [];
if (returnIndex) {
const indexed = numbers.map((v, i) => ({ v, i })).sort((a, b) => b.v - a.v);
for (let i = 0; i < n && i < indexed.length; i++) results.push(indexed[i].i);
} else {
const sorted = [...numbers].sort((a, b) => b - a);
for (let i = 0; i < n && i < sorted.length; i++) results.push(sorted[i]);
}
return results;
},
/**
* Find and return the smallest N numbers.
* @param numbers Array of numbers.
* @param n Number of results to return (default 1).
* @param returnIndex Return indices instead of values.
*/
getSmallest(numbers, n = 1, returnIndex = false) {
const results = [];
if (returnIndex) {
const indexed = numbers.map((v, i) => ({ v, i })).sort((a, b) => a.v - b.v);
for (let i = 0; i < n && i < indexed.length; i++) results.push(indexed[i].i);
} else {
const sorted = [...numbers].sort((a, b) => a - b);
for (let i = 0; i < n && i < sorted.length; i++) results.push(sorted[i]);
}
return results;
},
/**
* Return the intersection of two arrays.
* @param arrA Array A.
* @param arrB Array B.
* @param duplicated Include duplicate matches.
*/
getIntersect(arrA, arrB, duplicated = false) {
const seen = /* @__PURE__ */ new Map();
const result = [];
for (const item of arrA) seen.set(String(item), 1);
for (const item of arrB) {
const key = String(item);
if (seen.has(key)) {
if (!duplicated && seen.get(key) === 1) {
result.push(item);
seen.set(key, 2);
} else if (duplicated) {
result.push(item);
}
}
}
return result;
},
/**
* Randomly shuffle an array in-place (Fisher-Yates).
* @param arr Array to shuffle.
*/
shuffle(arr) {
let currentIndex = arr.length;
while (currentIndex !== 0) {
const randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[arr[currentIndex], arr[randomIndex]] = [arr[randomIndex], arr[currentIndex]];
}
return arr;
},
/**
* The Thanos snap — removes roughly half the elements randomly.
* Mutates the original array.
* @param arr Input array.
*/
thanosSnap(arr) {
const targetLen = arr.length % 2 === 0 ? arr.length / 2 : (arr.length - 1) / 2;
while (arr.length !== targetLen)
arr.splice(Math.floor(Math.random() * arr.length), 1);
return arr;
},
/**
* Convert a 2D array ([[key, value], ...]) into an object.
* @param arr Input 2D array.
*/
toObject(arr) {
const obj = {};
for (const [k, v] of arr) obj[k] = v;
return obj;
},
/**
* Split an array into groups by ratio.
* ex) [1,2,3,4] split [1,3] → {1:[1], 2:[2,3,4]}
* Remainder goes into "extra" if it doesn't fit.
* @param arr Array to split.
* @param ratio Ratio to split into.
*/
splitInto(arr, ratio) {
if (ratio.length === 0) return { 1: arr };
const filtered = ratio.filter((r) => r !== 0 && !isNaN(r));
const unitLen = Math.floor(1 / filtered.reduce((a, b) => a + b) * arr.length);
const rs = {};
let group = 1;
let offset = 0;
for (const r of ratio) {
const len = r * unitLen;
rs[group] = arr.slice(offset, offset + len);
offset += rs[group].length;
group++;
}
if (offset < arr.length) rs["extra"] = arr.slice(offset);
return rs;
},
/**
* Log array elements to console, optionally limited to a range.
* @param arr Array to log.
* @param from Starting index (inclusive), default 0.
* @param to Ending index (exclusive), default arr.length.
*/
log(arr, from = 0, to = arr.length) {
for (let i = from; i < to; i++) console.log(arr[i]);
}
};
// src/kmeans/kMeans.ts
var KMeans = (k = 2, arr, attempts = 1) => {
if (arr.length === 0) throw new Error("Empty array.");
const max = Math.max(...arr);
const min = Math.min(...arr);
const variations = [];
for (let attempt = 0; attempt < attempts; attempt++) {
let clusters = [];
for (let i = 0; i < k; i++)
clusters.push({ id: i + 1, position: Generate.int(min, max + 1), childs: [] });
let previousClusters = [];
while (!samePositions(clusters, previousClusters)) {
previousClusters = clusters.map((c) => ({ ...c }));
for (const c of clusters) c.childs = [];
clusters = assignPoints(clusters, arr);
clusters = recalibrate(clusters);
}
variations.push(clusters);
}
if (variations.length === 1) return variations[0];
const scores = variations.map((v) => {
let score = 0;
for (let i = 0; i < v.length - 1; i++)
score += Math.abs(v[i + 1].childs.length - v[i].childs.length);
return score;
});
const bestIndex = FromArray.getSmallest(scores, 1, true)[0];
return variations[bestIndex];
};
function samePositions(clusters, prev) {
if (prev.length === 0) return false;
return clusters.every((c, i) => c.position === prev[i].position);
}
function assignPoints(clusters, arr) {
for (const point of arr) {
const distances = clusters.map((c) => Math.abs(c.position - point));
const nearest = FromArray.getSmallest(distances, 1, true)[0];
clusters[nearest].childs.push(point);
}
return clusters;
}
function recalibrate(clusters) {
for (const c of clusters) {
if (c.childs.length === 0) continue;
const mean = c.childs.reduce((a, b) => a + b, 0) / c.childs.length;
c.position = Number(mean.toFixed(2));
}
return clusters;
}
var KMeansND = (k, data, features, options = {}) => {
if (data.length === 0) throw new Error("Empty data array.");
if (k < 1) throw new Error("k must be >= 1.");
if (k > data.length) throw new Error("k cannot exceed the number of data points.");
const { attempts = 5, init = "kmeans++", maxIter = 300 } = options;
const points = data.map(features);
const dims = points[0].length;
if (points.some((p) => p.length !== dims))
throw new Error("All feature vectors must have the same length.");
let bestClusters = null;
let bestTotalWCSS = Infinity;
for (let attempt = 0; attempt < attempts; attempt++) {
const centroids = init === "kmeans++" ? initKMeansPlusPlus(points, k) : initRandom(points, k);
const memberIndices = Array.from({ length: k }, () => []);
let prevCentroids = [];
let iter = 0;
while (!centroidsConverged(centroids, prevCentroids) && iter < maxIter) {
prevCentroids = centroids.map((c) => [...c]);
for (const m of memberIndices) m.length = 0;
for (let p = 0; p < points.length; p++) {
const dists = centroids.map((c) => euclidean(c, points[p]));
let nearest = 0;
for (let c = 1; c < k; c++)
if (dists[c] < dists[nearest]) nearest = c;
memberIndices[nearest].push(p);
}
for (let c = 0; c < k; c++) {
if (memberIndices[c].length === 0) {
const randomIdx = Math.floor(Math.random() * points.length);
memberIndices[c].push(randomIdx);
}
}
for (let c = 0; c < k; c++) {
const newCentroid = new Array(dims).fill(0);
for (const idx of memberIndices[c])
for (let d = 0; d < dims; d++)
newCentroid[d] += points[idx][d];
for (let d = 0; d < dims; d++)
newCentroid[d] /= memberIndices[c].length;
centroids[c] = newCentroid;
}
iter++;
}
const clusters = centroids.map((centroid, c) => {
const members = memberIndices[c].map((i) => data[i]);
const wcss = memberIndices[c].reduce(
(sum, i) => sum + euclideanSq(points[i], centroid),
0
);
return { id: c + 1, centroid, members, size: members.length, wcss };
});
const totalWCSS = clusters.reduce((s, c) => s + c.wcss, 0);
if (totalWCSS < bestTotalWCSS) {
bestTotalWCSS = totalWCSS;
bestClusters = clusters;
}
}
return bestClusters;
};
function euclidean(a, b) {
return Math.sqrt(euclideanSq(a, b));
}
function euclideanSq(a, b) {
let sum = 0;
for (let i = 0; i < a.length; i++) sum += (a[i] - b[i]) ** 2;
return sum;
}
function initKMeansPlusPlus(points, k) {
const centroids = [];
centroids.push([...points[Math.floor(Math.random() * points.length)]]);
for (let c = 1; c < k; c++) {
const weights = points.map((p) => {
let minDist = Infinity;
for (const centroid of centroids) {
const d = euclideanSq(p, centroid);
if (d < minDist) minDist = d;
}
return minDist;
});
centroids.push([...weightedChoice(points, weights)]);
}
return centroids;
}
function initRandom(points, k) {
const indices = /* @__PURE__ */ new Set();
while (indices.size < k)
indices.add(Math.floor(Math.random() * points.length));
return [...indices].map((i) => [...points[i]]);
}
function weightedChoice(points, weights) {
const total = weights.reduce((a, b) => a + b, 0);
let r = Math.random() * total;
for (let i = 0; i < weights.length; i++) {
r -= weights[i];
if (r <= 0) return points[i];
}
return points[points.length - 1];
}
var CONVERGENCE_EPSILON = 1e-10;
function centroidsConverged(curr, prev) {
if (prev.length === 0) return false;
return curr.every((c, i) => euclidean(c, prev[i]) < CONVERGENCE_EPSILON);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
KMeans,
KMeansND
});
//# sourceMappingURL=index.js.map