UNPKG

cerceis-lib

Version:

Contains list of quality of life functions that is written in TypeScript and es6

478 lines (474 loc) 16.4 kB
"use strict"; 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