UNPKG

@freeword/meta

Version:

Meta package for Freeword: exports all core types, constants, and utilities from the src/ directory.

293 lines 15.1 kB
import _ /**/ from 'lodash'; import * as WordbitsTables from "./WordbitsTables.js"; // const BITS_SMALLEST_26 = 0b11_1111_1111_1111_1111_1111_1111; const BITS_SMALLEST_8 = 0b1111_1111; const BITS_SMALLEST_2 = 0b11; const WordbitsMask = BITS_SMALLEST_26; // const BITS_SMALLEST_14 = 0b11_1111_1111_1111 // const BITS_SMALLEST_12 = 0b1111_1111_1111 // const BITS_SMALLEST_7 = 0b111_1111 /** Which letters in A are missing from B? | `A - B` | Wordbits | Difference | `A & ~B & WordbitMask` | */ export function aMinusB(aBits, bBits) { return (aBits & (~bBits)) & WordbitsMask; } /** Which letters in A are missing from B? | `A - B` | Wordbits | Difference | `A & ~B & WordbitMask` | */ export function aMinusAll(aBits, ...bBitses) { return (aBits & (~unions(...bBitses))) & WordbitsMask; } /** Which letters in A are missing from B? | `A - B` | Wordbits | Difference | `A & ~B & WordbitMask` | */ export function subtract(aBits, bBits) { return (aBits & (~bBits)) & WordbitsMask; } /** Which letters appear in either A or B (or both)? | `A ∪ B` | Wordbits | Union | `A \| B` | */ export function union(aBits, bBits) { return aBits | bBits; } /** Which letters appear in any of the given sets? | `A ∪ B ∪ C` | Wordbits | Union | `A \| B \| C` | */ export function unions(...bitses) { return bitses.reduce((acc, bits) => acc | bits, 0); } /** Which letters appear in either A or B (or both)? | `A ∪ B` | Wordbits | Union | `A \| B` | */ export function inEither(aBits, bBits) { return aBits | bBits; } /** Which letters do both A and B have in common? | `A ∩ B` | Wordbits | Intersection | `A & B` | */ export function intersection(aBits, bBits) { return aBits & bBits; } /** Which letters appear in all of the given sets? | `A ∩ B ∩ C` | Wordbits | Intersection | `A & B & C` | */ export function intersections(...bitses) { return bitses.reduce((acc, bits) => acc & bits, WordbitsMask); } /** Which letters do both A and B have in common? | `A ∩ B` | Wordbits | Intersection | `A & B` | */ export function inBoth(aBits, bBits) { return aBits & bBits; } /** Which letters are in A, or in B, but not in both? | `A ∆ B` | Wordbits | Symmetric Difference | `A ^ B` | */ export function inEitherNotBoth(aBits, bBits) { return aBits ^ bBits; } /** Which letters are in A, or in B, but not in both? | `A ∆ B` | Wordbits | Symmetric Difference | `A ^ B` | */ export function xor(aBits, bBits) { return aBits ^ bBits; } /** Do A and B share any letters at all? | `A ∩ B ≠ ∅` | boolean | Overlap | `(A & B) !== 0` | */ export function overlaps(aBits, bBits) { return (aBits & bBits) !== 0; } /** Do A and B have no letters in common? | `A ∩ B = ∅` | boolean | Disjoint | `(A & B) === 0` | */ export function hasNoOverlap(aBits, bBits) { return (aBits & bBits) === 0; } /** Do A and B have no letters in common? | `A ∩ B = ∅` | boolean | Disjoint | `(A & B) === 0` | */ export function disjoint(aBits, bBits) { return (aBits & bBits) === 0; } /** Do A and B use exactly the same letters? | `A = B` | boolean | Equality | `A === B` | */ export function equals(aBits, bBits) { return aBits === bBits; } export function missingLtrs(wordbits) { return ~wordbits & BITS_SMALLEST_26; } // /** @returns a mask suitable for use in s1MaskContainsS2 */ // export const containsMaskFor = missingLtrs // /** @returns a mask suitable for use in s1MaskContainsS2 */ // export function containsMaskForWord(word: TY.Word | TY.Letter[]): TY.MissingBits { // return missingLtrs(wordbitsForWord(word)) // } // /** @returns true if s1 contains s2 -- NOTE: you must pass in `~s1`, not `s1` */ // export function containsMasked(maskA: TY.MissingBits, bitsB: TY.WordbitsT): boolean { // // maskA has a bit set for each element maskA does not have, // // so if maskA & bitsB has anything set, then bitsB has an element that maskA does not have // return (maskA & bitsB) === 0 // } /** Does A include *all* of the letters in B? | `A ⊆ B` | boolean | Subset | `(A & B) === A` | */ export function contains(aBits, bBits) { return (aBits & bBits) === bBits; } /** Does A include *all* of the letters in B? | `A ⊆ B` | boolean | Subset | `(A & B) === A` | */ export function aHasAllOfB(aBits, bBits) { return (aBits & bBits) === bBits; } /** Does A include all the letters in B *and more*? | `A ⊊ B` | boolean | Strict Subset | `(A & B) === A && A !== B` | */ export function aHasAllAndMoreB(aBits, bBits) { return ((aBits & bBits) === bBits) && aBits !== bBits; } /** Does A include all the letters in B *and more*? | `A ⊊ B` | boolean | Strict Subset | `(A & B) === A && A !== B` | */ export function strictlyContains(aBits, bBits) { return ((aBits & bBits) === bBits) && aBits !== bBits; } /** Is at least one letter in A missing from B? | `A ⊄ B` | boolean | Not Subset | `(A & B) !== A` | */ export function aHasMissingFromB(aBits, bBits) { return (aBits & bBits) !== aBits; } /** Which letters are *not* in A? | `¬A` | Wordbits | Complement | `~A & WordbitMask` | */ export function missingFrom(aBits) { return ~aBits & WordbitsMask; } /** @returns the wordbit mask for a single letter */ export function wordbitForLtr(ltr) { return WordbitsTables.ltrToWordbitsTable[ltr]; } // how this works; using Little-endian for sanity, we have: // | | | | // wb * x15 = 000000000000001000000000000001000000000000001000000000000001 // 0nmlkjihgfedcba0nmlkjihgfedcba0nmlkjihgfedcba0nmlkjihgfedcba // multiplying by m15 puts a new copy every 15 bits // r2 & m15 = 000100010001000100010001000100010001000100010001000100010001 // this is 1/15 in the same way 1/9 = 0.11111... // l h d 0 k g c n j f b m i e a // mask picks out each letter once // 12 8 4 15 11 7 3 14 10 6 2 13 9 5 1 // the modulo 15 sums each nybble (4-bits) -- don't understand why yet but it's to do with it being remainder after division by 15 // 1/15 = 0.1000100010001000100010001000100010001000100010001000100010001 // % 15 sums the nybbles // // Javascript does bitwise operations on 32-bit numbers, // We can do it with a safe int if we only accept 7-bit words // 00010001000100010001000100010001 /// gfedcbagfedcbagfedcbagfedcba // 00010001000100010001000100010001 // d g c f b e a // const MAGIC_NUMBER14x2 = 0x2000_4000_8001n // const MAGIC_NUMBER14m2 = 0x0111_1111_1111_1111n const MAGIC_NUMBER14r = 15n; // 45| 30| 15| 0| // | 15 | 15 | 15 | const MAGIC_NUMBER14x = 35185445863425n; // 0x 2 0 0 0 4 0 0 0 8 0 0 1 // 0x 0____1____1____1____1____1____1____1____1____1____1____1____1____1____1____1 const MAGIC_NUMBER14m = 76861433640456465n; // |9 | 16 | 16 | 16 | // 1. The multiply step repeats the bitfield four times, shifted by 2^15 // (the fourteen, and an extra one each time). // That is, bit 0 will land at bits 3, 2, 1, 0 in their nybble. // 2. The AND masks out all but the bottom bit in each nybble // 3. The modulo 15 "sums" the nybbles // We need to use a bigint to avoid overflow for over 8 bits // export function countBits14Magic(wordbits) { // mult by x, mask by m, read by r return Number((((BigInt(wordbits) * MAGIC_NUMBER14x) & MAGIC_NUMBER14m) % MAGIC_NUMBER14r)); } // gfedcbAgfedcbAgfedcbAgfedcbA const MAGIC_NUMBER13xo = 0b000000001000000100000010000001; // d g c f b e a const MAGIC_NUMBER13mo = 0b00010001000100010001000100010001; const MAGIC_NUMBER13ro = 0b1111; /** count bits in a 7-bit number */ export function countBits7Magic(wordbitso) { return (wordbitso * MAGIC_NUMBER13xo & MAGIC_NUMBER13mo) % MAGIC_NUMBER13ro; } const MAGIC_NYBBLE_MASK = 0b00010001000100010001000100010001; // FEDCBAzyxwvutsrqponmlkjihgfedcba // mod 15 folds the nybbles into 4-bit numbers // C___y___u___q___m___i___e___a // D___z___v___r___n___j___f___b // E___A___w___s___o___k___g___c // F___B___x___t___p___l___h___d /** count bits in a 32-bit number */ export function countBits32(wordbitso) { const bits1 = (wordbitso & MAGIC_NYBBLE_MASK); // mod 15 folds the nybbles into 4-bit numbers const bits2 = ((wordbitso >> 1) & MAGIC_NYBBLE_MASK); const bits3 = ((wordbitso >> 2) & MAGIC_NYBBLE_MASK); const bits4 = ((wordbitso >> 3) & MAGIC_NYBBLE_MASK); // console.log(prettyBinaries([wordbitso, bits1, bits2, bits3, bits4]), (bits1 % 15), (bits2 % 15), (bits3 % 15), (bits4 % 15), (bits1 % 15) + (bits2 % 15) + (bits3 % 15) + (bits4% 15)) return ((bits1 % 15) + (bits2 % 15) + (bits3 % 15) + (bits4 % 15)); } /** @returns the number of bits set in the bitfield */ export function countUniqLtrs(wordbits) { return countBits32(wordbits); } export const countBits28 = countUniqLtrs; // == [Convert strings to/from wordbits] /** @returns a string of only lowercase letters; given nil, returns '' */ export function normalizeWord(str) { return str?.toLowerCase().replace(/[^a-z]/g, '') ?? ''; } /** @returns the wordbits encoding for a word: * bit 0 (least significant / rightmost) is set if the word has an 'a', * bit 1 is set if the word has a 'b', etc. * ... * bit 25 (most significant / leftmost) is set if the word has a 'z' */ export function wordbitsForWord(word) { let bitfield = 0; for (const ltr of word) { bitfield |= WordbitsTables.ltrToWordbitsTable[ltr]; } return bitfield; } /** @returns the wordbits encoding for a word. @see {wordbitsForWord} */ export function wordbitsForWordSafe(str) { return wordbitsForWord(normalizeWord(str)); } export function missingWordbits(wordbits) { return ~wordbits & BITS_SMALLEST_26; } /** ROT-13: trivially obscure/decode a string: a->n, b->o, ..., m->z, n->a, ..., z->m. * Installation is the reverse of removal. * @see {UF.rot13Word} */ export function rot13Wordbits(wordbits) { return ((wordbits << 13) | (wordbits >> 13)) & WordbitsMask; } /** ROT-n: trivially obscure/decode a string: a->n, b->o, ..., m->z, n->a, ..., z->m. * Installation is the reverse of removal. * @see {rotNWord} */ export function rotNWordbits(wordbits, by) { // console.log(by, prettyBinaries([wordbits, (wordbits >> (26 - by)), ((wordbits << by) & WordbitsMask)], 28)) return ((wordbits >> (26 - by)) | ((wordbits << by) & WordbitsMask)) & WordbitsMask; } /** @returns the unique letters in the word, in alphabetical order */ export function ltrsForWordbits(wordbits) { const bits00_06 = wordbits & 0b111_1111; const bits07_13 = (wordbits >> 7) & 0b111_1111; const bits14_20 = (wordbits >> 14) & 0b111_1111; const bits21_25 = (wordbits >> 21) & 0b1_1111; const uniqs = (WordbitsTables.abcdefgFor00_06[bits00_06] + WordbitsTables.hijklmnFor07_13[bits07_13] + WordbitsTables.opqrstuFor14_20[bits14_20] + WordbitsTables.vwxyzFor21_25[bits21_25]); return uniqs; } /** @returns a fixed-width string of the wordbits, eg 0b00_0000_0100_1001_0001_1000_0100 for 'chimps' */ export function prettyWordbits(wordbits) { const bit00to07 = wordbits & BITS_SMALLEST_8; const bit08to15 = (wordbits >> 8) & BITS_SMALLEST_8; const bit16to23 = (wordbits >> 16) & BITS_SMALLEST_8; const bit24to27 = (wordbits >> 24) & BITS_SMALLEST_2; return '0b' + [ WordbitsTables.prettyBinaryTable2[bit24to27], WordbitsTables.prettyBinaryTable8[bit16to23], WordbitsTables.prettyBinaryTable8[bit08to15], WordbitsTables.prettyBinaryTable8[bit00to07], ].join('_'); // return WordbitsTables.prettyBinaryTable26[wordbits] } export function prettyBinary53(val, num = 53) { return '0b' + (_.chunk(_.padStart(val.toString(2), 56, '0').slice(56 - num).split(''), 4).map((nibbles) => nibbles.join('')).join('_')); } export function prettyBinary32(val) { return prettyBinary53(val, 32); } export function prettyBinaries(vals, num) { return '\n' + vals.map((val) => prettyBinary53(Number(val), num)).join('\n'); } /** @returns {DigestedWord} a summary of the wordbits encoding for a word * @see {DigestedWord} */ export function digestWord(word) { let wordbits = 0; let dupearr = []; let uniqarr = []; const ltrs = word.split(''); const beg = ltrs[0]; const end = ltrs[ltrs.length - 1]; ltrs.sort(); for (const ltr of ltrs) { const ltrmask = WordbitsTables.ltrToWordbitsTable[ltr]; if (wordbits & ltrmask) { dupearr.push(ltr); continue; } uniqarr.push(ltr); wordbits |= ltrmask; } const missbits = (~wordbits & BITS_SMALLEST_26); const begbit = WordbitsTables.ltrToWordbitsTable[beg]; const endbit = WordbitsTables.ltrToWordbitsTable[end]; // const headbits = wordbits & ~endbits & BITS_SMALLEST_12 // const tailbits = wordbits & ~begbits & BITS_SMALLEST_12 // const midbits = headbits & ~begbits & BITS_SMALLEST_12 return { word, ltrs, uniqarr, dupearr, beg, end, wordbits, missbits, begbit, endbit }; } // export function wayDigestGameword(word: TY.Word, gamebitsTable: TY.GamebitsTable): WayDigestedWord { // const gameltrs = _.keys(gamebitsTable) as readonly TY.Letter[] // const digested = digestGameword(word, gamebitsTable) // const { uniqarr, dupearr, gamebits, missbits, midbits, headbits, tailbits } = digested // return { // ...digested, // uniqstr: uniqarr.join(''), // uniqstr: ltrsForGamebits(gamebits, gameltrs).join(''), // dupestr: dupearr.join(''), // missstr: ltrsForGamebits(missbits, gameltrs).join(''), // headstr: ltrsForGamebits(headbits, gameltrs).join(''), // midstr: ltrsForGamebits(midbits, gameltrs).join(''), // tailstr: ltrsForGamebits(tailbits, gameltrs).join(''), // } // } //# sourceMappingURL=Wordbits.js.map