Scriptable extensions.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
importModule('./lib/extensions')({ | |
// only need to pass in the types you need extended | |
// for this script | |
Math, | |
Number, | |
Array, | |
}) | |
console.log( | |
Math.getFactors((6).factorial()) | |
.shuffle() | |
.joinNL() | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const [ | |
_Object = Object, | |
_Number = Number, | |
_Array = Array, | |
_BigInt = BigInt, | |
_Math = Math, | |
_String = String, | |
_Promise = Promise, | |
] = [] | |
// used to ensure each type only gets extended once | |
// per sandbox | |
const extended = new Set() | |
const ex = (type, fn) => { | |
if (!extended.has(type)) { | |
fn() | |
extended.add(type) | |
} | |
} | |
const _primes = [2, 3] | |
const _primesSet = new Set(_primes) | |
const _memoedFactorials = [1, 1] | |
const _max = Math.max; | |
const _min = Math.min; | |
const extendNatives = module.exports = ({ | |
Object = _Object, | |
Number = _Number, | |
Array = _Array, | |
BigInt = _BigInt, | |
Math = _Math, | |
String = _String, | |
Promise = _Promise, | |
}) => { | |
ex(Array, () => { | |
Object.assign(Array.prototype, { | |
eachAnd(fn) { | |
this.forEach(fn) | |
return this | |
}, | |
unique() { | |
return Array.from(new Set(this)) | |
}, | |
shuffle(inPlace = true) { | |
const arr = inPlace ? this : this.slice() | |
return arr.eachAnd((_, idx) => { | |
const sIdx = | |
Math.randInt(idx, arr.length - 1) | |
[arr[idx], arr[sIdx]] = | |
[arr[sIdx], arr[idx]] | |
}) | |
}, | |
chooseRandom() { | |
return this[Math.randInt(this.length)] | |
}, | |
joinNL() { | |
return this.join('\n') | |
}, | |
}) | |
Object.defineProperty(Array.prototype, 'last', { | |
get() { | |
if (!this.length) { | |
return undefined | |
} | |
return this[this.length - 1] | |
}, | |
set(value) { | |
if (!this.length) { | |
throw new Error( | |
'Cannot assign last to empty array' | |
) | |
} | |
return (this[this.length - 1] = value) | |
}, | |
}) | |
}) | |
ex(BigInt, () => { | |
BigInt.isBigInt = n => | |
typeof n === 'bigint' || n instanceof BigInt | |
BigInt.divide = (n, d) => { | |
const val = Math.log(n) - Math.log(d) | |
if (Number.isInteger(val)) { | |
return n / d | |
} | |
return val | |
}; | |
BigInt.prototype.divideBy = function (d) { | |
return BigInt.divide(this, d) | |
} | |
}) | |
ex(Number, () => { | |
Number.isBigInt = BigInt.isBigInt | |
Number.memoedPrimes = _primes | |
Number.memoedPrimesSet = _primesSet | |
Number.prototype.divideBy = function (d) { | |
if (!BigInt.isBigInt(d)) { | |
return this / d | |
} | |
return BigInt.divide(this, d) | |
}; | |
Number.prototype.factorial = | |
BigInt.prototype.factorial = | |
function () { return Math.factorial(this) } | |
Number.range = function* (min, max, step) { | |
if (max == null) { | |
[min, max] = [0, min] | |
} | |
step = step != null | |
? step | |
: min <= max | |
? 1 | |
: -1 | |
for (let i = min; i < max; i += step) { | |
yield i | |
} | |
} | |
Number.rangeArray = (min, max, step) => { | |
if (step === 0) { | |
throw new Error('step cannot be 0') | |
} | |
if (step != null) { | |
if (step > 0 && max < min) { | |
throw new Error( | |
`step (${step}) must be positive when max (${max}) < min (${min})` | |
) | |
} else if (step < 0 && min < max) { | |
throw new Error( | |
`step (${step}) must be negative when min (${min}) < max (${max})` | |
) | |
} | |
} | |
return [...Number.range(min, max, step)] | |
} | |
Number.isPrime = n => n.isPrime() | |
Number.prototype.isPrime = function () { | |
const n = Number(this) | |
if (_primesSet.has(n)) { | |
return true; | |
} | |
return [ | |
...Math.genPrimes(_primes.last + 2, n) | |
].last === n | |
} | |
Number.prototype.divides = function (n) { | |
return !(n % this) | |
} | |
Number.compare = (a, b) => a - b | |
}) | |
ex(Math, () => { | |
Math.factorial = function factorial(n) { | |
if (_memoedFactorials[n]) { | |
return _memoedFactorials[n] | |
} | |
const nM1F = factorial(n - 1) | |
if ( | |
BigInt.isBigInt(n) || | |
BigInt.isBigInt(nM1F) | |
) { | |
return BigInt(n) * BigInt(nM1F) | |
} | |
return (_memoedFactorials[n] = n * nM1F) | |
} | |
Math.nCr = function (n, r) { | |
if (r > n) { | |
return 0 | |
} | |
n = BigInt(n) | |
let c = BigInt(1) | |
for (let d = BigInt(1); d <= r; d++) { | |
c *= n-- | |
c /= d | |
} | |
return c > Number.MAX_SAFE_INTEGER | |
? c | |
: Number(c) | |
} | |
Math.max = (...vals) => | |
vals.flat().reduce((m, v) => | |
Number.isNaN(m) | |
? v | |
: _max(m, v), | |
NaN) | |
Math.min = (...vals) => | |
vals.reduce((m, v) => | |
Number.isNaN(m) | |
? v | |
: _min(m, v), | |
NaN) | |
Math.sum = (...values) => | |
values.reduce((a, f) => a + f, 0) | |
Math.product = (...values) => | |
values.reduce((a, f) => a * f, 1) | |
Math.avg = (...vals) => | |
Math.sum(...vals) / vals.length | |
Math.genPrimes = function* ( | |
start = 2, | |
end = Infinity | |
) { | |
if (start <= _primes.last) { | |
yield* _primes | |
.filter(p => p >= start && p <= end) | |
} | |
for ( | |
let p = _primes.last + 2; | |
p <= end; | |
p += 2 | |
) { | |
const rootP = Math.ceil(Math.sqrt(p)) | |
let isPrime = true | |
for ( | |
let i = 0, n = _primes[i]; | |
i < _primes.length && n <= rootP; | |
i++, n = _primes[i] | |
) { | |
if (!(p % n)) { | |
isPrime = false | |
break | |
} | |
} | |
if (isPrime) { | |
_primes.push(p) | |
_primesSet.add(p) | |
yield p | |
} | |
} | |
} | |
/** @param {any[]} arr */ | |
const weave = arr => arr.length < 3 | |
? arr | |
: [arr[0], ...weave(arr.slice(2)), arr[1]] | |
Math.getFactors = n => weave( | |
Number.rangeArray( | |
1, | |
Math.floor(Math.sqrt(n)) + 1 | |
) | |
.filter(d => d.divides(n)) | |
.flatMap(d => (n === d * d ? d : [d, n / d])) | |
) | |
Math.getPrimeFactors = n => | |
Math.getFactors(n).filter(Number.isPrime) | |
Math.primeFactorization = n => { | |
/** @type {number[]} */ | |
const result = []; | |
const gen = Math.genPrimes() | |
while (n > 1) { | |
const d = gen.next().value | |
while (!(n % d)) { | |
result.push(d) | |
n /= d | |
} | |
} | |
return result | |
} | |
Math.roundTo = (n, d) => | |
Math.round(n * 10 ** d) / d | |
Math.randInt = (minOrMax, maxOrUndefined) => { | |
const [min, max] = maxOrUndefined == null | |
? [0, minOrMax] | |
: [minOrMax, maxOrUndefined + 1] | |
const range = max - min | |
return Math.floor(Math.random() * range) + min | |
} | |
}) | |
ex(Promise, () => { | |
Promise.sleep = (ms = 0) => | |
new Promise(res => setTimeout(res, ms)) | |
}) | |
ex(String, () => { | |
Object.entries( | |
Object.getOwnPropertyDescriptors( | |
Array.prototype | |
) | |
) | |
.filter(([n, d]) => | |
!(n in String.prototype) && | |
typeof d.value === 'function' | |
) | |
.forEach(([n]) => | |
String.prototype[n] = String.prototype[n] || | |
function (...args) { | |
return Array.from(this)[n](...args) | |
} | |
) | |
String.prototype.splitNL = function () { | |
return this.split(/\r?\n/) | |
} | |
}) | |
ex(Object, () => { | |
Object.mapObject = (obj, fn) => | |
Object.fromEntries(Object.entries(obj).map(fn)) | |
}) | |
} | |
extendNatives({}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment