Skip to content

Instantly share code, notes, and snippets.

@kybernetikos
Last active May 7, 2021 09:59
Show Gist options
  • Save kybernetikos/11cccdf7cd6dd36b1506ee67e09bb46b to your computer and use it in GitHub Desktop.
Save kybernetikos/11cccdf7cd6dd36b1506ee67e09bb46b to your computer and use it in GitHub Desktop.
Factoradic
const min = (a,b) => (a < b) ? a : b // this works on big ints too, unlike Math.min
const err = message => {throw new Error(message)} // useful if you want to throw an error in an expression
const castToNumber = bigInt => (BigInt(bigInt) <= Number.MAX_SAFE_INTEGER) ? Number(bigInt) : err(`Number too big: ${bigInt} > MAX_SAFE_INTEGER`)
const prefixTo = (array, length) => (array.length >= length) ? array : [...Array(length - array.length).fill(0), ...array]
const factorial = (() => {
const lookupTableSize = 20
let lastVal = 1n
const lookUp = Array.from({length: lookupTableSize}, (_,i) => lastVal = i === 0 ? lastVal : BigInt(i) * lastVal)
return function factorial(n) {
let gotVal = BigInt(min(n, lookUp.length-1))
let result = lookUp[gotVal]
while (gotVal < n) { result *= ++gotVal }
return result
}
})()
class Factoradic {
constructor(elements) {
if (elements[elements.length - 1] !== 0) {
// we could also check none of the place values are too large, but that would involve a fair bit of work.
throw new Error(`Not a valid factoradic number ${elements.join(":")}`)
}
this._elements = elements
}
toBigInt() {
return this._elements.reduceRight((prev, curr, i) => prev + factorial(this._elements.length - i - 1) * BigInt(curr), 0n)
}
permute(array) {
const fullElements = prefixTo(this._elements, array.length)
const valuesToAssign = [...array]
const result = []
for (let place of fullElements) {
result.push(valuesToAssign.splice(castToNumber(place), 1)[0])
}
return result
}
toString(separator = ":") {
return this._elements.join(separator)
}
static parse(string, separator = ":") {
return new Factoradic(string.split(separator).map(x => parseInt(x, 10)))
}
static permutations(array) {
if (array.length > 12) {
throw new Error(`Cannot generate all permutations of arrays with more than 12 elements because the resulting array would be too large for javascript. Your array had ${array.length} elements.`)
}
const _elements = [...array]
const length = castToNumber(factorial(_elements.length))
return new Proxy([], {
get(target, prop) {
if (prop === 'length') {
return length
} else {
const numericProp = (typeof prop === "string") ? parseInt(prop, 10) : NaN
if (!isNaN(numericProp) && numericProp < length && numericProp >= 0 && prop == numericProp) {
return Factoradic.fromInt(numericProp).permute(_elements)
} else {
return target[prop]
}
}
}
})
}
static fromInt(number) {
let maxPlace = 1
while (factorial(maxPlace) <= number) {
maxPlace++
}
const result = Array.from({length: maxPlace})
let remainder = BigInt(number)
for (let i = 0; i < result.length; ++i) {
let placeVal = factorial(maxPlace - i - 1)
let digit = remainder / placeVal
remainder -= digit * placeVal
result[i] = castToNumber(digit)
}
return new Factoradic(result)
}
}
const source = ["alice", "bob", "claudia", "dave"]
const srcPerms = Factoradic.permutations(source)
for (let permutation of srcPerms) {
console.log(permutation)
}
const luckyPermutationNo = Math.floor(Math.random()*srcPerms.length)
console.log(`Lucky permutation is number ${luckyPermutationNo} : ${srcPerms[luckyPermutationNo]}`)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment