mirror of
https://github.com/gnh1201/welsonjs.git
synced 2025-10-24 17:41:17 +00:00
Returns false if more than one third of the grid is selected, treating it as an abnormal case. Also updates VERSIONINFO to 1.0.4.
150 lines
5.4 KiB
JavaScript
150 lines
5.4 KiB
JavaScript
// extramath.js
|
|
// Copyright 2019-2025, Namhyeon Go <gnh1201@catswords.re.kr> and the WelsonJS contributors.
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// https://github.com/gnh1201/welsonjs
|
|
//
|
|
// Documentation: https://app.rdbl.io/T0AB411UHDW9/S0VLKP4HKVGC/P0TL9IQZOUFT
|
|
//
|
|
// DTM(Document-term Matrix): https://en.wikipedia.org/wiki/Document-term_matrix
|
|
function DTM() {
|
|
this.data = [];
|
|
this.terms = [];
|
|
|
|
this.add = function(s) {
|
|
var w = s.trim().split(/\s+/);
|
|
for (var i = 0; i < w.length; i++) {
|
|
if (this.terms.indexOf(w[i]) < 0) this.terms.push(w[i]);
|
|
}
|
|
this.data.push(w);
|
|
};
|
|
|
|
this.toArray = function() {
|
|
var dtm = [];
|
|
for (var i = 0; i < this.data.length; i++) {
|
|
var dt = [];
|
|
for (var k = 0; k < this.terms.length; k++) {
|
|
dt.push(this.data[i].indexOf(this.terms[k]) < 0 ? 0 : 1);
|
|
}
|
|
dtm.push(dt);
|
|
}
|
|
return dtm;
|
|
};
|
|
}
|
|
|
|
// Cosine similarity: https://en.wikipedia.org/wiki/Cosine_similarity
|
|
function arrayCos(A, B) {
|
|
var dotproduct = 0;
|
|
var mA = 0;
|
|
var mB = 0;
|
|
for (i = 0; i < A.length; i++) {
|
|
dotproduct += (A[i] * B[i]);
|
|
mA += (A[i] * A[i]);
|
|
mB += (B[i] * B[i]);
|
|
}
|
|
mA = Math.sqrt(mA);
|
|
mB = Math.sqrt(mB);
|
|
var similarity = (dotproduct) / ((mA) * (mB))
|
|
return similarity;
|
|
}
|
|
|
|
function measureSimilarity(s1, s2) {
|
|
var dtm = new DTM();
|
|
dtm.add(s1);
|
|
dtm.add(s2);
|
|
var mat = dtm.toArray();
|
|
return arrayCos(mat[0], mat[1]);
|
|
}
|
|
|
|
function export_measureSimilarity() {
|
|
return "var ExtraMath=function(){};ExtraMath.DTM=function(){this.data=[],this.terms=[],this.add=function(t){for(var r=t.trim().split(/\s+/),a=0;a<r.length;a++)0>this.terms.indexOf(r[a])&&this.terms.push(r[a]);this.data.push(r)},this.toArray=function(){for(var t=[],r=0;r<this.data.length;r++){for(var a=[],s=0;s<this.terms.length;s++)a.push(0>this.data[r].indexOf(this.terms[s])?0:1);t.push(a)}return t}},ExtraMath.arrayCos=function(t,r){var a=0,s=0,h=0;for(i=0;i<t.length;i++)a+=t[i]*r[i],s+=t[i]*t[i],h+=r[i]*r[i];return a/((s=Math.sqrt(s))*(h=Math.sqrt(h)))},ExtraMath.measureSimilarity=function(t,r){var a=new ExtraMath.DTM;a.add(t),a.add(r);var s=a.toArray();return ExtraMath.arrayCos(s[0],s[1])};";
|
|
}
|
|
|
|
// Cartesian product: https://en.wikipedia.org/wiki/Cartesian_product
|
|
function cartesianProduct(arr) {
|
|
return arr.reduce(function(a,b){
|
|
return a.map(function(x){
|
|
return b.map(function(y){
|
|
return x.concat([y]);
|
|
})
|
|
}).reduce(function(a,b){ return a.concat(b) },[])
|
|
}, [[]])
|
|
}
|
|
|
|
function clusteredCellsDensity(numbers, size, minDensity) {
|
|
if (!numbers || !numbers.length) return false;
|
|
if (typeof size !== 'number' || size <= 0) size = 4; // default grid size = 4
|
|
if (typeof minDensity !== 'number' || isNaN(minDensity)) minDensity = 0.6;
|
|
|
|
var maxIndex = size * size; // because it's 1-based (1..size*size)
|
|
|
|
// 1. Convert cell number → (x,y) coordinates with 1-based check
|
|
var coords = [];
|
|
for (var i = 0; i < numbers.length; i++) {
|
|
var n = numbers[i];
|
|
// must be within 1..maxIndex
|
|
if (typeof n !== 'number' || n < 1 || n > maxIndex) {
|
|
return false; // invalid index -> immediately return false
|
|
}
|
|
var idx = n - 1; // shift to 0-based
|
|
coords.push({ x: idx % size, y: Math.floor(idx / size) });
|
|
}
|
|
|
|
// --- Additional rule: if more than 1/3 of the entire grid is selected,
|
|
// treat it as an abnormal case and return false immediately.
|
|
var globalRatio = coords.length / (size * size);
|
|
if (globalRatio > (1/3)) {
|
|
return false;
|
|
}
|
|
|
|
// --- Normal density calculation based on bounding box ---
|
|
var xs = coords.map(function (c) { return c.x; });
|
|
var ys = coords.map(function (c) { return c.y; });
|
|
|
|
// 2. Compute bounding box of selected cells
|
|
var minX = Math.min.apply(Math, xs), maxX = Math.max.apply(Math, xs);
|
|
var minY = Math.min.apply(Math, ys), maxY = Math.max.apply(Math, ys);
|
|
|
|
// 3. Compute area and density
|
|
var w = maxX - minX + 1;
|
|
var h = maxY - minY + 1;
|
|
var boxArea = w * h;
|
|
|
|
var density = coords.length / boxArea;
|
|
return density >= minDensity;
|
|
}
|
|
|
|
function estimateTileStartPosition(index, tiles, spreadSize, gap) {
|
|
if (typeof tiles !== 'number' || tiles <= 0) return null;
|
|
if (typeof spreadSize !== 'number' || !isFinite(spreadSize)) return null;
|
|
if (typeof gap !== 'number' || !isFinite(gap) || gap < 0) gap = 0;
|
|
if (typeof index !== 'number' || !isFinite(index) || index % 1 !== 0 || index < 1) return null;
|
|
|
|
var totalSlots = tiles * tiles;
|
|
if (index > totalSlots) return null;
|
|
|
|
var tileSize = (spreadSize - gap * (tiles - 1)) / tiles;
|
|
if (!isFinite(tileSize)) return null;
|
|
|
|
var i = index - 1;
|
|
var col = i % tiles;
|
|
var row = Math.floor(i / tiles);
|
|
|
|
return {
|
|
x: Math.max(0, col) * (tileSize + gap),
|
|
y: Math.max(0, row) * (tileSize + gap)
|
|
};
|
|
}
|
|
|
|
exports.DTM = DTM;
|
|
exports.arrayCos = arrayCos;
|
|
exports.measureSimilarity = measureSimilarity;
|
|
exports.export_measureSimilarity = export_measureSimilarity;
|
|
exports.cartesianProduct = cartesianProduct;
|
|
exports.clusteredCellsDensity = clusteredCellsDensity;
|
|
exports.estimateTileStartPosition = estimateTileStartPosition;
|
|
|
|
exports.VERSIONINFO = "ExtraMath module (extramath.js) version 1.0.4";
|
|
exports.AUTHOR = "gnh1201@catswords.re.kr";
|
|
exports.global = global;
|
|
exports.require = global.require;
|