├── .clang-format ├── .gitignore ├── CHANGELOG.md ├── README.md ├── UNLICENSE ├── dist ├── mudder.cjs ├── mudder.min.js ├── mudder.min.js.map ├── mudder.min.mjs └── mudder.min.mjs.map ├── esbuild.mjs ├── index.js ├── jayne.jpg ├── package-lock.json ├── package.json ├── tangle.js └── test └── mudder-test.js /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: JavaScript 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: true 13 | AllowShortCaseLabelsOnASingleLine: true 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: true 16 | AllowShortLoopsOnASingleLine: true 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: MultiLine 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Attach 41 | BreakBeforeInheritanceComma: false 42 | BreakInheritanceList: BeforeColon 43 | BreakBeforeTernaryOperators: true 44 | BreakConstructorInitializersBeforeComma: false 45 | BreakConstructorInitializers: BeforeColon 46 | BreakAfterJavaFieldAnnotations: false 47 | BreakStringLiterals: true 48 | ColumnLimit: 120 49 | CommentPragmas: '^ IWYU pragma:' 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 52 | ConstructorInitializerIndentWidth: 4 53 | ContinuationIndentWidth: 4 54 | Cpp11BracedListStyle: true 55 | DerivePointerAlignment: false 56 | DisableFormat: false 57 | ExperimentalAutoDetectBinPacking: false 58 | FixNamespaceComments: true 59 | ForEachMacros: 60 | - foreach 61 | - Q_FOREACH 62 | - BOOST_FOREACH 63 | IncludeBlocks: Preserve 64 | IncludeCategories: 65 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 66 | Priority: 2 67 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 68 | Priority: 3 69 | - Regex: '.*' 70 | Priority: 1 71 | IncludeIsMainRegex: '(Test)?$' 72 | IndentCaseLabels: false 73 | IndentPPDirectives: None 74 | IndentWidth: 2 75 | IndentWrappedFunctionNames: false 76 | JavaScriptQuotes: Leave 77 | JavaScriptWrapImports: true 78 | KeepEmptyLinesAtTheStartOfBlocks: true 79 | MacroBlockBegin: '' 80 | MacroBlockEnd: '' 81 | MaxEmptyLinesToKeep: 1 82 | NamespaceIndentation: None 83 | ObjCBinPackProtocolList: Auto 84 | ObjCBlockIndentWidth: 2 85 | ObjCSpaceAfterProperty: false 86 | ObjCSpaceBeforeProtocolList: true 87 | PenaltyBreakAssignment: 2 88 | PenaltyBreakBeforeFirstCallParameter: 19 89 | PenaltyBreakComment: 300 90 | PenaltyBreakFirstLessLess: 120 91 | PenaltyBreakString: 1000 92 | PenaltyBreakTemplateDeclaration: 10 93 | PenaltyExcessCharacter: 1000000 94 | PenaltyReturnTypeOnItsOwnLine: 60 95 | PointerAlignment: Right 96 | ReflowComments: true 97 | SortIncludes: true 98 | SortUsingDeclarations: true 99 | SpaceAfterCStyleCast: false 100 | SpaceAfterTemplateKeyword: true 101 | SpaceBeforeAssignmentOperators: true 102 | SpaceBeforeCpp11BracedList: false 103 | SpaceBeforeCtorInitializerColon: true 104 | SpaceBeforeInheritanceColon: true 105 | SpaceBeforeParens: ControlStatements 106 | SpaceBeforeRangeBasedForLoopColon: true 107 | SpaceInEmptyParentheses: false 108 | SpacesBeforeTrailingComments: 1 109 | SpacesInAngles: false 110 | SpacesInContainerLiterals: false 111 | SpacesInCStyleCastParentheses: false 112 | SpacesInParentheses: false 113 | SpacesInSquareBrackets: false 114 | Standard: Cpp11 115 | StatementMacros: 116 | - Q_UNUSED 117 | - QT_REQUIRE_VERSION 118 | TabWidth: 8 119 | UseTab: Never 120 | ... 121 | 122 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage 22 | # (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directories 32 | node_modules 33 | jspm_packages 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional REPL history 39 | .node_repl_history 40 | 41 | # Output 42 | 43 | yarn.lock 44 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | # 2.1 4 | 5 | New parameter `placesToKeep` per discussion in [#7](https://github.com/fasiha/mudderjs/issues/7#issuecomment-1132602577). Many thanks to @pie6k for patiently explaining the problem to me. 6 | 7 | ## 2.0 8 | 9 | Build ESM, CommonJS, and IIFE modules 10 | 11 | ## 1.1.0 and 1.1.2 12 | 13 | Fixes [#16](https://github.com/fasiha/mudderjs/issues/16) so when you ask for 0 strings, you get 0 strings. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mudder.js 2 | 3 | Generate lexicographically-spaced strings between two strings from pre-defined alphabets. 4 | 5 | The name came to me while I was writing an early version that called the central function “midder” (i.e., the mid-point between strings), which I took to calling “mudder” from the memorable episode of *Firefly* called “Jaynestown”. 6 | 7 | ![Jayne, confused, stands in front of a mud statue of himself](jayne.jpg) 8 | 9 | ## Quickstart 10 | 11 | **Node.js** `npm install --save mudder`, then `var mudder = require('mudder')`. 12 | 13 | **Browser** Download [`mudder.min.js`](dist/mudder.min.js), then include it in your HTML: ``. This loads the `mudder` object into the browser’s global namespace. 14 | 15 | **TypeScript** The community maintains type definitions for this JavaScript library on [DefinitelyTyped](https://www.npmjs.com/package/@types/mudder). To get them, run `npm install --save-dev @types/mudder` (after first installing the JavaScript package, with `npm install --save mudder`), then import as usual—for example, `import {SymbolTable, base62} from 'mudder';`. 16 | 17 | **Example usage** Three symbol tables are pre-generated for your convenience: 18 | - `base62`: `0-9A-Za-z`, 19 | - `base36`: `0-9a-z` (lower- and upper-case accepted, to match `Number.toString`), 20 | - `alphabet`: `a-z` (lower- and upper-case accepted). 21 | 22 | Or you may create a new symbol table with the list of characters you want to use. In this example, we consider lowercase hexadecimal strings, and then ask for three strings between `ffff` and `fe0f`: 23 | ```js 24 | var mudder = require('mudder'); // only in Node 25 | var hex = new mudder.SymbolTable('0123456789abcdef'); 26 | var hexstrings = hex.mudder('ffff', 'fe0f', 3); 27 | console.log(hexstrings); 28 | // [ 'ff8', 'ff', 'fe8' ] 29 | ``` 30 | The three strings are guaranteed to be the shortest and as-close-to-evenly-spaced between the two original strings (`ffff` and `fe0f`, in this case) as possible. 31 | 32 | You may also omit the start and/or end strings, and provide only the number of strings to subdivide the entire string space. (Even this number may also be omitted if you just want one string in the middle of the string space.) 33 | ```js 34 | var mudder = require('mudder'); // only in Node 35 | var strings = mudder.base62.mudder(1000); 36 | console.log(strings); 37 | // [ '03', '07', '0B', ... 'zo', 'zs', 'zw' ] 38 | ``` 39 | 40 | **Ports** See [mudder_dart](https://pub.dev/packages/mudder_dart) for Dart/Flutter, contributed by [@tiagocpeixoto](https://github.com/tiagocpeixoto). 41 | 42 | **Try right now** My [blog post](https://fasiha.github.io/post/mudder/) lets you interactively experiment with this library. 43 | 44 | ## API 45 | 46 | ### Constructor 47 | 48 | `var m = new mudder.SymbolTable(string)` creates a new symbol table using the individual characters of `string`. 49 | 50 | `var m = new mudder.SymbolTable(symbolsArr)` uses the stringy elements of `symbolsArr` as allowed symbols. This way you can get fancy, i.e., Roman numerals, Emoji, Chinese phrases, etc. 51 | 52 | `var m = new mudder.SymbolTable(symbolsArr, symbolsMap)` allows the most flexibility in creating symbol tables. The stringy elements of `symbolsArr` are again the allowed symbols, while `symbolsMap` is a JavaScript object or [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map), whose keys must include all the strings in `symbolsArr` while the corresponding values must be JavaScript numbers, running from 0 up without skips. `symbolsMap` can contain keys *not* found in `symbolsArr`: this allows you to let multiple strings represent the same value, i.e., lower- and upper-case hexadecimal values. Mudder.js *outputs* will contain only the strings found in `symbolsArr` but it can *consume* strings containing anything found among the keys of `symbolsMap`. 53 | 54 | There are very few restrictions on what symbols the `SymbolTable` constructor accepts. The symbols are permitted to be non-[prefix-free](https://en.wikipedia.org/wiki/Prefix_code). In fact the library won’t object if you have repeated symbols in the table, though this makes very little sense. But in either of these cases, the `mudder` function (below) can only be invoked with arrays, not strings—i.e., you’ve parsed strings into symbols somehow yourself. 55 | 56 | ### Generate strings 57 | 58 | **`m.mudder(start = '', end = '' [, numStrings[, base[, numDivisions[, placesToKeep]]]])`** for strings, or array-of-strings, `start` and `end`, returns a `numStrings`-length (default one) array of strings. 59 | 60 | `base` is an integer defaulting to the size of the symbol table `m`, but can be less than it if you, for some reason, wish to use only a subset of the symbol table. 61 | 62 | `start` can be lexicographically less than or greater than `end`, but in either case, the returned array will be lexicographically sorted between them. 63 | 64 | If `start` or `end` are non-truthy, the first is replaced by the first symbol, and the second is replaced by repeating the final symbol several times—e.g., for a numeric symbol table, `start` would default to `0` and `end` to `999999` or similar. This is done so that the strings returned cover 99.99...% of the available string space. 65 | 66 | `numDivisions` defaults to `numStrings + 1` and must be greater than `numStrings`. It represents the number of pieces to subdivide the lexical space between `start` and `end` into—then the returned array will contain the first `numStrings` steps along that grid from `start` to `end`. You can customize `numDivisions` to be (much) larger than `numStrings` in cases where you know you are going to insert many strings between two endpoints, but only *one (or a few) at a time*. 67 | 68 | `placesToKeep` defaults to 0 and tells Mudder, instead of returning the shortest string(s) possible, to allow them to be up to (or greater than) this length. 69 | 70 | > For example, if you call `start = m.mudder(start, end, 1)[0]` over and over (overwriting `start` each iteration), you *halve* the space between the endpoints each call, eventually making the new string very long. If you *knew* you were going to do this, you can call `start = m.mudder(start, end, 1, undefined, 100)[0]`, i.e., set `numDivisons=100`, to subdivide the space between the endpoints a hundred times (instead of just two times), and return just the first 1/100th step from `start` to `end`. This makes your string length grow much more sedately, and you can always reverse `start` and `end` to get the same behavior going in the other direction. See [#7](https://github.com/fasiha/mudderjs/issues/7) for numerous examples, and a caveat if you’re using non-truthy `start`. 71 | 72 | > `placesTokeep` is useful when you want just one string back but also want to use a high `numDivisions`. For example, `base62.mudder('a', '0', 1, undefined, numDivisions)` is the same (`Z`) whether `numDivisions` is 100, 1000, or 10,000. By using a high `numDivisions` you probably don’t want this behavior: you probably want more dynamic range the more divisions you use. By passing in `placesToKeep`, you can ask Mudder to not aggressively truncate the strings it generates. With `placesToKeep=4`, the previous call returns `Zdg`, `Zxm`, and `ZzmA`. 73 | 74 | > If the symbol table was *not* prefix-free, the function will refuse to operate on *strings* `start`/`end` because, without the prefix-free criterion, a string can’t be parsed unambiguously: you have to split the string into an array of stringy symbols yourself. Invalid or unrecognized symbols are silently ignored. 75 | 76 | **`m.mudder(number = 1)`** is equivalent to `m.mudder('', '', number)`. See above. 77 | 78 | #### Recipes 79 | In this section we attempt to highlight when the library's default behavior is fine and when you might need to use which specific arguments described in the API above. 80 | 81 | **Abeni:** *“I am just starting my database and need ten keys for the first set of data; I expect future keys to be interspersed among this set.”* This is the most benign case. With a new dataset, you probably want compact keys, so I recommend `base62`: 82 | ```js 83 | var mudder = require('mudder'); // only in Node 84 | var keys = mudder.base62.mudder(10); 85 | console.log(keys); 86 | /* 87 | [ 88 | '5', 'B', 'G', 'M', 89 | 'S', 'X', 'd', 'j', 90 | 'o', 'u' 91 | ] 92 | */ 93 | ``` 94 | 95 | *Now I have my eleventh data point and need a key between the 4th and 5th.* 96 | ```js 97 | var newKey = mudder.base62.mudder(keys[3], keys[4]); 98 | console.log(newKey) 99 | // [ 'P' ] 100 | ``` 101 | 102 | *I need **two** keys between the first and the second.* 103 | ```js 104 | var newKeys = mudder.base62.mudder(keys[0], keys[1], 2); 105 | console.log(newKeys) 106 | // [ '7', '9' ] 107 | ``` 108 | In general, as Abeni’s database grows, a data point may come before the first: 109 | ```js 110 | console.log(mudder.base62.mudder(undefined, keys[0])) 111 | // [ '2' ] 112 | ``` 113 | or after the last: 114 | ```js 115 | console.log(mudder.base62.mudder(keys[keys.length-1])) 116 | // [ 'w' ] 117 | ``` 118 | but assuming random ordering, the keys will grow in length as needed. 119 | 120 | **Bolanle:** *I’m also starting my database and also need ten keys for my initial data, but I am confident all future data will come **after** the initial data.* Bolanle does not want her first ten keys to span the entire `base62` gamut from `0` to `z` because she is confident most of her future keys will need to go after the tenth key, and Abeni’s approach would squander the bulk of lexicographic space. 121 | 122 | A simple approach would be, generate 99 keys covering the whole `base62` key space and keep only the first ten needed: 123 | ```js 124 | var mudder = require('mudder'); // only in Node 125 | var keys = mudder.base62.mudder(99).slice(0, 10); 126 | console.log(keys); 127 | /* 128 | [ 129 | '0c', '1', '1r', 130 | '2', '3', '3i', 131 | '4', '4x', '5', 132 | '6' 133 | ] 134 | */ 135 | ``` 136 | or equivalently, use `numDivisions = 100` which does the same thing, just a more efficiently: split up the `base62` gamut into a hundred pieces and gets the first ten: 137 | ```js 138 | var numDivisions = 100; 139 | var keys = mudder.base62.mudder('', '', 10, undefined, numDivisions); 140 | console.log(keys); 141 | /* Same as above: 142 | [ 143 | '0c', '1', '1r', 144 | '2', '3', '3i', 145 | '4', '4x', '5', 146 | '6' 147 | ] 148 | */ 149 | ``` 150 | (Note how we can use `''` (the empty string) instead of `undefined` for the start and end.) 151 | 152 | *And what if all my future data is likely to come **before** my initial data? Can I use `numDivisions` to generate keys close to the **end** of `base62`?* Yes, though, because you want to go “backwards” now, you need to actually give the start and end: 153 | ```js 154 | console.log(mudder.base62.mudder('zzzzz', '0', 10, undefined, numDivisions)); 155 | /* 156 | [ 157 | 'z', 'yl', 'y', 158 | 'x', 'wt', 'w', 159 | 've', 'v', 'u', 160 | 't' 161 | ] 162 | */ 163 | ``` 164 | Running from `z` to `t` covers the same amount of “lexicographic space” as the previous example from `0` to `6`. 165 | 166 | The brute-force way to achieve this, by the way, is: 167 | ```js 168 | console.log(mudder.base62.mudder(undefined, undefined, 99).slice(-10)); 169 | /* 170 | [ 171 | 'tn', 'u', 'v', 172 | 've', 'w', 'wt', 173 | 'x', 'y', 'yl', 174 | 'z' 175 | ] 176 | */ 177 | ``` 178 | which is quite similar to the above, just reversed (because this is still going from low to high, i.e., the start of `base62` to its end) and with more letters for `t` because of “rounding”. 179 | 180 | **Chinonso** *I need to insert ten keys between `n` and `l`, and I want them to be close to `n` because I know that later I’ll need **thousands** more keys “before” these ten and I cannot afford any wasted space.* Unless she is ok with the brute-force approach of generating, say, 10,000 keys between `n` and `l` and picking the first ten, Chinonso needs the final argument, `placesToKeep`: 181 | ```js 182 | var mudder = require('mudder'); // only in Node 183 | var numDivisions = 10_000; 184 | var placesToKeep = 4; 185 | console.log(mudder.base62.mudder('n', 'l', 10, undefined, numDivisions, placesToKeep)); 186 | /* 187 | [ 188 | 'mzzE', 'mzyT', 189 | 'mzxh', 'mzwv', 190 | 'mzwA', 'mzvO', 191 | 'mzuc', 'mztr', 192 | 'mzt5', 'mzsJ' 193 | ] 194 | */ 195 | ``` 196 | Without `placesToKeep`, Mudder will prefer shorter keys, which Chinonso wants to explicitly avoid: 197 | ```js 198 | var numDivisions = 10_000; 199 | console.log(mudder.base62.mudder('n', 'l', 10, undefined, numDivisions)); // no placesToKeep 200 | /* 201 | [ 202 | 'mzz', 'mzy', 'mzx', 203 | 'mzwv', 'mzw', 'mzv', 204 | 'mzu', 'mzt', 'mz', 205 | 'm' 206 | ] 207 | */ 208 | ``` 209 | 210 | Note that Chinonso might not need `placesToKeep` if she needed to go from `l` to `n`: 211 | ```js 212 | var numDivisions = 10_000; 213 | console.log(mudder.base62.mudder('l', 'n', 10, undefined, numDivisions)); 214 | /* 215 | [ 216 | 'l00m', 'l01', 217 | 'l02', 'l03', 218 | 'l03q', 'l04', 219 | 'l05', 'l06', 220 | 'l06v', 'l07' 221 | ] 222 | */ 223 | ``` 224 | Although Mudder is trying to minimize key length here without `placesToKeep`, it needs to keep a fair number of digits going “forward” that it was able to “round off” when going “backwards”. 225 | 226 | ### For fun: string–number conversion 227 | 228 | `m.stringToNumber(string[, base])` returns the number encoded by `string` in our everyday base-10 number system using all or the first `base` symbols of the symbol table `m`. 229 | 230 | `m.numberToString(int[, base])` returns the string representing a positive integer `int` in the number system defined by the symbol table. By default, all symbols are used but fewer can be specified with `base`. 231 | 232 | ## Hacking this library 233 | 234 | This library is written as a literate document: in this `README.md`, prose explanations and code explanations surround the few bits of source code that actually make up the library. (Caveat: I had to rush near the end so the last few functions lack prose discussion—sorry! Todo!) Fenced code blocks that contain the string `< export FOO` are appended to the file `FOO`. 235 | 236 | The Markdown “literate source” `README.md` is “tangled” into actual source code by [`tangle.js`](tangle.js) and can be invoked by `yarn prebuild` (or `npm run prebuild`). 237 | 238 | This results in a [`index.js`](index.js). ESBuild bundles this into [various](https://github.com/fasiha/mudderjs/pull/18) modules in [`dist/`](./dist/) including ESM, CommonJS, and IIFE. This can be invoked by `yarn build` (or `npm run build`). 239 | 240 | ## Literate source 241 | 242 | **Requirement** A function that, given two strings, returns one or more strings lexicographically between them (see [Java’s `compareTo` docs](http://docs.oracle.com/javase/8/docs/api/java/lang/String.html#compareTo-java.lang.String-) for a cogent summary of lexicographical ordering). 243 | 244 | That is, if `c = lexmid(a, b, 1)` a string, then `a ≶ c ≶ b`. 245 | 246 | Similarly, if `cs = lexmid(a, b, N)` is an `N>1`-element array of strings, `a ≶ cs[0] ≶ cs[1] ≶ … ≶ cs[N-1] ≶ b`. 247 | 248 | **Use Case** Reliably ordering (or ranking) entries in a database. Such entries may be reordered (shuffled). New entries might be inserted between existing ones. 249 | 250 | [My StackOverflow question](http://stackoverflow.com/q/39125091/500207) links to six other questions about this topic, all lacking convincing solutions. Some try to encode order using floating-point numbers, but a pair of numbers can only be subdivided so many times before losing precision. One “fix” to this is to renormalize all entries’ spacings periodically, but some NoSQL databases lack atomic operations and cannot modify multiple entries in one go. Such databases would have to stop accepting new writes, update each entry with a normalized rank number, then resume accepting writes. 251 | 252 | Since many databases are happy to sort entries using stringy fields, let’s just use strings instead of numbers. This library aids in the creation of new strings that lexicographically sort between two other strings. 253 | 254 | (N.B. After writing this library, I discovered Joe Nelson’s blog post [“User-defined Order in SQL”](https://begriffs.com/posts/2018-03-20-user-defined-order.html) that offers a number of other solutions, as well as a cogent statement of the problem. Some of you may know Joe from his work on PostgREST.) 255 | 256 | **Desiderata** I’d like to be able to insert thousands of documents between adjacent ones, so `lexmid()` must never return strings which can’t be “subdivided” further. But memory isn’t free, so shorter strings are preferred. 257 | 258 | **Prior art** [@m69’s algorithm](http://stackoverflow.com/a/38927158/500207) is perfect: you give it two alphabetic strings containing just `a-z`, and you get back a short alphabetic string that’s “roughly half-way” between them. 259 | 260 | I asked how to get `N` evenly-spaced strings ex nihilo, i.e., not between any two strings. [@m69’s clever suggestion](http://stackoverflow.com/questions/38923376/return-a-new-string-that-sorts-between-two-given-strings/38927158#comment65638725_38927158) was, for strings allowed to use `B` distinct characters, and `B^(m-1) < N < B^m`, evenly distribute `N` integers from 2 to `B^m - 2` (or some suitable start and end), and write them as radix-`B`. 261 | 262 | This works! Here’s a quick example, generating 25 hexadcimal (`B=16`) strings: 263 | ~~~js 264 | var N = 25; // How many strings to generate. Governs how long the strings are. 265 | var B = 16; // Radix, or how many characters to use, < N 266 | 267 | // Left and right margins 268 | var start = 2; 269 | var places = Math.ceil(Math.log(N) / Math.log(B)); // max length for N strings 270 | var end = Math.pow(B, places) - 2; 271 | 272 | // N integers between `start` and `end` 273 | var ns = Array.from(Array(N), (_, i) => start + Math.round(i / N * end)); 274 | 275 | // JavaScript's toString can't pad numbers to a fixed length, so: 276 | var leftpad = (str, desiredLen, padChar) => 277 | padChar.repeat(desiredLen - str.length) + str; 278 | 279 | var strings = ns.map(n => leftpad(n.toString(B), places, '0')); 280 | console.log(strings); 281 | // > [ '02', 282 | // > '0c', 283 | // > '16', 284 | // > '20', 285 | // > '2b', 286 | // > '35', 287 | // > '3f', 288 | // > '49', 289 | // > '53', 290 | // > '5d', 291 | // > '68', 292 | // > '72', 293 | // > '7c', 294 | // > '86', 295 | // > '90', 296 | // > '9a', 297 | // > 'a5', 298 | // > 'af', 299 | // > 'b9', 300 | // > 'c3', 301 | // > 'cd', 302 | // > 'd7', 303 | // > 'e2', 304 | // > 'ec', 305 | // > 'f6' ] 306 | ~~~ 307 | 308 | This uses JavaScript’s `Number.prototype.toString` which works for bases up to `B=36`, but no more. A desire to use more than thirty-six characters led to [a discussion about representing integers in base-62](http://stackoverflow.com/a/2557508/500207), where @DanielVassallo showed a custom `toString`. 309 | 310 | Meanwhile, [numbase](https://www.npmjs.com/package/numbase) supports arbitrary-radix interconversion, and, how delightful, lets you specify the universe of characters to use: 311 | ```js 312 | // From https://www.npmjs.com/package/numbase#examples // no-hydrogen 313 | // Setup an instance with custom base string 314 | base = new NumBase('中国上海市徐汇区'); 315 | // Encode an integer, use default radix 8 316 | base.encode(19901230); // returns '国国海区上徐市徐汇' 317 | // Decode a string, with default radix 8 318 | base.decode('国国海区上徐市徐汇'); // returns '19901230' 319 | ``` 320 | 321 | Finally, [@Eclipse’s observation](http://stackoverflow.com/a/2510928/500207) really elucidated the connection between strings and numbers, and explaining the mathematical vein that @m69’s algorithm was ad hocly tapping. @Eclipse suggested converting a string to a number and then *treating the result as a fraction between 0 and 1*. That is, just place a radix-point before the first digit (in the given base) and perform arithmetic on it. (In this document, I use “radix” and “base” interchangeably.) 322 | 323 | **Innovations** Mudder.js (this dependency-free JavaScript/ES2015 library) is a generalization of @m69’s algorithm. It operates on strings containing *arbitrary substrings* instead of just lowercase `a-z` characters: your strings can contain, e.g., 日本語 characters or 🔥 emoji. (Like @m69’s algorithm, you do have to specify upfront the universe of stringy symbols to operate on.) 324 | 325 | You can ask Mudder.js for `N ≥ 1` strings that sort between two input strings. These strings will be as short as possible. 326 | 327 | These are possible because Mudder.js converts strings to non-decimal-radix (non-base-10), arbitrary-precision fractional numbers between 0 and 1. Having obtained numeric representations of strings, it’s straightforward to compute their average, or midpoint, `(a + b) / 2`, or even `N` intermediate points `a + (b - a) / N * i` for `i` going from 1 to `N - 1`, using the long addition and long division you learned in primary school. (By avoiding native floating-point, Mudder.js can handle arbitrarily-long strings, and generalizes @Eclipse’s suggestion.) 328 | 329 | Because `numbase` made it look so fun, as a bonus, Mudder.js can convert regular JavaScript integers to strings. You may specify a multi-character string for each digit. Therefore, should the gastronome in you invent a ternary (radix-3) numerical system based on today’s meals, with 0=🍌🍳☕️, 1=🍱, and 2=🍣🍮, Mudder.js can help you rewrite (42)10, that is, 42 in our everyday base 10, as (🍱🍱🍣🍮🍌🍳☕️)breakfast, lunch, and dinner. 330 | 331 | **This document** This document is a Markdown file that I edit in Atom. It contains code blocks that I can run in Node.js via Hydrogen, an Atom plugin that talks to Node over the Jupyter protocol (formerly IPython Notebook). I don’t have a terminal window open: all development, including scratch work and test snippets, happens in this Markdown file and goes through Hydrogen. 332 | 333 | This workflow allows this document to be a heavily-edited diary of writing the library. You, the reader, can see not just the final code but also the experimentation, design choices and decisions and alternatives, opportunities for improvement, references, and asides. 334 | 335 | The result of evaluating all code blocks is included at the bottom of each code block (using custom Atom [plugins](https://github.com/fasiha/atom-papyrus-sedge)). 336 | 337 | Furthermore, all source code files and configuration files included in this repository are derived from code blocks in *this* Markdown file. This is done by another plugin that goes through this document and pipes code blocks to external files. 338 | 339 | In this way, this document is a primitive (or futuristic?) riff on literate programming, the approach discovered and celebrated by Donald Knuth. 340 | 341 | Besides reading this document passively on GitHub or npmjs.org, you will eventually be able to read it as an interactive, live-coding webapp, where each code block is editable and executable in your browser. This in turn makes it a riff on [Alan Kay’s vision](http://blog.klipse.tech/javascript/2016/06/20/blog-javascript.html) for interactive programming environments for *readers* as well as writers. 342 | 343 | ## Plan 344 | Since we must convert strings to arbitrary-radix digits, and back again, this library includes enhanced versions of 345 | 346 | - [`Number.prototype.toString`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString) which converts JavaScript integers to strings for bases between base-2 (binary) and base-36 (alphanumeric), 347 | - [`parseInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt) which inverts this operation by converting a string of digits in some base between 2 and 36 to a JavaScript number. 348 | 349 | Specifically, we need versions of these functions that operate on bases >36, and that let the user specify the strings used to denote each digit in the arbitrary-radix numerical system. (Recall that I use “base” and “radix” interchangeably.) 350 | 351 | We will create these library functions in the next section. 352 | 353 | Once we can represent arbitrary strings as equivalent numbers, we will describe the very specific positional number system that lets us find lexicographic mid-points easily. This positional system involves mapping a given string’s numeric representation to a rational number between 0 and 1, and in this system, the lexicographic midpoint between two strings is the simple mean (average) between their two numbers. 354 | 355 | This sounds fancy, but again, it’s quite pedestrian. We’ll implement long addition and long division (the two steps required to calculate the mean of two numbers) in a subsequent section. 356 | 357 | Finally, with these preliminaries out of the way, we’ll implement the functions to help us paper over all this machinery and that just give us strings lexicographically between two other strings. 358 | 359 | ## Symbol tables, numbers, and digits 360 | Let us remind ourselves what `toString` and `parseInt` do: 361 | ~~~js 362 | console.log([ (200).toString(2), parseInt('11001000', 2) ]) 363 | console.log([ (200).toString(36), parseInt('5K', 36) ]) 364 | // > [ '11001000', 200 ] 365 | // > [ '5k', 200 ] 366 | // > undefined 367 | ~~~ 368 | (200)10 = (1100 1000)2 = (5K)36. One underlying number, many different representations. Each of these is a positional number system with a different base: base-10 is our everyday decimal system, base-2 is the binary system our computers operate on, and base-36 is an uncommon but valid alphanumeric system. 369 | 370 | Recall from grade school that this way of writing numbers, as (digit 1, digit 2, digit 3)base=B, means each digit is a multiple of `Math.pow(B, i)` where `i=0` for the right-most digit (in the ones place), and going up for each digit to its left. 371 | ~~~js 372 | var dec = 2 * 100 + // 100 = Math.pow(10, 2) 373 | 0 * 10 + // 10 = Math.pow(10, 1) 374 | 0 * 1; // 1 = Math.pow(10, 0) 375 | 376 | var bin = 1 * 128 + // 128 = Math.pow(2, 7) 377 | 1 * 64 + // 64 = Math.pow(2, 6) 378 | 0 * 32 + // 32 = Math.pow(2, 5) 379 | 0 * 16 + // 16 = Math.pow(2, 4) 380 | 1 * 8 + // 8 = Math.pow(2, 3) 381 | 0 * 4 + // 4 = Math.pow(2, 2) 382 | 0 * 2 + // 2 = Math.pow(2, 1) 383 | 0 * 1; // 1 = Math.pow(2, 0) 384 | 385 | var aln = 5 * 36 + // 36 = Math.pow(36, 1) 386 | 20 * 1; // 1 = Math.pow(36, 0) 387 | 388 | console.log(dec === bin && dec === aln ? 'All same!' : 'all NOT same?'); 389 | // > All same! 390 | ~~~ 391 | That last example and its use of `K` as a digit might seem strange, but for bases >10, people just use letters instead of numbers. `A=10`, `F=15`, `K=20`, and `Z=36` using this convention. 392 | 393 | Both these functions operate on what we’ll call a *symbol table*: a mapping between stringy symbols and the numbers from 0 to one less than the maximum base. Here’s the symbol table underlying `parseInt` and `Number.prototype.toString`, with stringy symbols on the left and numbers on the right: 394 | 395 | - `0` ⇔ 0 396 | - `1` ⇔ 1 397 | - `2` ⇔ 2 398 | - ⋮ 399 | - `8` ⇔ 8 400 | - `9` ⇔ 9 401 | - `a` ⇔ 10 402 | - `A` ⇒ 10 403 | - ⋮ 404 | - `z` ⇔ 35 405 | - `Z` ⇒ 35 406 | 407 | (Aside: `parseInt` accepts uppercase letters, treating them as lowercase. `Number.prototype.toString` outputs only lowercase letters. Therefore, uppercase letters above have a right-arrow, instead of bidirectional.) 408 | 409 | For both the broader problem of lexicographically interior strings, as well as the sub-problem of converting between numbers and strings, we want to specify our own symbol tables. Here are a few ways we’d like to handle, in order of increasing complexity and flexibility: 410 | 411 | 1. **a string** Great for those simple use-cases: the string is `split` into individual characters, and each character is the symbol for its index number. Such a symbol table can handle bases as high as the number of characters in the input string. Example: `new SymbolTable('abcd')`. 412 | 1. **an array of strings** To specify multi-character symbols such as emoji (which `String.split` will butcher), or whole words. Quaternary (radix-4) Roman-numeral example: `new SymbolTable('_,I,II,III'.split(','))`. 413 | 1. **an array of strings, _plus_ a map of stringy symbols to numbers** This would let us specify fully-generic symbol tables like `parseInt`’s, where both `'F'` and `'f'` correspond to 15. The array uniquely sends numbers to strings, and the map sends ≥1 strings to numbers. The quaternary Roman-numeral example capable of ingesting lower-case letters: 414 | ~~~js 415 | new SymbolTable('_,I,II,III'.split(','), new Map([ 416 | [ '_', 0 ], // zero 417 | [ 'I', 1 ], [ 'i', 1 ], // 1, lower AND upper case! 418 | [ 'II', 2 ], [ 'ii', 2 ], // 2 419 | [ 'III', 3 ], [ 'iii', 3 ] // 3 420 | ])); 421 | // no-hydrogen 422 | ~~~ 423 | 424 | Let’s resist the temptation to be avant-garde: let’s agree that, to be valid, a symbol table must include symbols for *all* numbers between 0 and some maximum, with none skipped. `B` (for “base”) unique numbers lets the symbol table define number systems between radix-2 (binary) up to radix-`B`. JavaScript’s implicit symbol table handles `B≤36`, but as the examples above show, we don’t have to be restricted to base-36. 425 | 426 | (Aside: radix-36 doesn’t seem to have a fancy name like the ancient Sumerians’ radix-60 “sexagesimal” system so I call it “alphanumeric”.) 427 | 428 | (Aside²: While Sumerian and Babylonian scribes no doubt had astounding skills, they didn’t keep track of *sixty* unique symbols. Not even *fifty-nine*, since they lacked zero. Just two: “Y” for one and “<” for ten. So 𒐘 was four and 𒐏 forty, so forty-four might be Unicodized as 𒐏𒐘?) 429 | 430 | We will indulge the postmodern in one way: we’ll allow symbol tables that are no lexicographically-sorted. That is, the number systems we define are allowed to flout the conventions of lexicographical ordering, in which case interior strings produced by Mudder.js won’t sort. I can’t think of a case where this would be actually useful, instead of just playful, so if you think this should be banned, get in touch, but for now, caveat emptor. 431 | 432 | ### Some prefix problems 433 | 434 | The discussion of the Roman numeral system reminds me of a subtle but important point. If `i`, `ii`, and `iii` are all valid symbols, how on earth can we tell if (iii)Roman quaternary is 435 | 436 | - (3)4 = (3)10, 437 | - (12)4 = (6)10, 438 | - (21)4 = (9)10, or 439 | - (111)4 = (21)10? 440 | 441 | We can’t. We cannot parse strings like `iii`, not without punctuation like spaces which splits a string into an array individual symbols. 442 | 443 | At this stage one might recall reading about Huffman coding, or Dr El Gamal’s lecture on [prefix codes](https://en.wikipedia.org/wiki/Prefix_code) in information theory class. In a nutshell, a set of strings has the prefix property, or is prefix-free, if no string starts with another string—if no set member is *prefixed* by another set member. 444 | 445 | I’ve decided to allow Mudder.js to parse raw strings only if the symbol table is prefix-free. If it is *not* prefix-free, then Mudder.js’s version of `parseInt` will throw an exception if fed a string—you must pass it an array, having resolved the ambiguity yourself, using punctuation perhaps. 446 | 447 | #### Code to detect the prefix property 448 | So, let’s write a dumb way to decide if a set or array of stringy symbols constitutes a prefix code. If any symbol is a prefix of another symbol (other than itself of course), the symbol table **isn’t** prefix-free, and we don’t have a prefix code. 449 | ~~~js 450 | function isPrefixCode(strings) { 451 | // Note: we skip checking for prefixness if two symbols are equal to each 452 | // other. This implies that repeated symbols in the input are *silently 453 | // ignored*! 454 | for (const i of strings) { 455 | for (const j of strings) { 456 | if (j === i) { // [🍅] 457 | continue; 458 | } 459 | if (i.startsWith(j)) { 460 | return false; 461 | } 462 | } 463 | } 464 | return true; 465 | } 466 | // < export mudder.js 467 | ~~~ 468 | As with most mundane-seeming things, there’s some subtlety here. Do you see how, at `[🍅]` above, we skip comparing the same strings?—that part’s not tricky, that’s absolutely needed. But because of this, if the input set contains *repeats*, this function will implicitly treat those repeats as the *same* symbol. 469 | ~~~js 470 | console.log(isPrefixCode('a,b,b,b,b,b'.split(',')) ? 'is prefix code!' 471 | : 'NOT PREFIX CODE 😷'); 472 | // > is prefix code! 473 | ~~~ 474 | One alternative might be to throw an exception upon detecting repeat symbols. Or: instead of comparing the strings themselves at `[🍅]`, compare indexes—this will declare sets with repeats as non-prefix-free, but that would imply that there was some sense in treating `'b'` and `'b'` as different numbers. 475 | 476 | So the design decision here is that `isPrefixFree` ignores repeated symbols in its calculation, and assumes repeats are somehow dealt with downstream. Please write if this is the wrong decision. 477 | 478 | Making sure it works: 479 | ~~~js 480 | console.log(isPrefixCode('a,b,c'.split(','))); 481 | console.log(isPrefixCode('a,b,bc'.split(','))); 482 | // > true 483 | // > false 484 | ~~~ 485 | 486 | #### A faster `isPrefixCode` 487 | 488 | But wait! This nested-loop has quadratic runtime, with `N*N` string equality checks and nearly `N*N` `startsWith()`s, for an `N`-element input. Can’t this be recast as an `N*log(N)` operation, by first *sorting* the stringy symbols lexicographically (`N*log(N)` runtime), and then looping through once to check `startsWith`? Try this: 489 | ~~~js 490 | function isPrefixCodeLogLinear(strings) { 491 | strings = Array.from(strings).sort(); // set->array or array->copy 492 | for (const [i, curr] of strings.entries()) { 493 | const prev = strings[i - 1]; // undefined for first iteration 494 | if (prev === curr) { // Skip repeated entries, match quadratic API 495 | continue; 496 | } 497 | if (curr.startsWith(prev)) { // str.startsWith(undefined) always false 498 | return false; 499 | }; 500 | } 501 | return true; 502 | } 503 | // < export mudder.js 504 | ~~~ 505 | It was a bit of wild intuition to try this, but it has been confirmed to work: proof by internet, courtesy of [@KWillets on Computer Science StackExchange](http://cs.stackexchange.com/a/63313/8216). 506 | 507 | To see why this works, recall that in [lexicographical ordering](http://docs.oracle.com/javase/8/docs/api/java/lang/String.html#compareTo-java.lang.String-), `'abc' < 'abc+anything else'`. The only way for a string to sort between a string `s` and `s + anotherString` is to be prefixed by `s` itself. This guarantees that prefixes sort adjacent to prefixeds. 508 | 509 | (Aside: note that we’re use a lexicographical sort here just to find prefixes—our underlying symbol tables are allowed to be postmodern and lexicographically shuffled.) 510 | 511 | But is it faster? Let’s test it on a big set of random numbers, which should be prefix-free so neither algorithm bails early: 512 | ~~~js 513 | test = Array.from(Array(1000), () => '' + Math.random()); 514 | console.time('quad'); 515 | isPrefixCode(test); 516 | console.timeEnd('quad'); 517 | 518 | console.time('log'); 519 | isPrefixCodeLogLinear(test); 520 | console.timeEnd('log'); 521 | // > quad: 103.818ms 522 | // > log: 1.758ms 523 | ~~~ 524 | Yes indeed, the log-linear approach using a sort is maybe ~100× faster than the quadratic approach using a double-loop. So let’s use the faster one: 525 | ~~~js 526 | isPrefixCode = isPrefixCodeLogLinear; 527 | // < export mudder.js 528 | ~~~ 529 | 530 | ### Symbol table object constructor 531 | With this out of the way, let’s write our `SymbolTable` object. Recall from the examples above that it should take 532 | 533 | - a string or an array, which uniquely maps integers to strings—since an array element can contain only a single string!—and 534 | - optionally a map (literally a `Map`, or an object) from strings to numbers (many-to-one acceptable). 535 | 536 | If a string→number map is provided in addition to a `B`-length array, this map ought to be checked to ensure that its values include `B` numbers from 0 to `B - 1`. 537 | 538 | The symbol table should also remember if it’s prefix-free. If it is, parsing strings to numbers is doable. If not, strings must be split into an array of sub-strings first (using out-of-band, non-numeric punctuation, perhaps). 539 | 540 | Without further ado: 541 | ~~~js 542 | /* Constructor: 543 | symbolsArr is a string (split into an array) or an array. In either case, it 544 | maps numbers (array indexes) to stringy symbols. Its length defines the max 545 | radix the symbol table can handle. 546 | 547 | symbolsMap is optional, but goes the other way, so it can be an object or Map. 548 | Its keys are stringy symbols and its values are numbers. If omitted, the 549 | implied map goes from the indexes of symbolsArr to the symbols. 550 | 551 | When symbolsMap is provided, its values are checked to ensure that each number 552 | from 0 to max radix minus one is present. If you had a symbol as an entry in 553 | symbolsArr, then number->string would use that symbol, but the resulting 554 | string couldn't be parsed because that symbol wasn't in symbolMap. 555 | */ 556 | function SymbolTable(symbolsArr, symbolsMap) { 557 | 'use strict'; // [⛈] 558 | if (typeof this === 'undefined') { 559 | throw new TypeError('constructor called as a function') 560 | }; 561 | 562 | // Condition the input `symbolsArr` 563 | if (typeof symbolsArr === 'string') { 564 | symbolsArr = symbolsArr.split(''); 565 | } else if (!Array.isArray(symbolsArr)) { 566 | throw new TypeError('symbolsArr must be string or array'); 567 | } 568 | 569 | // Condition the second input, `symbolsMap`. If no symbolsMap passed in, make 570 | // it by inverting symbolsArr. If it's an object (and not a Map), convert its 571 | // own-properties to a Map. 572 | if (typeof symbolsMap === 'undefined') { 573 | symbolsMap = new Map(symbolsArr.map((str, idx) => [str, idx])); 574 | } else if (symbolsMap instanceof Object && !(symbolsMap instanceof Map)) { 575 | symbolsMap = new Map(Object.entries(symbolsMap)); 576 | } else if (!(symbolsMap instanceof Map) ){ 577 | throw new TypeError('symbolsMap can be omitted, a Map, or an Object'); 578 | } 579 | 580 | // Ensure that each integer from 0 to `symbolsArr.length - 1` is a value in 581 | // `symbolsMap` 582 | let symbolsValuesSet = new Set(symbolsMap.values()); 583 | for (let i = 0; i < symbolsArr.length; i++) { 584 | if (!symbolsValuesSet.has(i)) { 585 | throw new RangeError(symbolsArr.length + ' symbols given but ' + i + 586 | ' not found in symbol table'); 587 | } 588 | } 589 | 590 | this.num2sym = symbolsArr; 591 | this.sym2num = symbolsMap; 592 | this.maxBase = this.num2sym.length; 593 | this.isPrefixCode = isPrefixCode(symbolsArr); 594 | } 595 | // < export mudder.js 596 | ~~~ 597 | 598 | A programmatic note: around `[⛈]` we’re making sure that forgetting `new` when calling `SymbolTable` will throw an exception. It’s a simple solution to the [JavaScript constructor problem](http://raganwald.com/2014/07/09/javascript-constructor-problem.html#solution-kill-it-with-fire) 599 | 600 | Let’s make sure the constructor at least works: 601 | ~~~js 602 | var binary = new SymbolTable('01'); 603 | var meals = new SymbolTable('🍌🍳☕️,🍱,🍣🍮'.split(',')); 604 | var romanQuat = 605 | new SymbolTable('_,I,II,III'.split(','), 606 | {_ : 0, I : 1, i : 1, II : 2, ii : 2, III : 3, iii : 3}); 607 | console.log('Binary', binary); 608 | console.log('Meals', meals); 609 | console.log('Roman quaternary', romanQuat); 610 | // > Binary SymbolTable { 611 | // > num2sym: [ '0', '1' ], 612 | // > sym2num: Map { '0' => 0, '1' => 1 }, 613 | // > maxBase: 2, 614 | // > isPrefixCode: true } 615 | // > Meals SymbolTable { 616 | // > num2sym: [ '🍌🍳☕️', '🍱', '🍣🍮' ], 617 | // > sym2num: Map { '🍌🍳☕️' => 0, '🍱' => 1, '🍣🍮' => 2 }, 618 | // > maxBase: 3, 619 | // > isPrefixCode: true } 620 | // > Roman quaternary SymbolTable { 621 | // > num2sym: [ '_', 'I', 'II', 'III' ], 622 | // > sym2num: 623 | // > Map { 624 | // > '_' => 0, 625 | // > 'I' => 1, 626 | // > 'i' => 1, 627 | // > 'II' => 2, 628 | // > 'ii' => 2, 629 | // > 'III' => 3, 630 | // > 'iii' => 3 }, 631 | // > maxBase: 4, 632 | // > isPrefixCode: false } 633 | ~~~ 634 | A quick note: the quaternary Roman-numeral symbol table is indeed marked as a non-prefix-code. 635 | 636 | ### Conversion functions: numbers ↔︎ digits ↔︎ strings 637 | We need four converters, two for numbers ↔︎ digits and two more for digits ↔︎ strings. (By numbers, I always mean positive integers in this document.) Let’s write those functions, and it should become clear what role “digits” play in this whole story. 638 | 639 | Recall how, when we write “123”, we mean “1 * 100 + 2 * 10 + 3 * 1”. This is how positional number systems work. 640 | 641 | To get this breakdown for any given number in base `B`, we repeatedly divide the integer by `B` and peel off the remainder each time to be a digit, giving you its digits from left to right. Here’s the idea in code: 642 | ~~~js 643 | SymbolTable.prototype.numberToDigits = function(num, base) { 644 | base = base || this.maxBase; 645 | let digits = []; 646 | while (num >= 1) { 647 | digits.push(num % base); 648 | num = Math.floor(num / base); 649 | } 650 | return digits.length ? digits.reverse() : [ 0 ]; 651 | }; 652 | // < export mudder.js 653 | ~~~ 654 | There’s a bit of incidental complexity here. In current JavaScript engines, [`push`ing](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push) scalars to the end of an array is usually much faster than [`unshift`ing](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift) scalars to its beginning. In my case: 655 | ~~~js 656 | var v1 = [], v2 = []; 657 | console.time('push'); 658 | for (let i = 0; i < 1e5; i++) { 659 | v1.push(i % 7907); 660 | } 661 | console.timeEnd('push'); 662 | 663 | console.time('unshift'); 664 | for (let i = 0; i < 1e5; i++) { 665 | v2.unshift(i % 7907); 666 | } 667 | console.timeEnd('unshift'); 668 | // > push: 5.277ms 669 | // > unshift: 3051.876ms 670 | ~~~ 671 | So `SymbolTable.prototype.numberToDigits` calculates the left-most digit first and moves right, but `push`ing them onto the array leaves it reversed. So it reverses its final answer. It also has a special case that checks for 0. 672 | 673 | Let’s make sure it works: 674 | ~~~js 675 | var decimal = new SymbolTable('0123456789'); 676 | console.log(decimal.numberToDigits(123)); 677 | console.log(decimal.numberToDigits(0)); 678 | // > [ 1, 2, 3 ] 679 | // > [ 0 ] 680 | ~~~ 681 | Let’s also make sure we don’t have any decimal/base-10 chauvinism: 682 | ~~~js 683 | console.log(decimal.numberToDigits(123, 2), (123).toString(2)); 684 | 685 | var hex = new SymbolTable('0123456789abcdef'); 686 | console.log(hex.numberToDigits(123), (123).toString(16)); 687 | // > [ 1, 1, 1, 1, 0, 1, 1 ] '1111011' 688 | // > [ 7, 11 ] '7b' 689 | ~~~ 690 | Note that each digit has to be `< B`, due to the modulo operation. 691 | 692 | This makes me want to implement digits→string to get `Number.prototype.toString`-like functionality: 693 | ~~~js 694 | SymbolTable.prototype.digitsToString = function(digits) { 695 | return digits.map(n => this.num2sym[n]).join(''); 696 | }; 697 | // < export mudder.js 698 | ~~~ 699 | This function is independent of what base to operate on. It’s just blindly replacing numbers with strings using the one-to-one `SymbolTable.num2sym` array. 700 | 701 | Confirming it works by going from number→digits→string: 702 | ~~~js 703 | console.log(decimal.digitsToString(decimal.numberToDigits(123))); 704 | console.log(hex.digitsToString(hex.numberToDigits(123))); 705 | // > 123 706 | // > 7b 707 | ~~~ 708 | 709 | Let’s just work backwards from strings to digits. We’ll build a big regular expressions to peel off each symbol if the symbol table is prefix-free. If it’s not, the “string” must actually be an array. 710 | ~~~js 711 | SymbolTable.prototype.stringToDigits = function(string) { 712 | if (!this.isPrefixCode && typeof string === 'string') { 713 | throw new TypeError( 714 | 'parsing string without prefix code is unsupported. Pass in array of stringy symbols?'); 715 | } 716 | if (typeof string === 'string') { 717 | const re = 718 | new RegExp('(' + Array.from(this.sym2num.keys()).join('|') + ')', 'g'); 719 | string = string.match(re); 720 | } 721 | return string.map(symbol => this.sym2num.get(symbol)); 722 | }; 723 | // < export mudder.js 724 | ~~~ 725 | Again, this operation is independent of the base. It’s just a table lookup, and involves no arithmetic. 726 | ~~~js 727 | console.log(decimal.stringToDigits('123')); 728 | console.log(decimal.stringToDigits('123'.split(''))); 729 | // > [ 1, 2, 3 ] 730 | // > [ 1, 2, 3 ] 731 | ~~~ 732 | 733 | Finally, we achieve `parseInt`-parity with the digits→number converter. Each element in the digits array is multiplied by a power of base `B` and summed. In code: 734 | ~~~js 735 | SymbolTable.prototype.digitsToNumber = function(digits, base) { 736 | base = base || this.maxBase; 737 | let currBase = 1; 738 | return digits.reduceRight((accum, curr) => { 739 | let ret = accum + curr * currBase; 740 | currBase *= base; 741 | return ret; 742 | }, 0); 743 | }; 744 | // < export mudder.js 745 | ~~~ 746 | A programmatic note: I used `Array.prototype.reduceRight` to loop from the *end* of `digits` to the beginning and avoid manual management of the index-to-power relationship. Also, this let me replace an expensive `Math.pow` call each iteration with a cheap multiply. 747 | 748 | Let’s test it, both with 123 = 0x7B (hexadecimal base-16 numbers are commonly prefixed by `0x`): 749 | ~~~js 750 | console.log(decimal.digitsToNumber([1, 2, 3]), hex.digitsToNumber([7, 11])); 751 | // > 123 123 752 | ~~~ 753 | We can trivially write non-stop number↔︎string functions: 754 | ~~~js 755 | SymbolTable.prototype.numberToString = function(num, base) { 756 | return this.digitsToString(this.numberToDigits(num, base)); 757 | }; 758 | SymbolTable.prototype.stringToNumber = function(num, base) { 759 | return this.digitsToNumber(this.stringToDigits(num), base); 760 | }; 761 | // < export mudder.js 762 | ~~~ 763 | With these, `SymbolTable` is `parseInt` and `Number.prototype.toString` super-charged. 764 | 765 | Now for some silly fun. 766 | ~~~js 767 | var oda = new SymbolTable('天下布武'); 768 | var meals = new SymbolTable('🍌🍳☕️,🍱,🍣🍮'.split(',')); 769 | var base62 = new SymbolTable( 770 | '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'); 771 | var kangxi = `一丨丶丿乙亅二亠人儿入八冂冖冫几凵刀力勹匕匚匸十卜卩厂厶又口囗土士 772 | 夂夊夕大女子宀寸小尢尸屮山川工己巾干幺广廴廾弋弓彐彡彳心戈戶手支攴 773 | 文斗斤方无日曰月木欠止歹殳毋比毛氏气水火爪父爻爿片牙牛犬玄玉瓜瓦甘 774 | 生用田疋疒癶白皮皿目矛矢石示禸禾穴立竹米糸缶网羊羽老而耒耳聿肉臣自 775 | 至臼舌舛舟艮色艸虍虫血行衣襾見角言谷豆豕豸貝赤走足身車辛辰辵邑酉釆 776 | 里金長門阜隶隹雨青非面革韋韭音頁風飛食首香馬骨高髟鬥鬯鬲鬼魚鳥鹵鹿 777 | 麥麻黃黍黑黹黽鼎鼓鼠鼻齊齒龍龜龠`.replace(/\s/g, ''); 778 | var rad = new SymbolTable(kangxi); 779 | 780 | var v = [ 0, 1, 9, 10, 35, 36, 37, 61, 62, 63, 1945 ]; 781 | var v2 = [ 2e3, 2e4, 2e5, 2e6, 2e7, 2e8, 2e9 ]; 782 | console.log(v.map(x => [x, rad.numberToString(x), base62.numberToString(x), 783 | oda.numberToString(x), meals.numberToString(x)] 784 | .join(' '))); 785 | console.log(v2.map( 786 | x => [x, rad.numberToString(x), base62.numberToString(x)].join(' '))); 787 | // > [ '0 一 0 天 🍌🍳☕️', 788 | // > '1 丨 1 下 🍱', 789 | // > '9 儿 9 布下 🍱🍌🍳☕️🍌🍳☕️', 790 | // > '10 入 A 布布 🍱🍌🍳☕️🍱', 791 | // > '35 夕 Z 布天武 🍱🍌🍳☕️🍣🍮🍣🍮', 792 | // > '36 大 a 布下天 🍱🍱🍌🍳☕️🍌🍳☕️', 793 | // > '37 女 b 布下下 🍱🍱🍌🍳☕️🍱', 794 | // > '61 戈 z 武武下 🍣🍮🍌🍳☕️🍣🍮🍱', 795 | // > '62 戶 10 武武布 🍣🍮🍌🍳☕️🍣🍮🍣🍮', 796 | // > '63 手 11 武武武 🍣🍮🍱🍌🍳☕️🍌🍳☕️', 797 | // > '1945 儿勹 VN 下武布下布下 🍣🍮🍣🍮🍌🍳☕️🍌🍳☕️🍌🍳☕️🍌🍳☕️🍱' ] 798 | // > [ '2000 儿木 WG', 799 | // > '20000 犬甘 5Ca', 800 | // > '200000 乙殳老 q1o', 801 | // > '2000000 尸行隶 8OI4', 802 | // > '20000000 丶人貝黑 1Luue', 803 | // > '200000000 匕父小玄 DXB8S', 804 | // > '2000000000 黽几黃水 2BLnMW' ] 805 | ~~~ 806 | Here, I made a few whimsical number systems to rewrite various interesting numbers in: 807 | 808 | - a quaternary radix-4 number system based on Oda Nobunaga’s motto circa late-1560s, 天下(tenka)布武fubu) (with 天下 meaning ‘all under heaven’ and 布武 roughly meaning ‘military order’). 809 | - The epicurean ternary radix-3 number system using the day’s meals: 🍌🍳☕️ for breakfast, 🍱 for lunch, and 🍣🍮 for dinner. 810 | - Base-62, using `0-9A-Za-z`, which is actually quite reasonable for database keys. 811 | - a radix-214 number system using all 214 radicals of Chinese as promulgated in the 1716 Dictionary ordered by the Kangxi Emperor. Two billion, instead of ten digits in base-10, is rendered using just four radicals: 黽几黃水, traditionally meaning *frog, table, yellow, water*. Perhaps the next Joshua Foer will fashion this into a memory system for memory championships. 812 | 813 | ## Arithmetic on digits 814 | In the previous section, we added methods to the `SymbolTable` object to convert positive integers ↔︎ digits ↔︎ strings, using the stringy symbols contained in the object, and a given radix `B`. By “digits” we meant an array of plain JavaScript numbers between 0 and `B - 1`. From this digits array you can: 815 | 816 | - recover the number by multiplying sucessive digits with successive powers of `B` and summing, so `[1, 2, 3]` is 123 in base-10 but in hexadecimal base-16, 0x123 is 291; 817 | - create a long string by mapping each digit to a unique stringy symbol, which is independent of base: `[1, 2, 3]` ↔︎ `'123'` using our Arabic symbols or `'一二三'` using Chinese symbols. 818 | 819 | Now. 820 | 821 | Here’s the way to get strings *between* two given strings. 822 | 823 | 1. Convert both strings to digits. 824 | 2. Instead of treating the sequence of digits as a number with the radix-point to the *right* of the last digit, let’s pretend that the radix point was to the *left of the first digit*. This gives you two numbers both between 0 and 1. 825 | 3. Still using the digits array, calculate their average. This new array of digits is readily mapped to a string that will be lexicographically “between” the original two strings. 826 | 827 | This might seem confusing! Arbitrary! Over-complicated! But I think every piece of this scheme is necessary and as simple as possible to achieve the desiderata at the top of this document. 828 | 829 | **Stupid example** Consider the base `B = 10` decimal system, and two strings, `'2'` and `'44'`. These strings map to digits `[2]` and `[4, 4]` respectively (and also to the numbers 2 and 44—stupid example to get you comfortable). 830 | 831 | Instead of these two strings representing integers with the radix-point (decimal point) after them, shift your perspective so that the radix-point is on the left: 832 | 833 | 1. Not 2, but 0.2 (which is 2 ÷ 10). 834 | 1. Not 44, but 0.44 (which is 44 ÷ 100). 835 | 836 | In other words, pretend both numbers have been divided by base `B` until they first drop below 1. 837 | 838 | Why do this? Just look at the mean of these fractions: `(0.2 + 0.44) / 2 = 0.32`. Move the decimal point in 0.32 to the end to get an integer, 32. Map 32 to a string: `'32'`. Look at that: `'2' < '32' < '44'`. 839 | 840 | The mean of two numbers comes from splitting the interval between them into two pieces. You can get `N` numbers by splitting the interval into `N + 1` pieces: `0.2 + (0.44 - 0.2) / (N + 1) * i` for `i` running from 1 to `N`. For `N = 5`, these are: 841 | ~~~js 842 | var N = 5, a = 0.2, b = 0.44; 843 | var intermediates = 844 | Array.from(Array(N), (_, i) => a + (b - a) / (N + 1) * (i + 1)); 845 | console.log(intermediates); 846 | // > [ 0.24000000000000002, 0.28, 0.32, 0.36, 0.4 ] 847 | ~~~ 848 | Ignoring floating-point-precision problems, `'2' < '24' < '28' < '32' < '36' < '40' < '44'`. 849 | 850 | Initially when developing this library, the `N > 1` case wasn’t important to me—I just wanted the average between two values: `(a + b) / 2`, so I only cared about adding two digits arrays (long-addition) and dividing by a scalar (long-division). However, the `N > 1` case is really useful, so Mudder.js finds `N ≥ 1` evenly-spaced numbers between `a` and `b` using the equation, `a + (b - a) / (N + 1) * i`, for `i` running from 1 to `N`. 851 | 852 | (Example—from ranking database entries. I frequently want to insert not just one new entry between two currently-adjacent entries. I want to insert `N ≫ 1` new entries. This *could* be faked by recursively finding averages, splitting the `a`–`b` interval into power-of-two sub-intervals. For example, to get `N = 5` numbers between 0.1 and 0.2, evaluate `[0.1, 0.2]`→`[0.1, 0.15, 0.2]`→`[0.1, 0.125, 0.15, 0.175, 0.25]`→`[0.1, 0.1125, 0.125, 0.1375, 0.15, 0.1625, 0.175, 0.1875, 0.2]`, then pick 5 of the 7 interior numbers. If you just pick the first five interior points, `0.1125, 0.125, 0.1375, 0.15, 0.1625` to return, there will be a big gap between the last point 0.1625 and the upper-bound 0.2. For this reason, Mudder.js directly computes `N` evenly-spaced numbers between `a` and `b`.) 853 | 854 | Evaluating `a + (b - a) / (N + 1) * i`, with `i = 1..N` and for `a` and `b` numbers in base-`B` between 0 and 1 represented by digits arrays (each element of which is between 0 and `B - 1`), as it’s written there, requires 855 | 856 | - adding digits arrays (long-addition), 857 | - subtracting digits arrays (long-subtraction), 858 | - multiplying and/or dividing digits arrays by scalars (long-multiplication and/or long-division). 859 | 860 | The mean, `(a + b) / 2`, can be achieved with just long-addition and long-division by 2. I did not want to do figure out much beyond this just to evaluate `a + (b - a) / (N + 1) * i`—if this expression was too much trouble, I could just recursively evaluate the mean. 861 | 862 | Let’s do some arithmetic massaging of the expression for `N` evenly-spaced interior points between `a` and `b`: 863 | ``` 864 | a + (b - a) / (N + 1) * i = ((N + 1) * a + b * i - a * i) / (N + 1) 865 | = (a / (N + 1)) * (N + 1 - i) + (b / (N + 1)) * i 866 | ``` 867 | The original expression can be written in several other ways that seem more attractive or less attractive than the original, but the last one is, I think, the simplest to implement: it needs long-division by arbitrary integers to divide `a / (N + 1)` and `b / (N + 1)`, then long-addition to generate the two sequences of `[a/(N+1), 2 * a/(N+1), 3 * a/(N+1), ..., , N * a/(N+1)]` and similarly for `b`. 868 | 869 | Let’s implement long-division with remainders, and then show how to do long-addition in the presence of remainders. 870 | 871 | --- 872 | 873 | 874 | The same idea works for bases other than `B = 10`. It’s just that adding and dividing becomes a *bit* more complicated in non-decimal bases. Let’s write routines that add two digits arrays, and that can divide a digit array by a scalar, both in base-`B`. 875 | 876 | 877 | 878 | 879 | 880 | ### Adding digits arrays 881 | Let’s re-create the scene. We started with two strings. We use a symbol table containing `B` entries to convert the strings to two arrays of digits, each element of which is a regular JavaScript integer between 0 and `B - 1`. *We are going to pretend that the digits have a radix-point before the first element* and we want to *add* the two sets of digits. 882 | 883 | Recall long addition from your youth. You add two base-10 decimal numbers by 884 | 885 | 1. lining up the decimal point, 886 | 1. adding the two rightmost numbers (filling in zeros if one number has fewer decimal places than the other), 887 | 1. then moving left, 888 | 1. taking care to carry the tens place of a sum if it was ≥10. 889 | 890 | **Example base-10** Let’s add 0.12 + 0.456: 891 | ``` 892 | 0.12 893 | + 0.456 894 | ----- 895 | 0.576 ⟸ found 6 first, then 7, then finally 5. 896 | ``` 897 | 898 | An example complicated by carries: 0.12 + 0.999: 899 | ``` 900 | [1 1] ⟸ carries 901 | 0.12 902 | + 0.999 903 | ----- 904 | 1.119 ⟸ found 9 first, then to the left 905 | ``` 906 | 907 | **Example base-16** Here’s a hexadecimal example: 0x0.12 + 0x0.9ab. Recall that the “0x” in the beginning tells you the following number is base-16, its digits going from 0 to ‘f’=15, so `0x1 + 0x9 = 0xa`, and `0xf + 0x1 = 0x10`. Other than that, it’s the same long-addition algorithm: 908 | ``` 909 | 0.12 910 | + 0.9ab 911 | ----- 912 | 0.acb ⟸ found 0xb first, then 0xc, then finally 0xa 913 | ``` 914 | Let’s check that this is right: 915 | ~~~js 916 | console.log((0x12 / 0x100 + 0x9ab / 0x1000).toString(16)); 917 | // > 0.acb 918 | ~~~ 919 | 920 | Here’s an example with carries, 0x0.12 + 0x0.ffd: 921 | ``` 922 | [1 1] ⟸ hexadecimal carries 923 | 0.12 924 | + 0.ffd 925 | ----- 926 | 1.11d ⟸ found 0xd first, then to the left 927 | ``` 928 | Checking this too: 929 | ~~~js 930 | console.log((0x12 / 0x100 + 0xffd / 0x1000).toString(16)); 931 | // > 1.11d 932 | ~~~ 933 | The carry digit will be either 0 or 1. Why? Because the biggest carry would come from adding the biggest digits: `(B - 1) + (B - 1) = (1) * B + (B - 2) * 1` which would be written with two digits, 1 and `B - 2`. In hexadecimal, this means `0xf + 0xf = 0x1e = 16 + 14 = 30 ✓`. So if you had a column in long-addition of 0xf, 0xf, and a carry of 0x1, the sum will be 0x1f, and you’d write ‘f’ underneath the line and carry that ‘1’ to the column to the left. 934 | 935 | **Code** Thinking about code to long-add two arrays of digits, assuming the radix-point to the left of the first element of both, and where each digit is a JavaScript integer between 0 and `B - 1`, I wanted to get three things right: (1) determining when a carry happens—when the sum of two elements was `≥B`; (2) tracking the carry as it moved leftwards; and (3) handling arrays of different lengths. 936 | 937 | How do I want to deal with arrays of differing lengths? In the examples above, when a column lacked a number from one of the summands, we pretended it was zero. One option could be to pad a shorter digits array with zeros. But that’s just equivalent to *copying* the trailing elements of the longer array to the result array. My plan is to *make a copy* of the longer array, then update its elements with the result of adding each digit from the shorter array. Because we have to work from the *ends* of both arrays to the beginning, we’ll use `Array.prototype.reduceRight` again: 938 | ~~~js 939 | function longAdd(long, short, base) { 940 | if (long.length < short.length) { 941 | [long, short] = [ short, long ]; 942 | } 943 | let carry = false, sum = long.slice(); // `sum` = copy of `long` 944 | short.reduceRight((_, shorti, i) => { 945 | const result = shorti + long[i] + carry; 946 | carry = result >= base; 947 | sum[i] = carry ? result - base : result; 948 | }, null); 949 | return {sum, carry}; 950 | }; 951 | ~~~ 952 | Programming note: I used `reduceRight`, a very functional-programming-y technique, in a very mutable way above, essentially as a `for`-loop, except `reduceRight` keeps track of the indexing starting at the end of arrays. 953 | 954 | I use a single boolean to indicate whether there’s a carry digit. It’s returned, along with a new array of digits representing the sum. Just like long-addition by hand, the radix-point is to the left of the `sum` array of digits—but, again just like long-addition by hand, if the final `carry` is true, there’s a “1” to the left of that radix point! 955 | 956 | An example will help clear this up. Again, I’d like to emphasize that, for purposes of this base-`B` long-addition, an array of digits, like `[1, 2]` or `[15, 15, 13]`, represents the number (0.12)`B` or (0.ffd)`B` respectively. Let’s check the hexadecimal base-16 answer from before: 957 | ~~~js 958 | console.log([ 959 | longAdd([ 1, 2 ], [ 0xf, 0xf, 0xd ], 16), 960 | longAdd([ 0xf, 0xf, 0xd ], [ 1, 2 ], 16), 961 | ]); 962 | // > [ { sum: [ 1, 1, 13 ], carry: true }, 963 | // > { sum: [ 1, 1, 13 ], carry: true } ] 964 | ~~~ 965 | Previously we’d shown that 0x0.12 + 0x0.ffd = 0x1.11d. Since the returned `carry` is true, `longAdd`’s final solution is 0x1.11d (since 0xd = 13). 966 | 967 | ### Dividing a digits array with a scalar 968 | Consider two numbers, `a` and `b`. Their mean is `(a + b) / 2`. This mean comes from splitting the interval between `a` and `b` into two pieces. `N` evenly-spaced points between `a` and `b` (not including these) comes from splitting the interval into `N + 1` pieces: `a + (b - a) / (N + 1) * i`, where `i` runs from `1` to `N`. 969 | 970 | Let’s do some arithmetic massaging: 971 | ``` 972 | a + (b - a) / (N + 1) * i = ((N + 1) * a + b * i - a * i) / (N + 1) 973 | = (a / (N + 1)) * (N + 1 - i) + (b / (N + 1)) * i 974 | ``` 975 | 976 | We’d like the `N ≥ 1` evenly-spaced numbers between `a` and `b`, for our case where `0 ≤ a, b < 1` and both are expressed as arrays of digits. A key element of this is dividing an array of digits by a scalar, which can be quite large (`N + 1` potentially much larger than base-`B`). 977 | 978 | ~~~js 979 | function longDiv(numeratorArr, den, base) { 980 | return numeratorArr.reduce((prev, curr) => { 981 | let newNum = curr + prev.rem * base; 982 | return { 983 | res : prev.res.concat(Math.floor(newNum / den)), 984 | rem : newNum % den, den 985 | }; 986 | }, {res : [], rem : 0, den}); 987 | } 988 | // < export mudder.js 989 | ~~~ 990 | ~~~js 991 | longDiv([ 1, 0 ], 2, 10); 992 | // > { res: [ 0, 5 ], rem: 0, den: 2 } 993 | ~~~ 994 | 995 | ~~~js 996 | /** 997 | * 998 | * @param {number[]} a larger number, as digits array 999 | * @param {number[]} b smaller number, as digits array 1000 | * @param {number} base 1001 | * @param {[number, number]} rem `a` and `b`'s remainders 1002 | * @param {number} den denominator for the remainders 1003 | * @returns {{res: number[], den: number, rem: number}} 1004 | */ 1005 | function longSubSameLen(a, b, base, rem = [], den = 0) { 1006 | if (a.length !== b.length) { throw new Error('same length arrays needed'); } 1007 | if (rem.length !== 0 && rem.length !== 2) { throw new Error('zero or two remainders expected'); } 1008 | a = a.slice(); // pre-emptively copy 1009 | if (rem.length) { 1010 | a = a.concat(rem[0]); 1011 | b = b.slice().concat(rem[1]); 1012 | } 1013 | const ret = Array(a.length).fill(0); 1014 | 1015 | // this is a LOOP LABEL! https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label 1016 | OUTER: for (let i = a.length - 1; i >= 0; --i) { 1017 | // console.log({a, ret}) 1018 | if (a[i] >= b[i]) { 1019 | ret[i] = a[i] - b[i]; 1020 | continue; 1021 | } 1022 | if (i === 0) { throw new Error('cannot go negative'); } 1023 | // look for a digit to the left to borrow from 1024 | for (let j = i - 1; j >= 0; --j) { 1025 | if (a[j] > 0) { 1026 | // found a non-zero digit. Decrement it 1027 | a[j]--; 1028 | // increment digits to its right by `base-1` 1029 | for (let k = j + 1; k < i; ++k) { a[k] += base - 1; } 1030 | // until you reach the digit you couldn't subtract 1031 | ret[i] = a[i] + (rem.length > 0 && i === a.length - 1 ? den : base) - b[i]; 1032 | continue OUTER; 1033 | } 1034 | } 1035 | // should have `continue`d `OUTER` loop 1036 | throw new Error('failed to find digit to borrow from'); 1037 | } 1038 | if (rem.length) { return {res: ret.slice(0, -1), rem: ret[ret.length - 1], den}; } 1039 | return {res: ret, rem: 0, den}; 1040 | } 1041 | 1042 | /** 1043 | * 1044 | * @param {number[]} a array of digits 1045 | * @param {number[]} b array of digits 1046 | * @param {number} base 1047 | * @param {number} rem remainder 1048 | * @param {number} den denominator under remainder 1049 | */ 1050 | function longAddSameLen(a, b, base, rem, den) { 1051 | if (a.length !== b.length) { 1052 | throw new Error('same length arrays needed'); 1053 | } 1054 | let carry = rem >= den, res = b.slice(); 1055 | if (carry) { 1056 | rem -= den; 1057 | } 1058 | a.reduceRight((_, ai, i) => { 1059 | const result = ai + b[i] + carry; 1060 | carry = result >= base; 1061 | res[i] = carry ? result - base : result; 1062 | }, null); 1063 | return {res, carry, rem, den}; 1064 | }; 1065 | 1066 | function rightpad(arr, finalLength, val) { 1067 | const padlen = Math.max(0, finalLength - arr.length); 1068 | return arr.concat(Array(padlen).fill(val || 0)); 1069 | } 1070 | 1071 | /** 1072 | * Returns `(a + (b-a)/M*n)` for n=[1, 2, ..., N], where `NN` 1078 | * @returns {{res: number[]; rem: number; den: number;}[]} `N` numbers 1079 | */ 1080 | function longLinspace(a, b, base, N, M) { 1081 | if (a.length < b.length) { 1082 | a = rightpad(a, b.length); 1083 | } else if (b.length < a.length) { 1084 | b = rightpad(b, a.length); 1085 | } 1086 | if (a.length === b.length && a.every((a, i) => a === b[i])) { 1087 | throw new Error('Start and end strings lexicographically inseparable'); 1088 | } 1089 | const aDiv = longDiv(a, M, base); 1090 | const bDiv = longDiv(b, M, base); 1091 | let aPrev = longSubSameLen(a, aDiv.res, base, [0, aDiv.rem], M); 1092 | let bPrev = bDiv; 1093 | const ret = []; 1094 | for (let n = 1; n <= N; ++n) { 1095 | const x = longAddSameLen(aPrev.res, bPrev.res, base, aPrev.rem + bPrev.rem, M); 1096 | ret.push(x); 1097 | aPrev = longSubSameLen(aPrev.res, aDiv.res, base, [aPrev.rem, aDiv.rem], M); 1098 | bPrev = longAddSameLen(bPrev.res, bDiv.res, base, bPrev.rem + bDiv.rem, M); 1099 | } 1100 | return ret; 1101 | } 1102 | function leftpad(arr, finalLength, val) { 1103 | const padlen = Math.max(0, finalLength - arr.length); 1104 | return Array(padlen).fill(val || 0).concat(arr); 1105 | } 1106 | 1107 | SymbolTable.prototype.roundFraction = function(numerator, denominator, base) { 1108 | base = base || this.maxBase; 1109 | var places = Math.ceil(Math.log(denominator) / Math.log(base)); 1110 | var scale = Math.pow(base, places); 1111 | var scaled = Math.round(numerator / denominator * scale); 1112 | var digits = this.numberToDigits(scaled, base); 1113 | return leftpad(digits, places, 0); 1114 | }; 1115 | 1116 | function chopDigits(rock, water, placesToKeep = 0) { 1117 | for (let idx = placesToKeep; idx < water.length; idx++) { 1118 | if (water[idx] && rock[idx] !== water[idx]) { 1119 | return water.slice(0, idx + 1); 1120 | } 1121 | } 1122 | return water; 1123 | } 1124 | 1125 | function lexicographicLessThanArray(a, b) { 1126 | const n = Math.min(a.length, b.length); 1127 | for (let i = 0; i < n; i++) { 1128 | if (a[i] === b[i]) { 1129 | continue; 1130 | } 1131 | return a[i] < b[i]; 1132 | } 1133 | return a.length < b.length; 1134 | } 1135 | 1136 | function chopSuccessiveDigits(strings, placesToKeep = 0) { 1137 | const reversed = !lexicographicLessThanArray(strings[0], strings[1]); 1138 | if (reversed) { 1139 | strings.reverse(); 1140 | } 1141 | const result = 1142 | strings.slice(1).reduce((accum, curr) => accum.concat( 1143 | [ chopDigits(accum[accum.length - 1], curr, placesToKeep ) ]), 1144 | [ strings[0] ]); 1145 | if (reversed) { 1146 | result.reverse(); 1147 | } 1148 | return result; 1149 | } 1150 | 1151 | function truncateLexHigher(lo, hi) { 1152 | const swapped = lo > hi; 1153 | if (swapped) { 1154 | [lo, hi] = [ hi, lo ]; 1155 | } 1156 | if (swapped) { 1157 | return [ hi, lo ]; 1158 | } 1159 | return [ lo, hi ]; 1160 | } 1161 | 1162 | SymbolTable.prototype.mudder = function(a, b, numStrings, base, numDivisions, placesToKeep = 0) { 1163 | if (typeof a === 'number'){ 1164 | numStrings = a; 1165 | a = ''; 1166 | b = ''; 1167 | } 1168 | a = a || this.num2sym[0]; 1169 | b = b || this.num2sym[this.num2sym.length - 1].repeat(a.length + 6); 1170 | numStrings = typeof numStrings === 'number' ? numStrings : 1; 1171 | base = base || this.maxBase; 1172 | numDivisions = numDivisions || numStrings + 1; 1173 | 1174 | [a, b] = truncateLexHigher(a, b); 1175 | const ad = this.stringToDigits(a, base); 1176 | const bd = this.stringToDigits(b, base); 1177 | const intermediateDigits = longLinspace(ad, bd, base, numStrings, numDivisions); 1178 | let finalDigits = intermediateDigits.map( 1179 | v => v.res.concat(this.roundFraction(v.rem, v.den, base))); 1180 | finalDigits.unshift(ad); 1181 | finalDigits.push(bd); 1182 | return chopSuccessiveDigits(finalDigits, placesToKeep) 1183 | .slice(1, finalDigits.length - 1) 1184 | .map(v => this.digitsToString(v)); 1185 | }; 1186 | // < export mudder.js 1187 | ~~~ 1188 | 1189 | ~~~js 1190 | 1191 | var B = 6; 1192 | var N = 9; 1193 | 1194 | Array.from(Array(N + 2), (_, i) => (i) / (N + 1) / B + 1 / B) 1195 | .map(x => x.toString(B)) 1196 | 1197 | longLinspace([ 1 ], [ 2 ], B, N); 1198 | longLinspace([ 2 ], [ 1 ], B, N); 1199 | longLinspace([ 1 ], [ 2 ], B, N) 1200 | .map(o => o.res.concat(decimal.roundFraction(o.rem, o.den, B))); 1201 | 1202 | decimal.mudder('1', '2', B, N); 1203 | decimal.mudder('2', '1', B, N); 1204 | 1205 | decimal.mudder('2', '34502105342105402154', B, 10) 1206 | 1207 | ~~~ 1208 | 1209 | Let’s make a few useful symbol tables; 1210 | 1211 | ~~~js 1212 | var iter = (char, len) => Array.from( 1213 | Array(len), (_, i) => String.fromCharCode(char.charCodeAt(0) + i)); 1214 | 1215 | var base62 = 1216 | new SymbolTable(iter('0', 10).concat(iter('A', 26)).concat(iter('a', 26))); 1217 | 1218 | // Base36 should use lowercase since that’s what Number.toString outputs. 1219 | var base36arr = iter('0', 10).concat(iter('a', 26)); 1220 | var base36keys = base36arr.concat(iter('A', 26)); 1221 | function range(n) { return Array.from(Array(n), (_, i) => i); } 1222 | var base36vals = range(10) 1223 | .concat(range(26).map(i => i + 10)) 1224 | .concat(range(26).map(i => i + 10)); 1225 | function zip(a, b) { 1226 | return Array.from(Array(a.length), (_, i) => [a[i], b[i]]); 1227 | } 1228 | var base36 = new SymbolTable(base36arr, new Map(zip(base36keys, base36vals))); 1229 | 1230 | var alphabet = new SymbolTable(iter('a', 26), 1231 | new Map(zip(iter('a', 26).concat(iter('A', 26)), 1232 | range(26).concat(range(26))))); 1233 | 1234 | // < export mudder.js 1235 | ~~~ 1236 | 1237 | And make it an ES2015 module: 1238 | 1239 | ~~~js 1240 | export {SymbolTable, base62, base36, alphaupper, alphalower}; 1241 | ~~~ 1242 | 1243 | Actually, make it a standard Node module: 1244 | 1245 | ~~~js 1246 | module.exports = {SymbolTable, base62, base36, alphabet, longLinspace}; 1247 | // < export mudder.js 1248 | ~~~ 1249 | 1250 | ## References 1251 | 1252 | Cuneiform: http://it.stlawu.edu/~dmelvill/mesomath/Numbers.html and https://en.wikipedia.org/wiki/Sexagesimal#Babylonian_mathematics and Cuneiform Composite from http://oracc.museum.upenn.edu/doc/help/visitingoracc/fonts/index.html 1253 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /dist/mudder.cjs: -------------------------------------------------------------------------------- 1 | function isPrefixCode(strings) { 2 | for (const i of strings) { 3 | for (const j of strings) { 4 | if (j === i) { 5 | continue; 6 | } 7 | if (i.startsWith(j)) { 8 | return false; 9 | } 10 | } 11 | } 12 | return true; 13 | } 14 | function isPrefixCodeLogLinear(strings) { 15 | strings = Array.from(strings).sort(); 16 | for (const [i, curr] of strings.entries()) { 17 | const prev = strings[i - 1]; 18 | if (prev === curr) { 19 | continue; 20 | } 21 | if (curr.startsWith(prev)) { 22 | return false; 23 | } 24 | ; 25 | } 26 | return true; 27 | } 28 | isPrefixCode = isPrefixCodeLogLinear; 29 | function SymbolTable(symbolsArr, symbolsMap) { 30 | "use strict"; 31 | if (typeof this === "undefined") { 32 | throw new TypeError("constructor called as a function"); 33 | } 34 | ; 35 | if (typeof symbolsArr === "string") { 36 | symbolsArr = symbolsArr.split(""); 37 | } else if (!Array.isArray(symbolsArr)) { 38 | throw new TypeError("symbolsArr must be string or array"); 39 | } 40 | if (typeof symbolsMap === "undefined") { 41 | symbolsMap = new Map(symbolsArr.map((str, idx) => [str, idx])); 42 | } else if (symbolsMap instanceof Object && !(symbolsMap instanceof Map)) { 43 | symbolsMap = new Map(Object.entries(symbolsMap)); 44 | } else if (!(symbolsMap instanceof Map)) { 45 | throw new TypeError("symbolsMap can be omitted, a Map, or an Object"); 46 | } 47 | let symbolsValuesSet = new Set(symbolsMap.values()); 48 | for (let i = 0; i < symbolsArr.length; i++) { 49 | if (!symbolsValuesSet.has(i)) { 50 | throw new RangeError(symbolsArr.length + " symbols given but " + i + " not found in symbol table"); 51 | } 52 | } 53 | this.num2sym = symbolsArr; 54 | this.sym2num = symbolsMap; 55 | this.maxBase = this.num2sym.length; 56 | this.isPrefixCode = isPrefixCode(symbolsArr); 57 | } 58 | SymbolTable.prototype.numberToDigits = function(num, base) { 59 | base = base || this.maxBase; 60 | let digits = []; 61 | while (num >= 1) { 62 | digits.push(num % base); 63 | num = Math.floor(num / base); 64 | } 65 | return digits.length ? digits.reverse() : [0]; 66 | }; 67 | SymbolTable.prototype.digitsToString = function(digits) { 68 | return digits.map((n) => this.num2sym[n]).join(""); 69 | }; 70 | SymbolTable.prototype.stringToDigits = function(string) { 71 | if (!this.isPrefixCode && typeof string === "string") { 72 | throw new TypeError( 73 | "parsing string without prefix code is unsupported. Pass in array of stringy symbols?" 74 | ); 75 | } 76 | if (typeof string === "string") { 77 | const re = new RegExp("(" + Array.from(this.sym2num.keys()).join("|") + ")", "g"); 78 | string = string.match(re); 79 | } 80 | return string.map((symbol) => this.sym2num.get(symbol)); 81 | }; 82 | SymbolTable.prototype.digitsToNumber = function(digits, base) { 83 | base = base || this.maxBase; 84 | let currBase = 1; 85 | return digits.reduceRight((accum, curr) => { 86 | let ret = accum + curr * currBase; 87 | currBase *= base; 88 | return ret; 89 | }, 0); 90 | }; 91 | SymbolTable.prototype.numberToString = function(num, base) { 92 | return this.digitsToString(this.numberToDigits(num, base)); 93 | }; 94 | SymbolTable.prototype.stringToNumber = function(num, base) { 95 | return this.digitsToNumber(this.stringToDigits(num), base); 96 | }; 97 | function longDiv(numeratorArr, den, base) { 98 | return numeratorArr.reduce((prev, curr) => { 99 | let newNum = curr + prev.rem * base; 100 | return { 101 | res: prev.res.concat(Math.floor(newNum / den)), 102 | rem: newNum % den, 103 | den 104 | }; 105 | }, { res: [], rem: 0, den }); 106 | } 107 | function longSubSameLen(a, b, base, rem = [], den = 0) { 108 | if (a.length !== b.length) { 109 | throw new Error("same length arrays needed"); 110 | } 111 | if (rem.length !== 0 && rem.length !== 2) { 112 | throw new Error("zero or two remainders expected"); 113 | } 114 | a = a.slice(); 115 | if (rem.length) { 116 | a = a.concat(rem[0]); 117 | b = b.slice().concat(rem[1]); 118 | } 119 | const ret = Array(a.length).fill(0); 120 | OUTER: 121 | for (let i = a.length - 1; i >= 0; --i) { 122 | if (a[i] >= b[i]) { 123 | ret[i] = a[i] - b[i]; 124 | continue; 125 | } 126 | if (i === 0) { 127 | throw new Error("cannot go negative"); 128 | } 129 | for (let j = i - 1; j >= 0; --j) { 130 | if (a[j] > 0) { 131 | a[j]--; 132 | for (let k = j + 1; k < i; ++k) { 133 | a[k] += base - 1; 134 | } 135 | ret[i] = a[i] + (rem.length > 0 && i === a.length - 1 ? den : base) - b[i]; 136 | continue OUTER; 137 | } 138 | } 139 | throw new Error("failed to find digit to borrow from"); 140 | } 141 | if (rem.length) { 142 | return { res: ret.slice(0, -1), rem: ret[ret.length - 1], den }; 143 | } 144 | return { res: ret, rem: 0, den }; 145 | } 146 | function longAddSameLen(a, b, base, rem, den) { 147 | if (a.length !== b.length) { 148 | throw new Error("same length arrays needed"); 149 | } 150 | let carry = rem >= den, res = b.slice(); 151 | if (carry) { 152 | rem -= den; 153 | } 154 | a.reduceRight((_, ai, i) => { 155 | const result = ai + b[i] + carry; 156 | carry = result >= base; 157 | res[i] = carry ? result - base : result; 158 | }, null); 159 | return { res, carry, rem, den }; 160 | } 161 | ; 162 | function rightpad(arr, finalLength, val) { 163 | const padlen = Math.max(0, finalLength - arr.length); 164 | return arr.concat(Array(padlen).fill(val || 0)); 165 | } 166 | function longLinspace(a, b, base, N, M) { 167 | if (a.length < b.length) { 168 | a = rightpad(a, b.length); 169 | } else if (b.length < a.length) { 170 | b = rightpad(b, a.length); 171 | } 172 | if (a.length === b.length && a.every((a2, i) => a2 === b[i])) { 173 | throw new Error("Start and end strings lexicographically inseparable"); 174 | } 175 | const aDiv = longDiv(a, M, base); 176 | const bDiv = longDiv(b, M, base); 177 | let aPrev = longSubSameLen(a, aDiv.res, base, [0, aDiv.rem], M); 178 | let bPrev = bDiv; 179 | const ret = []; 180 | for (let n = 1; n <= N; ++n) { 181 | const x = longAddSameLen(aPrev.res, bPrev.res, base, aPrev.rem + bPrev.rem, M); 182 | ret.push(x); 183 | aPrev = longSubSameLen(aPrev.res, aDiv.res, base, [aPrev.rem, aDiv.rem], M); 184 | bPrev = longAddSameLen(bPrev.res, bDiv.res, base, bPrev.rem + bDiv.rem, M); 185 | } 186 | return ret; 187 | } 188 | function leftpad(arr, finalLength, val) { 189 | const padlen = Math.max(0, finalLength - arr.length); 190 | return Array(padlen).fill(val || 0).concat(arr); 191 | } 192 | SymbolTable.prototype.roundFraction = function(numerator, denominator, base) { 193 | base = base || this.maxBase; 194 | var places = Math.ceil(Math.log(denominator) / Math.log(base)); 195 | var scale = Math.pow(base, places); 196 | var scaled = Math.round(numerator / denominator * scale); 197 | var digits = this.numberToDigits(scaled, base); 198 | return leftpad(digits, places, 0); 199 | }; 200 | function chopDigits(rock, water, placesToKeep = 0) { 201 | for (let idx = placesToKeep; idx < water.length; idx++) { 202 | if (water[idx] && rock[idx] !== water[idx]) { 203 | return water.slice(0, idx + 1); 204 | } 205 | } 206 | return water; 207 | } 208 | function lexicographicLessThanArray(a, b) { 209 | const n = Math.min(a.length, b.length); 210 | for (let i = 0; i < n; i++) { 211 | if (a[i] === b[i]) { 212 | continue; 213 | } 214 | return a[i] < b[i]; 215 | } 216 | return a.length < b.length; 217 | } 218 | function chopSuccessiveDigits(strings, placesToKeep = 0) { 219 | const reversed = !lexicographicLessThanArray(strings[0], strings[1]); 220 | if (reversed) { 221 | strings.reverse(); 222 | } 223 | const result = strings.slice(1).reduce( 224 | (accum, curr) => accum.concat( 225 | [chopDigits(accum[accum.length - 1], curr, placesToKeep)] 226 | ), 227 | [strings[0]] 228 | ); 229 | if (reversed) { 230 | result.reverse(); 231 | } 232 | return result; 233 | } 234 | function truncateLexHigher(lo, hi) { 235 | const swapped = lo > hi; 236 | if (swapped) { 237 | [lo, hi] = [hi, lo]; 238 | } 239 | if (swapped) { 240 | return [hi, lo]; 241 | } 242 | return [lo, hi]; 243 | } 244 | SymbolTable.prototype.mudder = function(a, b, numStrings, base, numDivisions, placesToKeep = 0) { 245 | if (typeof a === "number") { 246 | numStrings = a; 247 | a = ""; 248 | b = ""; 249 | } 250 | a = a || this.num2sym[0]; 251 | b = b || this.num2sym[this.num2sym.length - 1].repeat(a.length + 6); 252 | numStrings = typeof numStrings === "number" ? numStrings : 1; 253 | base = base || this.maxBase; 254 | numDivisions = numDivisions || numStrings + 1; 255 | [a, b] = truncateLexHigher(a, b); 256 | const ad = this.stringToDigits(a, base); 257 | const bd = this.stringToDigits(b, base); 258 | const intermediateDigits = longLinspace(ad, bd, base, numStrings, numDivisions); 259 | let finalDigits = intermediateDigits.map( 260 | (v) => v.res.concat(this.roundFraction(v.rem, v.den, base)) 261 | ); 262 | finalDigits.unshift(ad); 263 | finalDigits.push(bd); 264 | return chopSuccessiveDigits(finalDigits, placesToKeep).slice(1, finalDigits.length - 1).map((v) => this.digitsToString(v)); 265 | }; 266 | var iter = (char, len) => Array.from( 267 | Array(len), 268 | (_, i) => String.fromCharCode(char.charCodeAt(0) + i) 269 | ); 270 | var base62 = new SymbolTable(iter("0", 10).concat(iter("A", 26)).concat(iter("a", 26))); 271 | var base36arr = iter("0", 10).concat(iter("a", 26)); 272 | var base36keys = base36arr.concat(iter("A", 26)); 273 | function range(n) { 274 | return Array.from(Array(n), (_, i) => i); 275 | } 276 | var base36vals = range(10).concat(range(26).map((i) => i + 10)).concat(range(26).map((i) => i + 10)); 277 | function zip(a, b) { 278 | return Array.from(Array(a.length), (_, i) => [a[i], b[i]]); 279 | } 280 | var base36 = new SymbolTable(base36arr, new Map(zip(base36keys, base36vals))); 281 | var alphabet = new SymbolTable( 282 | iter("a", 26), 283 | new Map(zip( 284 | iter("a", 26).concat(iter("A", 26)), 285 | range(26).concat(range(26)) 286 | )) 287 | ); 288 | module.exports = { SymbolTable, base62, base36, alphabet, longLinspace }; 289 | -------------------------------------------------------------------------------- /dist/mudder.min.js: -------------------------------------------------------------------------------- 1 | (()=>{var E=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var O=E((F,A)=>{function v(t){for(const e of t)for(const n of t)if(n!==e&&e.startsWith(n))return!1;return!0}function S(t){t=Array.from(t).sort();for(const[e,n]of t.entries()){const r=t[e-1];if(r!==n&&n.startsWith(r))return!1}return!0}v=S;function u(t,e){"use strict";if(typeof this>"u")throw new TypeError("constructor called as a function");if(typeof t=="string")t=t.split("");else if(!Array.isArray(t))throw new TypeError("symbolsArr must be string or array");if(typeof e>"u")e=new Map(t.map((r,i)=>[r,i]));else if(e instanceof Object&&!(e instanceof Map))e=new Map(Object.entries(e));else if(!(e instanceof Map))throw new TypeError("symbolsMap can be omitted, a Map, or an Object");let n=new Set(e.values());for(let r=0;r=1;)n.push(t%e),t=Math.floor(t/e);return n.length?n.reverse():[0]};u.prototype.digitsToString=function(t){return t.map(e=>this.num2sym[e]).join("")};u.prototype.stringToDigits=function(t){if(!this.isPrefixCode&&typeof t=="string")throw new TypeError("parsing string without prefix code is unsupported. Pass in array of stringy symbols?");if(typeof t=="string"){const e=new RegExp("("+Array.from(this.sym2num.keys()).join("|")+")","g");t=t.match(e)}return t.map(e=>this.sym2num.get(e))};u.prototype.digitsToNumber=function(t,e){e=e||this.maxBase;let n=1;return t.reduceRight((r,i)=>{let o=r+i*n;return n*=e,o},0)};u.prototype.numberToString=function(t,e){return this.digitsToString(this.numberToDigits(t,e))};u.prototype.stringToNumber=function(t,e){return this.digitsToNumber(this.stringToDigits(t),e)};function d(t,e,n){return t.reduce((r,i)=>{let o=i+r.rem*n;return{res:r.res.concat(Math.floor(o/e)),rem:o%e,den:e}},{res:[],rem:0,den:e})}function m(t,e,n,r=[],i=0){if(t.length!==e.length)throw new Error("same length arrays needed");if(r.length!==0&&r.length!==2)throw new Error("zero or two remainders expected");t=t.slice(),r.length&&(t=t.concat(r[0]),e=e.slice().concat(r[1]));const o=Array(t.length).fill(0);t:for(let c=t.length-1;c>=0;--c){if(t[c]>=e[c]){o[c]=t[c]-e[c];continue}if(c===0)throw new Error("cannot go negative");for(let s=c-1;s>=0;--s)if(t[s]>0){t[s]--;for(let h=s+1;h0&&c===t.length-1?i:n)-e[c];continue t}throw new Error("failed to find digit to borrow from")}return r.length?{res:o.slice(0,-1),rem:o[o.length-1],den:i}:{res:o,rem:0,den:i}}function y(t,e,n,r,i){if(t.length!==e.length)throw new Error("same length arrays needed");let o=r>=i,c=e.slice();return o&&(r-=i),t.reduceRight((s,h,l)=>{const f=h+e[l]+o;o=f>=n,c[l]=o?f-n:f},null),{res:c,carry:o,rem:r,den:i}}function w(t,e,n){const r=Math.max(0,e-t.length);return t.concat(Array(r).fill(n||0))}function x(t,e,n,r,i){if(t.lengthf===e[p]))throw new Error("Start and end strings lexicographically inseparable");const o=d(t,i,n),c=d(e,i,n);let s=m(t,o.res,n,[0,o.rem],i),h=c;const l=[];for(let f=1;f<=r;++f){const p=y(s.res,h.res,n,s.rem+h.rem,i);l.push(p),s=m(s.res,o.res,n,[s.rem,o.rem],i),h=y(h.res,c.res,n,h.rem+c.rem,i)}return l}function j(t,e,n){const r=Math.max(0,e-t.length);return Array(r).fill(n||0).concat(t)}u.prototype.roundFraction=function(t,e,n){n=n||this.maxBase;var r=Math.ceil(Math.log(e)/Math.log(n)),i=Math.pow(n,r),o=Math.round(t/e*i),c=this.numberToDigits(o,n);return j(c,r,0)};function C(t,e,n=0){for(let r=n;ri.concat([C(i[i.length-1],o,e)]),[t[0]]);return n&&r.reverse(),r}function B(t,e){const n=t>e;return n&&([t,e]=[e,t]),n?[e,t]:[t,e]}u.prototype.mudder=function(t,e,n,r,i,o=0){typeof t=="number"&&(n=t,t="",e=""),t=t||this.num2sym[0],e=e||this.num2sym[this.num2sym.length-1].repeat(t.length+6),n=typeof n=="number"?n:1,r=r||this.maxBase,i=i||n+1,[t,e]=B(t,e);const c=this.stringToDigits(t,r),s=this.stringToDigits(e,r);let l=x(c,s,r,n,i).map(f=>f.res.concat(this.roundFraction(f.rem,f.den,r)));return l.unshift(c),l.push(s),P(l,o).slice(1,l.length-1).map(f=>this.digitsToString(f))};var a=(t,e)=>Array.from(Array(e),(n,r)=>String.fromCharCode(t.charCodeAt(0)+r)),M=new u(a("0",10).concat(a("A",26)).concat(a("a",26))),T=a("0",10).concat(a("a",26)),N=T.concat(a("A",26));function g(t){return Array.from(Array(t),(e,n)=>n)}var R=g(10).concat(g(26).map(t=>t+10)).concat(g(26).map(t=>t+10));function D(t,e){return Array.from(Array(t.length),(n,r)=>[t[r],e[r]])}var _=new u(T,new Map(D(N,R))),k=new u(a("a",26),new Map(D(a("a",26).concat(a("A",26)),g(26).concat(g(26)))));A.exports={SymbolTable:u,base62:M,base36:_,alphabet:k,longLinspace:x}});O();})(); 2 | //# sourceMappingURL=mudder.min.js.map 3 | -------------------------------------------------------------------------------- /dist/mudder.min.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../index.js"], 4 | "sourcesContent": ["function isPrefixCode(strings) {\n // Note: we skip checking for prefixness if two symbols are equal to each\n // other. This implies that repeated symbols in the input are *silently\n // ignored*!\n for (const i of strings) {\n for (const j of strings) {\n if (j === i) { // [\uD83C\uDF45]\n continue;\n }\n if (i.startsWith(j)) {\n return false;\n }\n }\n }\n return true;\n}\n// < export mudder.js\n\nfunction isPrefixCodeLogLinear(strings) {\n strings = Array.from(strings).sort(); // set->array or array->copy\n for (const [i, curr] of strings.entries()) {\n const prev = strings[i - 1]; // undefined for first iteration\n if (prev === curr) { // Skip repeated entries, match quadratic API\n continue;\n }\n if (curr.startsWith(prev)) { // str.startsWith(undefined) always false\n return false;\n };\n }\n return true;\n}\n// < export mudder.js\n\nisPrefixCode = isPrefixCodeLogLinear;\n// < export mudder.js\n\n/* Constructor:\nsymbolsArr is a string (split into an array) or an array. In either case, it\nmaps numbers (array indexes) to stringy symbols. Its length defines the max\nradix the symbol table can handle.\n\nsymbolsMap is optional, but goes the other way, so it can be an object or Map.\nIts keys are stringy symbols and its values are numbers. If omitted, the\nimplied map goes from the indexes of symbolsArr to the symbols.\n\nWhen symbolsMap is provided, its values are checked to ensure that each number\nfrom 0 to max radix minus one is present. If you had a symbol as an entry in\nsymbolsArr, then number->string would use that symbol, but the resulting\nstring couldn't be parsed because that symbol wasn't in symbolMap.\n*/\nfunction SymbolTable(symbolsArr, symbolsMap) {\n 'use strict'; // [\u26C8]\n if (typeof this === 'undefined') {\n throw new TypeError('constructor called as a function')\n };\n\n // Condition the input `symbolsArr`\n if (typeof symbolsArr === 'string') {\n symbolsArr = symbolsArr.split('');\n } else if (!Array.isArray(symbolsArr)) {\n throw new TypeError('symbolsArr must be string or array');\n }\n\n // Condition the second input, `symbolsMap`. If no symbolsMap passed in, make\n // it by inverting symbolsArr. If it's an object (and not a Map), convert its\n // own-properties to a Map.\n if (typeof symbolsMap === 'undefined') {\n symbolsMap = new Map(symbolsArr.map((str, idx) => [str, idx]));\n } else if (symbolsMap instanceof Object && !(symbolsMap instanceof Map)) {\n symbolsMap = new Map(Object.entries(symbolsMap));\n } else if (!(symbolsMap instanceof Map) ){\n throw new TypeError('symbolsMap can be omitted, a Map, or an Object');\n }\n\n // Ensure that each integer from 0 to `symbolsArr.length - 1` is a value in\n // `symbolsMap`\n let symbolsValuesSet = new Set(symbolsMap.values());\n for (let i = 0; i < symbolsArr.length; i++) {\n if (!symbolsValuesSet.has(i)) {\n throw new RangeError(symbolsArr.length + ' symbols given but ' + i +\n ' not found in symbol table');\n }\n }\n\n this.num2sym = symbolsArr;\n this.sym2num = symbolsMap;\n this.maxBase = this.num2sym.length;\n this.isPrefixCode = isPrefixCode(symbolsArr);\n}\n// < export mudder.js\n\nSymbolTable.prototype.numberToDigits = function(num, base) {\n base = base || this.maxBase;\n let digits = [];\n while (num >= 1) {\n digits.push(num % base);\n num = Math.floor(num / base);\n }\n return digits.length ? digits.reverse() : [ 0 ];\n};\n// < export mudder.js\n\nSymbolTable.prototype.digitsToString = function(digits) {\n return digits.map(n => this.num2sym[n]).join('');\n};\n// < export mudder.js\n\nSymbolTable.prototype.stringToDigits = function(string) {\n if (!this.isPrefixCode && typeof string === 'string') {\n throw new TypeError(\n 'parsing string without prefix code is unsupported. Pass in array of stringy symbols?');\n }\n if (typeof string === 'string') {\n const re =\n new RegExp('(' + Array.from(this.sym2num.keys()).join('|') + ')', 'g');\n string = string.match(re);\n }\n return string.map(symbol => this.sym2num.get(symbol));\n};\n// < export mudder.js\n\nSymbolTable.prototype.digitsToNumber = function(digits, base) {\n base = base || this.maxBase;\n let currBase = 1;\n return digits.reduceRight((accum, curr) => {\n let ret = accum + curr * currBase;\n currBase *= base;\n return ret;\n }, 0);\n};\n// < export mudder.js\n\nSymbolTable.prototype.numberToString = function(num, base) {\n return this.digitsToString(this.numberToDigits(num, base));\n};\nSymbolTable.prototype.stringToNumber = function(num, base) {\n return this.digitsToNumber(this.stringToDigits(num), base);\n};\n// < export mudder.js\n\nfunction longDiv(numeratorArr, den, base) {\n return numeratorArr.reduce((prev, curr) => {\n let newNum = curr + prev.rem * base;\n return {\n res : prev.res.concat(Math.floor(newNum / den)),\n rem : newNum % den, den\n };\n }, {res : [], rem : 0, den});\n}\n// < export mudder.js\n\n/**\n *\n * @param {number[]} a larger number, as digits array\n * @param {number[]} b smaller number, as digits array\n * @param {number} base\n * @param {[number, number]} rem `a` and `b`'s remainders\n * @param {number} den denominator for the remainders\n * @returns {{res: number[], den: number, rem: number}}\n */\nfunction longSubSameLen(a, b, base, rem = [], den = 0) {\n if (a.length !== b.length) { throw new Error('same length arrays needed'); }\n if (rem.length !== 0 && rem.length !== 2) { throw new Error('zero or two remainders expected'); }\n a = a.slice(); // pre-emptively copy\n if (rem.length) {\n a = a.concat(rem[0]);\n b = b.slice().concat(rem[1]);\n }\n const ret = Array(a.length).fill(0);\n\n // this is a LOOP LABEL! https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label\n OUTER: for (let i = a.length - 1; i >= 0; --i) {\n // console.log({a, ret})\n if (a[i] >= b[i]) {\n ret[i] = a[i] - b[i];\n continue;\n }\n if (i === 0) { throw new Error('cannot go negative'); }\n // look for a digit to the left to borrow from\n for (let j = i - 1; j >= 0; --j) {\n if (a[j] > 0) {\n // found a non-zero digit. Decrement it\n a[j]--;\n // increment digits to its right by `base-1`\n for (let k = j + 1; k < i; ++k) { a[k] += base - 1; }\n // until you reach the digit you couldn't subtract\n ret[i] = a[i] + (rem.length > 0 && i === a.length - 1 ? den : base) - b[i];\n continue OUTER;\n }\n }\n // should have `continue`d `OUTER` loop\n throw new Error('failed to find digit to borrow from');\n }\n if (rem.length) { return {res: ret.slice(0, -1), rem: ret[ret.length - 1], den}; }\n return {res: ret, rem: 0, den};\n}\n\n/**\n *\n * @param {number[]} a array of digits\n * @param {number[]} b array of digits\n * @param {number} base\n * @param {number} rem remainder\n * @param {number} den denominator under remainder\n */\nfunction longAddSameLen(a, b, base, rem, den) {\n if (a.length !== b.length) {\n throw new Error('same length arrays needed');\n }\n let carry = rem >= den, res = b.slice();\n if (carry) {\n rem -= den;\n }\n a.reduceRight((_, ai, i) => {\n const result = ai + b[i] + carry;\n carry = result >= base;\n res[i] = carry ? result - base : result;\n }, null);\n return {res, carry, rem, den};\n};\n\nfunction rightpad(arr, finalLength, val) {\n const padlen = Math.max(0, finalLength - arr.length);\n return arr.concat(Array(padlen).fill(val || 0));\n}\n\n/**\n * Returns `(a + (b-a)/M*n)` for n=[1, 2, ..., N], where `NN`\n * @returns {{res: number[]; rem: number; den: number;}[]} `N` numbers\n */\nfunction longLinspace(a, b, base, N, M) {\n if (a.length < b.length) {\n a = rightpad(a, b.length);\n } else if (b.length < a.length) {\n b = rightpad(b, a.length);\n }\n if (a.length === b.length && a.every((a, i) => a === b[i])) {\n throw new Error('Start and end strings lexicographically inseparable');\n }\n const aDiv = longDiv(a, M, base);\n const bDiv = longDiv(b, M, base);\n let aPrev = longSubSameLen(a, aDiv.res, base, [0, aDiv.rem], M);\n let bPrev = bDiv;\n const ret = [];\n for (let n = 1; n <= N; ++n) {\n const x = longAddSameLen(aPrev.res, bPrev.res, base, aPrev.rem + bPrev.rem, M);\n ret.push(x);\n aPrev = longSubSameLen(aPrev.res, aDiv.res, base, [aPrev.rem, aDiv.rem], M);\n bPrev = longAddSameLen(bPrev.res, bDiv.res, base, bPrev.rem + bDiv.rem, M);\n }\n return ret;\n}\nfunction leftpad(arr, finalLength, val) {\n const padlen = Math.max(0, finalLength - arr.length);\n return Array(padlen).fill(val || 0).concat(arr);\n}\n\nSymbolTable.prototype.roundFraction = function(numerator, denominator, base) {\n base = base || this.maxBase;\n var places = Math.ceil(Math.log(denominator) / Math.log(base));\n var scale = Math.pow(base, places);\n var scaled = Math.round(numerator / denominator * scale);\n var digits = this.numberToDigits(scaled, base);\n return leftpad(digits, places, 0);\n};\n\nfunction chopDigits(rock, water, placesToKeep = 0) {\n for (let idx = placesToKeep; idx < water.length; idx++) {\n if (water[idx] && rock[idx] !== water[idx]) {\n return water.slice(0, idx + 1);\n }\n }\n return water;\n}\n\nfunction lexicographicLessThanArray(a, b) {\n const n = Math.min(a.length, b.length);\n for (let i = 0; i < n; i++) {\n if (a[i] === b[i]) {\n continue;\n }\n return a[i] < b[i];\n }\n return a.length < b.length;\n}\n\nfunction chopSuccessiveDigits(strings, placesToKeep = 0) {\n const reversed = !lexicographicLessThanArray(strings[0], strings[1]);\n if (reversed) {\n strings.reverse();\n }\n const result =\n strings.slice(1).reduce((accum, curr) => accum.concat(\n [ chopDigits(accum[accum.length - 1], curr, placesToKeep ) ]),\n [ strings[0] ]);\n if (reversed) {\n result.reverse();\n }\n return result;\n}\n\nfunction truncateLexHigher(lo, hi) {\n const swapped = lo > hi;\n if (swapped) {\n [lo, hi] = [ hi, lo ];\n }\n if (swapped) {\n return [ hi, lo ];\n }\n return [ lo, hi ];\n}\n\nSymbolTable.prototype.mudder = function(a, b, numStrings, base, numDivisions, placesToKeep = 0) {\n if (typeof a === 'number'){\n numStrings = a;\n a = '';\n b = '';\n }\n a = a || this.num2sym[0];\n b = b || this.num2sym[this.num2sym.length - 1].repeat(a.length + 6);\n numStrings = typeof numStrings === 'number' ? numStrings : 1;\n base = base || this.maxBase;\n numDivisions = numDivisions || numStrings + 1;\n\n [a, b] = truncateLexHigher(a, b);\n const ad = this.stringToDigits(a, base);\n const bd = this.stringToDigits(b, base);\n const intermediateDigits = longLinspace(ad, bd, base, numStrings, numDivisions);\n let finalDigits = intermediateDigits.map(\n v => v.res.concat(this.roundFraction(v.rem, v.den, base)));\n finalDigits.unshift(ad);\n finalDigits.push(bd);\n return chopSuccessiveDigits(finalDigits, placesToKeep)\n .slice(1, finalDigits.length - 1)\n .map(v => this.digitsToString(v));\n};\n// < export mudder.js\n\nvar iter = (char, len) => Array.from(\n Array(len), (_, i) => String.fromCharCode(char.charCodeAt(0) + i));\n\nvar base62 =\n new SymbolTable(iter('0', 10).concat(iter('A', 26)).concat(iter('a', 26)));\n\n// Base36 should use lowercase since that\u2019s what Number.toString outputs.\nvar base36arr = iter('0', 10).concat(iter('a', 26));\nvar base36keys = base36arr.concat(iter('A', 26));\nfunction range(n) { return Array.from(Array(n), (_, i) => i); }\nvar base36vals = range(10)\n .concat(range(26).map(i => i + 10))\n .concat(range(26).map(i => i + 10));\nfunction zip(a, b) {\n return Array.from(Array(a.length), (_, i) => [a[i], b[i]]);\n}\nvar base36 = new SymbolTable(base36arr, new Map(zip(base36keys, base36vals)));\n\nvar alphabet = new SymbolTable(iter('a', 26),\n new Map(zip(iter('a', 26).concat(iter('A', 26)),\n range(26).concat(range(26)))));\n\n// < export mudder.js\n\nmodule.exports = {SymbolTable, base62, base36, alphabet, longLinspace};\n// < export mudder.js\n"], 5 | "mappings": "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", 6 | "names": ["require_mudderjs", "__commonJSMin", "exports", "module", "isPrefixCode", "strings", "i", "j", "isPrefixCodeLogLinear", "curr", "prev", "SymbolTable", "symbolsArr", "symbolsMap", "str", "idx", "symbolsValuesSet", "num", "base", "digits", "n", "string", "re", "symbol", "currBase", "accum", "ret", "longDiv", "numeratorArr", "den", "newNum", "longSubSameLen", "a", "b", "rem", "OUTER", "k", "longAddSameLen", "carry", "res", "_", "ai", "result", "rightpad", "arr", "finalLength", "val", "padlen", "longLinspace", "N", "M", "aDiv", "bDiv", "aPrev", "bPrev", "x", "leftpad", "numerator", "denominator", "places", "scale", "scaled", "chopDigits", "rock", "water", "placesToKeep", "lexicographicLessThanArray", "chopSuccessiveDigits", "reversed", "truncateLexHigher", "lo", "hi", "swapped", "numStrings", "numDivisions", "ad", "bd", "finalDigits", "v", "iter", "char", "len", "base62", "base36arr", "base36keys", "range", "base36vals", "zip", "base36", "alphabet"] 7 | } 8 | -------------------------------------------------------------------------------- /dist/mudder.min.mjs: -------------------------------------------------------------------------------- 1 | var y={},u={get exports(){return y},set exports(e){y=e}};function T(e){for(let t of e)for(let n of e)if(n!==t&&t.startsWith(n))return!1;return!0}function A(e){e=Array.from(e).sort();for(let[t,n]of e.entries()){let r=e[t-1];if(r!==n&&n.startsWith(r))return!1}return!0}T=A;function a(e,t){"use strict";if(typeof this>"u")throw new TypeError("constructor called as a function");if(typeof e=="string")e=e.split("");else if(!Array.isArray(e))throw new TypeError("symbolsArr must be string or array");if(typeof t>"u")t=new Map(e.map((r,o)=>[r,o]));else if(t instanceof Object&&!(t instanceof Map))t=new Map(Object.entries(t));else if(!(t instanceof Map))throw new TypeError("symbolsMap can be omitted, a Map, or an Object");let n=new Set(t.values());for(let r=0;r=1;)n.push(e%t),e=Math.floor(e/t);return n.length?n.reverse():[0]};a.prototype.digitsToString=function(e){return e.map(t=>this.num2sym[t]).join("")};a.prototype.stringToDigits=function(e){if(!this.isPrefixCode&&typeof e=="string")throw new TypeError("parsing string without prefix code is unsupported. Pass in array of stringy symbols?");if(typeof e=="string"){let t=new RegExp("("+Array.from(this.sym2num.keys()).join("|")+")","g");e=e.match(t)}return e.map(t=>this.sym2num.get(t))};a.prototype.digitsToNumber=function(e,t){t=t||this.maxBase;let n=1;return e.reduceRight((r,o)=>{let i=r+o*n;return n*=t,i},0)};a.prototype.numberToString=function(e,t){return this.digitsToString(this.numberToDigits(e,t))};a.prototype.stringToNumber=function(e,t){return this.digitsToNumber(this.stringToDigits(e),t)};function m(e,t,n){return e.reduce((r,o)=>{let i=o+r.rem*n;return{res:r.res.concat(Math.floor(i/t)),rem:i%t,den:t}},{res:[],rem:0,den:t})}function x(e,t,n,r=[],o=0){if(e.length!==t.length)throw new Error("same length arrays needed");if(r.length!==0&&r.length!==2)throw new Error("zero or two remainders expected");e=e.slice(),r.length&&(e=e.concat(r[0]),t=t.slice().concat(r[1]));let i=Array(e.length).fill(0);e:for(let s=e.length-1;s>=0;--s){if(e[s]>=t[s]){i[s]=e[s]-t[s];continue}if(s===0)throw new Error("cannot go negative");for(let c=s-1;c>=0;--c)if(e[c]>0){e[c]--;for(let l=c+1;l0&&s===e.length-1?o:n)-t[s];continue e}throw new Error("failed to find digit to borrow from")}return r.length?{res:i.slice(0,-1),rem:i[i.length-1],den:o}:{res:i,rem:0,den:o}}function w(e,t,n,r,o){if(e.length!==t.length)throw new Error("same length arrays needed");let i=r>=o,s=t.slice();return i&&(r-=o),e.reduceRight((c,l,h)=>{let f=l+t[h]+i;i=f>=n,s[h]=i?f-n:f},null),{res:s,carry:i,rem:r,den:o}}function v(e,t,n){let r=Math.max(0,t-e.length);return e.concat(Array(r).fill(n||0))}function _(e,t,n,r,o){if(e.lengthf===t[d]))throw new Error("Start and end strings lexicographically inseparable");let i=m(e,o,n),s=m(t,o,n),c=x(e,i.res,n,[0,i.rem],o),l=s,h=[];for(let f=1;f<=r;++f){let d=w(c.res,l.res,n,c.rem+l.rem,o);h.push(d),c=x(c.res,i.res,n,[c.rem,i.rem],o),l=w(l.res,s.res,n,l.rem+s.rem,o)}return h}function S(e,t,n){let r=Math.max(0,t-e.length);return Array(r).fill(n||0).concat(e)}a.prototype.roundFraction=function(e,t,n){n=n||this.maxBase;var r=Math.ceil(Math.log(t)/Math.log(n)),o=Math.pow(n,r),i=Math.round(e/t*o),s=this.numberToDigits(i,n);return S(s,r,0)};function j(e,t,n=0){for(let r=n;ro.concat([j(o[o.length-1],i,t)]),[e[0]]);return n&&r.reverse(),r}function P(e,t){let n=e>t;return n&&([e,t]=[t,e]),n?[t,e]:[e,t]}a.prototype.mudder=function(e,t,n,r,o,i=0){typeof e=="number"&&(n=e,e="",t=""),e=e||this.num2sym[0],t=t||this.num2sym[this.num2sym.length-1].repeat(e.length+6),n=typeof n=="number"?n:1,r=r||this.maxBase,o=o||n+1,[e,t]=P(e,t);let s=this.stringToDigits(e,r),c=this.stringToDigits(t,r),h=_(s,c,r,n,o).map(f=>f.res.concat(this.roundFraction(f.rem,f.den,r)));return h.unshift(s),h.push(c),C(h,i).slice(1,h.length-1).map(f=>this.digitsToString(f))};var g=(e,t)=>Array.from(Array(t),(n,r)=>String.fromCharCode(e.charCodeAt(0)+r)),B=new a(g("0",10).concat(g("A",26)).concat(g("a",26))),D=g("0",10).concat(g("a",26)),M=D.concat(g("A",26));function p(e){return Array.from(Array(e),(t,n)=>n)}var O=p(10).concat(p(26).map(e=>e+10)).concat(p(26).map(e=>e+10));function E(e,t){return Array.from(Array(e.length),(n,r)=>[e[r],t[r]])}var k=new a(D,new Map(E(M,O))),N=new a(g("a",26),new Map(E(g("a",26).concat(g("A",26)),p(26).concat(p(26)))));u.exports={SymbolTable:a,base62:B,base36:k,alphabet:N,longLinspace:_};var R,z,F,W,H;Object.isExtensible(u.exports)&&Object.keys(u.exports).length===5&&(R=u.exports.SymbolTable,z=u.exports.base62,F=u.exports.base36,W=u.exports.alphabet,H=u.exports.longLinspace);var V=u.exports;export{R as SymbolTable,W as alphabet,F as base36,z as base62,V as default,H as longLinspace}; 2 | //# sourceMappingURL=mudder.min.mjs.map 3 | -------------------------------------------------------------------------------- /dist/mudder.min.mjs.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../index.js"], 4 | "sourcesContent": [null], 5 | "mappings": "yDAAA,SAASA,EAAaC,EAAS,CAI7B,QAAWC,KAAKD,EACd,QAAWE,KAAKF,EACd,GAAIE,IAAMD,GAGNA,EAAE,WAAWC,CAAC,EAChB,MAAO,GAIb,MAAO,EACT,CAGA,SAASC,EAAsBH,EAAS,CACtCA,EAAU,MAAM,KAAKA,CAAO,EAAE,KAAK,EACnC,OAAW,CAACC,EAAGG,CAAI,IAAKJ,EAAQ,QAAQ,EAAG,CACzC,IAAMK,EAAOL,EAAQC,EAAI,CAAC,EAC1B,GAAII,IAASD,GAGTA,EAAK,WAAWC,CAAI,EACtB,MAAO,EAEX,CACA,MAAO,EACT,CAGAN,EAAeI,EAiBf,SAASG,EAAYC,EAAYC,EAAY,CAC3C,aACA,GAAI,OAAO,KAAS,IAClB,MAAM,IAAI,UAAU,kCAAkC,EAIxD,GAAI,OAAOD,GAAe,SACxBA,EAAaA,EAAW,MAAM,EAAE,UACvB,CAAC,MAAM,QAAQA,CAAU,EAClC,MAAM,IAAI,UAAU,oCAAoC,EAM1D,GAAI,OAAOC,EAAe,IACxBA,EAAa,IAAI,IAAID,EAAW,IAAI,CAACE,EAAKC,IAAQ,CAACD,EAAKC,CAAG,CAAC,CAAC,UACpDF,aAAsB,QAAU,EAAEA,aAAsB,KACjEA,EAAa,IAAI,IAAI,OAAO,QAAQA,CAAU,CAAC,UACtC,EAAEA,aAAsB,KACjC,MAAM,IAAI,UAAU,gDAAgD,EAKtE,IAAIG,EAAmB,IAAI,IAAIH,EAAW,OAAO,CAAC,EAClD,QAASP,EAAI,EAAGA,EAAIM,EAAW,OAAQN,IACrC,GAAI,CAACU,EAAiB,IAAIV,CAAC,EACzB,MAAM,IAAI,WAAWM,EAAW,OAAS,sBAAwBN,EAC5C,4BAA4B,EAIrD,KAAK,QAAUM,EACf,KAAK,QAAUC,EACf,KAAK,QAAU,KAAK,QAAQ,OAC5B,KAAK,aAAeT,EAAaQ,CAAU,CAC7C,CAGAD,EAAY,UAAU,eAAiB,SAASM,EAAKC,EAAM,CACzDA,EAAOA,GAAQ,KAAK,QACpB,IAAIC,EAAS,CAAC,EACd,KAAOF,GAAO,GACZE,EAAO,KAAKF,EAAMC,CAAI,EACtBD,EAAM,KAAK,MAAMA,EAAMC,CAAI,EAE7B,OAAOC,EAAO,OAASA,EAAO,QAAQ,EAAI,CAAE,CAAE,CAChD,EAGAR,EAAY,UAAU,eAAiB,SAASQ,EAAQ,CACtD,OAAOA,EAAO,IAAIC,GAAK,KAAK,QAAQA,CAAC,CAAC,EAAE,KAAK,EAAE,CACjD,EAGAT,EAAY,UAAU,eAAiB,SAASU,EAAQ,CACtD,GAAI,CAAC,KAAK,cAAgB,OAAOA,GAAW,SAC1C,MAAM,IAAI,UACN,sFAAsF,EAE5F,GAAI,OAAOA,GAAW,SAAU,CAC9B,IAAMC,EACF,IAAI,OAAO,IAAM,MAAM,KAAK,KAAK,QAAQ,KAAK,CAAC,EAAE,KAAK,GAAG,EAAI,IAAK,GAAG,EACzED,EAASA,EAAO,MAAMC,CAAE,CAC1B,CACA,OAAOD,EAAO,IAAIE,GAAU,KAAK,QAAQ,IAAIA,CAAM,CAAC,CACtD,EAGAZ,EAAY,UAAU,eAAiB,SAASQ,EAAQD,EAAM,CAC5DA,EAAOA,GAAQ,KAAK,QACpB,IAAIM,EAAW,EACf,OAAOL,EAAO,YAAY,CAACM,EAAOhB,IAAS,CACzC,IAAIiB,EAAMD,EAAQhB,EAAOe,EACzB,OAAAA,GAAYN,EACLQ,CACT,EAAG,CAAC,CACN,EAGAf,EAAY,UAAU,eAAiB,SAASM,EAAKC,EAAM,CACzD,OAAO,KAAK,eAAe,KAAK,eAAeD,EAAKC,CAAI,CAAC,CAC3D,EACAP,EAAY,UAAU,eAAiB,SAASM,EAAKC,EAAM,CACzD,OAAO,KAAK,eAAe,KAAK,eAAeD,CAAG,EAAGC,CAAI,CAC3D,EAGA,SAASS,EAAQC,EAAcC,EAAKX,EAAM,CACxC,OAAOU,EAAa,OAAO,CAAClB,EAAMD,IAAS,CACzC,IAAIqB,EAASrB,EAAOC,EAAK,IAAMQ,EAC/B,MAAO,CACL,IAAMR,EAAK,IAAI,OAAO,KAAK,MAAMoB,EAASD,CAAG,CAAC,EAC9C,IAAMC,EAASD,EAAK,IAAAA,CACtB,CACF,EAAG,CAAC,IAAM,CAAC,EAAG,IAAM,EAAG,IAAAA,CAAG,CAAC,CAC7B,CAYA,SAASE,EAAeC,EAAGC,EAAGf,EAAMgB,EAAM,CAAC,EAAGL,EAAM,EAAG,CACrD,GAAIG,EAAE,SAAWC,EAAE,OAAU,MAAM,IAAI,MAAM,2BAA2B,EACxE,GAAIC,EAAI,SAAW,GAAKA,EAAI,SAAW,EAAK,MAAM,IAAI,MAAM,iCAAiC,EAC7FF,EAAIA,EAAE,MAAM,EACRE,EAAI,SACNF,EAAIA,EAAE,OAAOE,EAAI,CAAC,CAAC,EACnBD,EAAIA,EAAE,MAAM,EAAE,OAAOC,EAAI,CAAC,CAAC,GAE7B,IAAMR,EAAM,MAAMM,EAAE,MAAM,EAAE,KAAK,CAAC,EAGlCG,EAAO,QAAS7B,EAAI0B,EAAE,OAAS,EAAG1B,GAAK,EAAG,EAAEA,EAAG,CAE7C,GAAI0B,EAAE1B,CAAC,GAAK2B,EAAE3B,CAAC,EAAG,CAChBoB,EAAIpB,CAAC,EAAI0B,EAAE1B,CAAC,EAAI2B,EAAE3B,CAAC,EACnB,QACF,CACA,GAAIA,IAAM,EAAK,MAAM,IAAI,MAAM,oBAAoB,EAEnD,QAASC,EAAID,EAAI,EAAGC,GAAK,EAAG,EAAEA,EAC5B,GAAIyB,EAAEzB,CAAC,EAAI,EAAG,CAEZyB,EAAEzB,CAAC,IAEH,QAAS6B,EAAI7B,EAAI,EAAG6B,EAAI9B,EAAG,EAAE8B,EAAKJ,EAAEI,CAAC,GAAKlB,EAAO,EAEjDQ,EAAIpB,CAAC,EAAI0B,EAAE1B,CAAC,GAAK4B,EAAI,OAAS,GAAK5B,IAAM0B,EAAE,OAAS,EAAIH,EAAMX,GAAQe,EAAE3B,CAAC,EACzE,SAAS6B,CACX,CAGF,MAAM,IAAI,MAAM,qCAAqC,CACvD,CACA,OAAID,EAAI,OAAiB,CAAC,IAAKR,EAAI,MAAM,EAAG,EAAE,EAAG,IAAKA,EAAIA,EAAI,OAAS,CAAC,EAAG,IAAAG,CAAG,EACvE,CAAC,IAAKH,EAAK,IAAK,EAAG,IAAAG,CAAG,CAC/B,CAUA,SAASQ,EAAeL,EAAGC,EAAGf,EAAMgB,EAAKL,EAAK,CAC5C,GAAIG,EAAE,SAAWC,EAAE,OACjB,MAAM,IAAI,MAAM,2BAA2B,EAE7C,IAAIK,EAAQJ,GAAOL,EAAKU,EAAMN,EAAE,MAAM,EACtC,OAAIK,IACFJ,GAAOL,GAETG,EAAE,YAAY,CAACQ,EAAGC,EAAInC,IAAM,CAC1B,IAAMoC,EAASD,EAAKR,EAAE3B,CAAC,EAAIgC,EAC3BA,EAAQI,GAAUxB,EAClBqB,EAAIjC,CAAC,EAAIgC,EAAQI,EAASxB,EAAOwB,CACnC,EAAG,IAAI,EACA,CAAC,IAAAH,EAAK,MAAAD,EAAO,IAAAJ,EAAK,IAAAL,CAAG,CAC9B,CAEA,SAASc,EAASC,EAAKC,EAAaC,EAAK,CACvC,IAAMC,EAAS,KAAK,IAAI,EAAGF,EAAcD,EAAI,MAAM,EACnD,OAAOA,EAAI,OAAO,MAAMG,CAAM,EAAE,KAAKD,GAAO,CAAC,CAAC,CAChD,CAWA,SAASE,EAAahB,EAAGC,EAAGf,EAAM+B,EAAGC,EAAG,CAMtC,GALIlB,EAAE,OAASC,EAAE,OACfD,EAAIW,EAASX,EAAGC,EAAE,MAAM,EACfA,EAAE,OAASD,EAAE,SACtBC,EAAIU,EAASV,EAAGD,EAAE,MAAM,GAEtBA,EAAE,SAAWC,EAAE,QAAUD,EAAE,MAAM,CAACA,EAAG1B,IAAM0B,IAAMC,EAAE3B,CAAC,CAAC,EACvD,MAAM,IAAI,MAAM,qDAAqD,EAEvE,IAAM6C,EAAOxB,EAAQK,EAAGkB,EAAGhC,CAAI,EACzBkC,EAAOzB,EAAQM,EAAGiB,EAAGhC,CAAI,EAC3BmC,EAAQtB,EAAeC,EAAGmB,EAAK,IAAKjC,EAAM,CAAC,EAAGiC,EAAK,GAAG,EAAGD,CAAC,EAC1DI,EAAQF,EACN1B,EAAM,CAAC,EACb,QAASN,EAAI,EAAGA,GAAK6B,EAAG,EAAE7B,EAAG,CAC3B,IAAMmC,EAAIlB,EAAegB,EAAM,IAAKC,EAAM,IAAKpC,EAAMmC,EAAM,IAAMC,EAAM,IAAKJ,CAAC,EAC7ExB,EAAI,KAAK6B,CAAC,EACVF,EAAQtB,EAAesB,EAAM,IAAKF,EAAK,IAAKjC,EAAM,CAACmC,EAAM,IAAKF,EAAK,GAAG,EAAGD,CAAC,EAC1EI,EAAQjB,EAAeiB,EAAM,IAAKF,EAAK,IAAKlC,EAAMoC,EAAM,IAAMF,EAAK,IAAKF,CAAC,CAC3E,CACA,OAAOxB,CACT,CACA,SAAS8B,EAAQZ,EAAKC,EAAaC,EAAK,CACtC,IAAMC,EAAS,KAAK,IAAI,EAAGF,EAAcD,EAAI,MAAM,EACnD,OAAO,MAAMG,CAAM,EAAE,KAAKD,GAAO,CAAC,EAAE,OAAOF,CAAG,CAChD,CAEAjC,EAAY,UAAU,cAAgB,SAAS8C,EAAWC,EAAaxC,EAAM,CAC3EA,EAAOA,GAAQ,KAAK,QACpB,IAAIyC,EAAS,KAAK,KAAK,KAAK,IAAID,CAAW,EAAI,KAAK,IAAIxC,CAAI,CAAC,EACzD0C,EAAQ,KAAK,IAAI1C,EAAMyC,CAAM,EAC7BE,EAAS,KAAK,MAAMJ,EAAYC,EAAcE,CAAK,EACnDzC,EAAS,KAAK,eAAe0C,EAAQ3C,CAAI,EAC7C,OAAOsC,EAAQrC,EAAQwC,EAAQ,CAAC,CAClC,EAEA,SAASG,EAAWC,EAAMC,EAAOC,EAAe,EAAG,CACjD,QAASlD,EAAMkD,EAAclD,EAAMiD,EAAM,OAAQjD,IAC/C,GAAIiD,EAAMjD,CAAG,GAAKgD,EAAKhD,CAAG,IAAMiD,EAAMjD,CAAG,EACvC,OAAOiD,EAAM,MAAM,EAAGjD,EAAM,CAAC,EAGjC,OAAOiD,CACT,CAEA,SAASE,EAA2BlC,EAAGC,EAAG,CACxC,IAAM,EAAI,KAAK,IAAID,EAAE,OAAQC,EAAE,MAAM,EACrC,QAAS3B,EAAI,EAAGA,EAAI,EAAGA,IACrB,GAAI0B,EAAE1B,CAAC,IAAM2B,EAAE3B,CAAC,EAGhB,OAAO0B,EAAE1B,CAAC,EAAI2B,EAAE3B,CAAC,EAEnB,OAAO0B,EAAE,OAASC,EAAE,MACtB,CAEA,SAASkC,EAAqB9D,EAAS4D,EAAe,EAAG,CACvD,IAAMG,EAAW,CAACF,EAA2B7D,EAAQ,CAAC,EAAGA,EAAQ,CAAC,CAAC,EAC/D+D,GACF/D,EAAQ,QAAQ,EAElB,IAAMqC,EACJrC,EAAQ,MAAM,CAAC,EAAE,OAAO,CAACoB,EAAOhB,IAASgB,EAAM,OACrB,CAAEqC,EAAWrC,EAAMA,EAAM,OAAS,CAAC,EAAGhB,EAAMwD,CAAa,CAAE,CAAC,EAC9D,CAAE5D,EAAQ,CAAC,CAAE,CAAC,EACxC,OAAI+D,GACF1B,EAAO,QAAQ,EAEVA,CACT,CAEA,SAAS2B,EAAkBC,EAAIC,EAAI,CACjC,IAAMC,EAAUF,EAAKC,EAIrB,OAHIC,IACF,CAACF,EAAIC,CAAE,EAAI,CAAEA,EAAID,CAAG,GAElBE,EACK,CAAED,EAAID,CAAG,EAEX,CAAEA,EAAIC,CAAG,CAClB,CAEA5D,EAAY,UAAU,OAAS,SAASqB,EAAGC,EAAGwC,EAAYvD,EAAMwD,EAAcT,EAAe,EAAG,CAC1F,OAAOjC,GAAM,WACfyC,EAAazC,EACbA,EAAI,GACJC,EAAI,IAEND,EAAIA,GAAK,KAAK,QAAQ,CAAC,EACvBC,EAAIA,GAAK,KAAK,QAAQ,KAAK,QAAQ,OAAS,CAAC,EAAE,OAAOD,EAAE,OAAS,CAAC,EAClEyC,EAAa,OAAOA,GAAe,SAAWA,EAAa,EAC3DvD,EAAOA,GAAQ,KAAK,QACpBwD,EAAeA,GAAgBD,EAAa,EAE5C,CAACzC,EAAGC,CAAC,EAAIoC,EAAkBrC,EAAGC,CAAC,EAC/B,IAAM0C,EAAK,KAAK,eAAe3C,EAAGd,CAAI,EAChC0D,EAAK,KAAK,eAAe3C,EAAGf,CAAI,EAElC2D,EADuB7B,EAAa2B,EAAIC,EAAI1D,EAAMuD,EAAYC,CAAY,EACzC,IACjCI,GAAKA,EAAE,IAAI,OAAO,KAAK,cAAcA,EAAE,IAAKA,EAAE,IAAK5D,CAAI,CAAC,CAAC,EAC7D,OAAA2D,EAAY,QAAQF,CAAE,EACtBE,EAAY,KAAKD,CAAE,EACZT,EAAqBU,EAAaZ,CAAY,EAChD,MAAM,EAAGY,EAAY,OAAS,CAAC,EAC/B,IAAIC,GAAK,KAAK,eAAeA,CAAC,CAAC,CACtC,EAGA,IAAIC,EAAO,CAACC,EAAMC,IAAQ,MAAM,KAC5B,MAAMA,CAAG,EAAG,CAACzC,EAAGlC,IAAM,OAAO,aAAa0E,EAAK,WAAW,CAAC,EAAI1E,CAAC,CAAC,EAEjE4E,EACA,IAAIvE,EAAYoE,EAAK,IAAK,EAAE,EAAE,OAAOA,EAAK,IAAK,EAAE,CAAC,EAAE,OAAOA,EAAK,IAAK,EAAE,CAAC,CAAC,EAGzEI,EAAYJ,EAAK,IAAK,EAAE,EAAE,OAAOA,EAAK,IAAK,EAAE,CAAC,EAC9CK,EAAaD,EAAU,OAAOJ,EAAK,IAAK,EAAE,CAAC,EAC/C,SAASM,EAAMjE,EAAG,CAAE,OAAO,MAAM,KAAK,MAAMA,CAAC,EAAG,CAACoB,EAAGlC,IAAMA,CAAC,CAAG,CAC9D,IAAIgF,EAAaD,EAAM,EAAE,EACH,OAAOA,EAAM,EAAE,EAAE,IAAI/E,GAAKA,EAAI,EAAE,CAAC,EACjC,OAAO+E,EAAM,EAAE,EAAE,IAAI/E,GAAKA,EAAI,EAAE,CAAC,EACvD,SAASiF,EAAIvD,EAAGC,EAAG,CACjB,OAAO,MAAM,KAAK,MAAMD,EAAE,MAAM,EAAG,CAACQ,EAAGlC,IAAM,CAAC0B,EAAE1B,CAAC,EAAG2B,EAAE3B,CAAC,CAAC,CAAC,CAC3D,CACA,IAAIkF,EAAS,IAAI7E,EAAYwE,EAAW,IAAI,IAAII,EAAIH,EAAYE,CAAU,CAAC,CAAC,EAExEG,EAAW,IAAI9E,EAAYoE,EAAK,IAAK,EAAE,EACZ,IAAI,IAAIQ,EAAIR,EAAK,IAAK,EAAE,EAAE,OAAOA,EAAK,IAAK,EAAE,CAAC,EAClCM,EAAM,EAAE,EAAE,OAAOA,EAAM,EAAE,CAAC,CAAC,CAAC,CAAC,EAIxEK,EAAO,QAAU,CAAC,YAAA/E,EAAa,OAAAuE,EAAQ,OAAAM,EAAQ,SAAAC,EAAU,aAAAzC,CAAY", 6 | "names": ["isPrefixCode", "strings", "i", "j", "isPrefixCodeLogLinear", "curr", "prev", "SymbolTable", "symbolsArr", "symbolsMap", "str", "idx", "symbolsValuesSet", "num", "base", "digits", "n", "string", "re", "symbol", "currBase", "accum", "ret", "longDiv", "numeratorArr", "den", "newNum", "longSubSameLen", "a", "b", "rem", "OUTER", "k", "longAddSameLen", "carry", "res", "_", "ai", "result", "rightpad", "arr", "finalLength", "val", "padlen", "longLinspace", "N", "M", "aDiv", "bDiv", "aPrev", "bPrev", "x", "leftpad", "numerator", "denominator", "places", "scale", "scaled", "chopDigits", "rock", "water", "placesToKeep", "lexicographicLessThanArray", "chopSuccessiveDigits", "reversed", "truncateLexHigher", "lo", "hi", "swapped", "numStrings", "numDivisions", "ad", "bd", "finalDigits", "v", "iter", "char", "len", "base62", "base36arr", "base36keys", "range", "base36vals", "zip", "base36", "alphabet", "module"] 7 | } 8 | -------------------------------------------------------------------------------- /esbuild.mjs: -------------------------------------------------------------------------------- 1 | import { build } from "esbuild"; 2 | import commonjsPlugin from "@chialab/esbuild-plugin-commonjs"; 3 | 4 | // Build IIFE 5 | await build({ 6 | entryPoints: ["index.js"], 7 | outfile: "dist/mudder.min.js", 8 | sourcemap: true, 9 | minify: true, 10 | format: "iife", 11 | }); 12 | 13 | // Build Node commonjs 14 | await build({ 15 | entryPoints: ["index.js"], 16 | outfile: "dist/mudder.cjs", 17 | format: "cjs", 18 | }); 19 | 20 | // Build ESM module 21 | await build({ 22 | plugins: [commonjsPlugin()], 23 | entryPoints: ["index.js"], 24 | outfile: "dist/mudder.min.mjs", 25 | bundle: true, 26 | sourcemap: true, 27 | minify: true, 28 | format: "esm", 29 | target: ["es2021"], 30 | }); 31 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | function isPrefixCode(strings) { 2 | // Note: we skip checking for prefixness if two symbols are equal to each 3 | // other. This implies that repeated symbols in the input are *silently 4 | // ignored*! 5 | for (const i of strings) { 6 | for (const j of strings) { 7 | if (j === i) { // [🍅] 8 | continue; 9 | } 10 | if (i.startsWith(j)) { 11 | return false; 12 | } 13 | } 14 | } 15 | return true; 16 | } 17 | // < export mudder.js 18 | 19 | function isPrefixCodeLogLinear(strings) { 20 | strings = Array.from(strings).sort(); // set->array or array->copy 21 | for (const [i, curr] of strings.entries()) { 22 | const prev = strings[i - 1]; // undefined for first iteration 23 | if (prev === curr) { // Skip repeated entries, match quadratic API 24 | continue; 25 | } 26 | if (curr.startsWith(prev)) { // str.startsWith(undefined) always false 27 | return false; 28 | }; 29 | } 30 | return true; 31 | } 32 | // < export mudder.js 33 | 34 | isPrefixCode = isPrefixCodeLogLinear; 35 | // < export mudder.js 36 | 37 | /* Constructor: 38 | symbolsArr is a string (split into an array) or an array. In either case, it 39 | maps numbers (array indexes) to stringy symbols. Its length defines the max 40 | radix the symbol table can handle. 41 | 42 | symbolsMap is optional, but goes the other way, so it can be an object or Map. 43 | Its keys are stringy symbols and its values are numbers. If omitted, the 44 | implied map goes from the indexes of symbolsArr to the symbols. 45 | 46 | When symbolsMap is provided, its values are checked to ensure that each number 47 | from 0 to max radix minus one is present. If you had a symbol as an entry in 48 | symbolsArr, then number->string would use that symbol, but the resulting 49 | string couldn't be parsed because that symbol wasn't in symbolMap. 50 | */ 51 | function SymbolTable(symbolsArr, symbolsMap) { 52 | 'use strict'; // [⛈] 53 | if (typeof this === 'undefined') { 54 | throw new TypeError('constructor called as a function') 55 | }; 56 | 57 | // Condition the input `symbolsArr` 58 | if (typeof symbolsArr === 'string') { 59 | symbolsArr = symbolsArr.split(''); 60 | } else if (!Array.isArray(symbolsArr)) { 61 | throw new TypeError('symbolsArr must be string or array'); 62 | } 63 | 64 | // Condition the second input, `symbolsMap`. If no symbolsMap passed in, make 65 | // it by inverting symbolsArr. If it's an object (and not a Map), convert its 66 | // own-properties to a Map. 67 | if (typeof symbolsMap === 'undefined') { 68 | symbolsMap = new Map(symbolsArr.map((str, idx) => [str, idx])); 69 | } else if (symbolsMap instanceof Object && !(symbolsMap instanceof Map)) { 70 | symbolsMap = new Map(Object.entries(symbolsMap)); 71 | } else if (!(symbolsMap instanceof Map) ){ 72 | throw new TypeError('symbolsMap can be omitted, a Map, or an Object'); 73 | } 74 | 75 | // Ensure that each integer from 0 to `symbolsArr.length - 1` is a value in 76 | // `symbolsMap` 77 | let symbolsValuesSet = new Set(symbolsMap.values()); 78 | for (let i = 0; i < symbolsArr.length; i++) { 79 | if (!symbolsValuesSet.has(i)) { 80 | throw new RangeError(symbolsArr.length + ' symbols given but ' + i + 81 | ' not found in symbol table'); 82 | } 83 | } 84 | 85 | this.num2sym = symbolsArr; 86 | this.sym2num = symbolsMap; 87 | this.maxBase = this.num2sym.length; 88 | this.isPrefixCode = isPrefixCode(symbolsArr); 89 | } 90 | // < export mudder.js 91 | 92 | SymbolTable.prototype.numberToDigits = function(num, base) { 93 | base = base || this.maxBase; 94 | let digits = []; 95 | while (num >= 1) { 96 | digits.push(num % base); 97 | num = Math.floor(num / base); 98 | } 99 | return digits.length ? digits.reverse() : [ 0 ]; 100 | }; 101 | // < export mudder.js 102 | 103 | SymbolTable.prototype.digitsToString = function(digits) { 104 | return digits.map(n => this.num2sym[n]).join(''); 105 | }; 106 | // < export mudder.js 107 | 108 | SymbolTable.prototype.stringToDigits = function(string) { 109 | if (!this.isPrefixCode && typeof string === 'string') { 110 | throw new TypeError( 111 | 'parsing string without prefix code is unsupported. Pass in array of stringy symbols?'); 112 | } 113 | if (typeof string === 'string') { 114 | const re = 115 | new RegExp('(' + Array.from(this.sym2num.keys()).join('|') + ')', 'g'); 116 | string = string.match(re); 117 | } 118 | return string.map(symbol => this.sym2num.get(symbol)); 119 | }; 120 | // < export mudder.js 121 | 122 | SymbolTable.prototype.digitsToNumber = function(digits, base) { 123 | base = base || this.maxBase; 124 | let currBase = 1; 125 | return digits.reduceRight((accum, curr) => { 126 | let ret = accum + curr * currBase; 127 | currBase *= base; 128 | return ret; 129 | }, 0); 130 | }; 131 | // < export mudder.js 132 | 133 | SymbolTable.prototype.numberToString = function(num, base) { 134 | return this.digitsToString(this.numberToDigits(num, base)); 135 | }; 136 | SymbolTable.prototype.stringToNumber = function(num, base) { 137 | return this.digitsToNumber(this.stringToDigits(num), base); 138 | }; 139 | // < export mudder.js 140 | 141 | function longDiv(numeratorArr, den, base) { 142 | return numeratorArr.reduce((prev, curr) => { 143 | let newNum = curr + prev.rem * base; 144 | return { 145 | res : prev.res.concat(Math.floor(newNum / den)), 146 | rem : newNum % den, den 147 | }; 148 | }, {res : [], rem : 0, den}); 149 | } 150 | // < export mudder.js 151 | 152 | /** 153 | * 154 | * @param {number[]} a larger number, as digits array 155 | * @param {number[]} b smaller number, as digits array 156 | * @param {number} base 157 | * @param {[number, number]} rem `a` and `b`'s remainders 158 | * @param {number} den denominator for the remainders 159 | * @returns {{res: number[], den: number, rem: number}} 160 | */ 161 | function longSubSameLen(a, b, base, rem = [], den = 0) { 162 | if (a.length !== b.length) { throw new Error('same length arrays needed'); } 163 | if (rem.length !== 0 && rem.length !== 2) { throw new Error('zero or two remainders expected'); } 164 | a = a.slice(); // pre-emptively copy 165 | if (rem.length) { 166 | a = a.concat(rem[0]); 167 | b = b.slice().concat(rem[1]); 168 | } 169 | const ret = Array(a.length).fill(0); 170 | 171 | // this is a LOOP LABEL! https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label 172 | OUTER: for (let i = a.length - 1; i >= 0; --i) { 173 | // console.log({a, ret}) 174 | if (a[i] >= b[i]) { 175 | ret[i] = a[i] - b[i]; 176 | continue; 177 | } 178 | if (i === 0) { throw new Error('cannot go negative'); } 179 | // look for a digit to the left to borrow from 180 | for (let j = i - 1; j >= 0; --j) { 181 | if (a[j] > 0) { 182 | // found a non-zero digit. Decrement it 183 | a[j]--; 184 | // increment digits to its right by `base-1` 185 | for (let k = j + 1; k < i; ++k) { a[k] += base - 1; } 186 | // until you reach the digit you couldn't subtract 187 | ret[i] = a[i] + (rem.length > 0 && i === a.length - 1 ? den : base) - b[i]; 188 | continue OUTER; 189 | } 190 | } 191 | // should have `continue`d `OUTER` loop 192 | throw new Error('failed to find digit to borrow from'); 193 | } 194 | if (rem.length) { return {res: ret.slice(0, -1), rem: ret[ret.length - 1], den}; } 195 | return {res: ret, rem: 0, den}; 196 | } 197 | 198 | /** 199 | * 200 | * @param {number[]} a array of digits 201 | * @param {number[]} b array of digits 202 | * @param {number} base 203 | * @param {number} rem remainder 204 | * @param {number} den denominator under remainder 205 | */ 206 | function longAddSameLen(a, b, base, rem, den) { 207 | if (a.length !== b.length) { 208 | throw new Error('same length arrays needed'); 209 | } 210 | let carry = rem >= den, res = b.slice(); 211 | if (carry) { 212 | rem -= den; 213 | } 214 | a.reduceRight((_, ai, i) => { 215 | const result = ai + b[i] + carry; 216 | carry = result >= base; 217 | res[i] = carry ? result - base : result; 218 | }, null); 219 | return {res, carry, rem, den}; 220 | }; 221 | 222 | function rightpad(arr, finalLength, val) { 223 | const padlen = Math.max(0, finalLength - arr.length); 224 | return arr.concat(Array(padlen).fill(val || 0)); 225 | } 226 | 227 | /** 228 | * Returns `(a + (b-a)/M*n)` for n=[1, 2, ..., N], where `NN` 234 | * @returns {{res: number[]; rem: number; den: number;}[]} `N` numbers 235 | */ 236 | function longLinspace(a, b, base, N, M) { 237 | if (a.length < b.length) { 238 | a = rightpad(a, b.length); 239 | } else if (b.length < a.length) { 240 | b = rightpad(b, a.length); 241 | } 242 | if (a.length === b.length && a.every((a, i) => a === b[i])) { 243 | throw new Error('Start and end strings lexicographically inseparable'); 244 | } 245 | const aDiv = longDiv(a, M, base); 246 | const bDiv = longDiv(b, M, base); 247 | let aPrev = longSubSameLen(a, aDiv.res, base, [0, aDiv.rem], M); 248 | let bPrev = bDiv; 249 | const ret = []; 250 | for (let n = 1; n <= N; ++n) { 251 | const x = longAddSameLen(aPrev.res, bPrev.res, base, aPrev.rem + bPrev.rem, M); 252 | ret.push(x); 253 | aPrev = longSubSameLen(aPrev.res, aDiv.res, base, [aPrev.rem, aDiv.rem], M); 254 | bPrev = longAddSameLen(bPrev.res, bDiv.res, base, bPrev.rem + bDiv.rem, M); 255 | } 256 | return ret; 257 | } 258 | function leftpad(arr, finalLength, val) { 259 | const padlen = Math.max(0, finalLength - arr.length); 260 | return Array(padlen).fill(val || 0).concat(arr); 261 | } 262 | 263 | SymbolTable.prototype.roundFraction = function(numerator, denominator, base) { 264 | base = base || this.maxBase; 265 | var places = Math.ceil(Math.log(denominator) / Math.log(base)); 266 | var scale = Math.pow(base, places); 267 | var scaled = Math.round(numerator / denominator * scale); 268 | var digits = this.numberToDigits(scaled, base); 269 | return leftpad(digits, places, 0); 270 | }; 271 | 272 | function chopDigits(rock, water, placesToKeep = 0) { 273 | for (let idx = placesToKeep; idx < water.length; idx++) { 274 | if (water[idx] && rock[idx] !== water[idx]) { 275 | return water.slice(0, idx + 1); 276 | } 277 | } 278 | return water; 279 | } 280 | 281 | function lexicographicLessThanArray(a, b) { 282 | const n = Math.min(a.length, b.length); 283 | for (let i = 0; i < n; i++) { 284 | if (a[i] === b[i]) { 285 | continue; 286 | } 287 | return a[i] < b[i]; 288 | } 289 | return a.length < b.length; 290 | } 291 | 292 | function chopSuccessiveDigits(strings, placesToKeep = 0) { 293 | const reversed = !lexicographicLessThanArray(strings[0], strings[1]); 294 | if (reversed) { 295 | strings.reverse(); 296 | } 297 | const result = 298 | strings.slice(1).reduce((accum, curr) => accum.concat( 299 | [ chopDigits(accum[accum.length - 1], curr, placesToKeep ) ]), 300 | [ strings[0] ]); 301 | if (reversed) { 302 | result.reverse(); 303 | } 304 | return result; 305 | } 306 | 307 | function truncateLexHigher(lo, hi) { 308 | const swapped = lo > hi; 309 | if (swapped) { 310 | [lo, hi] = [ hi, lo ]; 311 | } 312 | if (swapped) { 313 | return [ hi, lo ]; 314 | } 315 | return [ lo, hi ]; 316 | } 317 | 318 | SymbolTable.prototype.mudder = function(a, b, numStrings, base, numDivisions, placesToKeep = 0) { 319 | if (typeof a === 'number'){ 320 | numStrings = a; 321 | a = ''; 322 | b = ''; 323 | } 324 | a = a || this.num2sym[0]; 325 | b = b || this.num2sym[this.num2sym.length - 1].repeat(a.length + 6); 326 | numStrings = typeof numStrings === 'number' ? numStrings : 1; 327 | base = base || this.maxBase; 328 | numDivisions = numDivisions || numStrings + 1; 329 | 330 | [a, b] = truncateLexHigher(a, b); 331 | const ad = this.stringToDigits(a, base); 332 | const bd = this.stringToDigits(b, base); 333 | const intermediateDigits = longLinspace(ad, bd, base, numStrings, numDivisions); 334 | let finalDigits = intermediateDigits.map( 335 | v => v.res.concat(this.roundFraction(v.rem, v.den, base))); 336 | finalDigits.unshift(ad); 337 | finalDigits.push(bd); 338 | return chopSuccessiveDigits(finalDigits, placesToKeep) 339 | .slice(1, finalDigits.length - 1) 340 | .map(v => this.digitsToString(v)); 341 | }; 342 | // < export mudder.js 343 | 344 | var iter = (char, len) => Array.from( 345 | Array(len), (_, i) => String.fromCharCode(char.charCodeAt(0) + i)); 346 | 347 | var base62 = 348 | new SymbolTable(iter('0', 10).concat(iter('A', 26)).concat(iter('a', 26))); 349 | 350 | // Base36 should use lowercase since that’s what Number.toString outputs. 351 | var base36arr = iter('0', 10).concat(iter('a', 26)); 352 | var base36keys = base36arr.concat(iter('A', 26)); 353 | function range(n) { return Array.from(Array(n), (_, i) => i); } 354 | var base36vals = range(10) 355 | .concat(range(26).map(i => i + 10)) 356 | .concat(range(26).map(i => i + 10)); 357 | function zip(a, b) { 358 | return Array.from(Array(a.length), (_, i) => [a[i], b[i]]); 359 | } 360 | var base36 = new SymbolTable(base36arr, new Map(zip(base36keys, base36vals))); 361 | 362 | var alphabet = new SymbolTable(iter('a', 26), 363 | new Map(zip(iter('a', 26).concat(iter('A', 26)), 364 | range(26).concat(range(26))))); 365 | 366 | // < export mudder.js 367 | 368 | module.exports = {SymbolTable, base62, base36, alphabet, longLinspace}; 369 | // < export mudder.js 370 | -------------------------------------------------------------------------------- /jayne.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fasiha/mudderjs/d83942d39c5d8525a180b3615738a4a8272e2126/jayne.jpg -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mudder", 3 | "version": "2.1.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "mudder", 9 | "version": "2.1.0", 10 | "license": "Unlicense", 11 | "devDependencies": { 12 | "@chialab/esbuild-plugin-commonjs": "^0.17.2", 13 | "esbuild": "^0.16.17", 14 | "tape": "^4.6.0" 15 | } 16 | }, 17 | "node_modules/@chialab/cjs-to-esm": { 18 | "version": "0.17.6", 19 | "resolved": "https://registry.npmjs.org/@chialab/cjs-to-esm/-/cjs-to-esm-0.17.6.tgz", 20 | "integrity": "sha512-3L/zkOeRK/BTHjo06nMj5KoOec9EU1kVApCSHcJppr22F+FjjvQzY/zdt7safvx3LL7VvArQFSMIT7HlHFax/A==", 21 | "dev": true, 22 | "dependencies": { 23 | "@chialab/estransform": "^0.17.3" 24 | }, 25 | "engines": { 26 | "node": ">=13" 27 | } 28 | }, 29 | "node_modules/@chialab/esbuild-plugin-commonjs": { 30 | "version": "0.17.2", 31 | "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-commonjs/-/esbuild-plugin-commonjs-0.17.2.tgz", 32 | "integrity": "sha512-C30UShSb89PJiO+mJTwAS1ei6aKVxMcxZQrl0g1JAJVd4d1AE6OjcbYlCs4847PcbmMNqYZrTcWpgIg+zRQL3Q==", 33 | "dev": true, 34 | "dependencies": { 35 | "@chialab/cjs-to-esm": "^0.17.0", 36 | "@chialab/esbuild-rna": "^0.17.0", 37 | "@chialab/node-resolve": "^0.17.0" 38 | }, 39 | "engines": { 40 | "node": ">=13" 41 | } 42 | }, 43 | "node_modules/@chialab/esbuild-rna": { 44 | "version": "0.17.3", 45 | "resolved": "https://registry.npmjs.org/@chialab/esbuild-rna/-/esbuild-rna-0.17.3.tgz", 46 | "integrity": "sha512-LwPGDBeRnCNSkUfCEXYWKf7iORt6JCbmCLvEaeZnqle2Tm5yNRtZjCnQHdG9D4UpY9YQgrlZQnF785yOnKwL7Q==", 47 | "dev": true, 48 | "dependencies": { 49 | "@chialab/estransform": "^0.17.2", 50 | "@chialab/node-resolve": "^0.17.0" 51 | }, 52 | "engines": { 53 | "node": ">=13" 54 | } 55 | }, 56 | "node_modules/@chialab/estransform": { 57 | "version": "0.17.3", 58 | "resolved": "https://registry.npmjs.org/@chialab/estransform/-/estransform-0.17.3.tgz", 59 | "integrity": "sha512-ZFCcDk85ZllhUiAMIzRFsLhrA2z8xwRyXyJBNMP/84+3czLEydSTzzBK9nXNGloOspwp+lL+EgPlFY8Ry693Bg==", 60 | "dev": true, 61 | "dependencies": { 62 | "@parcel/source-map": "^2.0.0" 63 | }, 64 | "engines": { 65 | "node": ">=13" 66 | } 67 | }, 68 | "node_modules/@chialab/node-resolve": { 69 | "version": "0.17.0", 70 | "resolved": "https://registry.npmjs.org/@chialab/node-resolve/-/node-resolve-0.17.0.tgz", 71 | "integrity": "sha512-u9VwO/0djjXsM70aH/Wrx0BrXjtjhfNOxaDks32xa1mZP9T0CXZ0be5Sv3hXI9g5UeJbfvCyFJS2hfJEu7Ifgg==", 72 | "dev": true, 73 | "engines": { 74 | "node": ">=13" 75 | } 76 | }, 77 | "node_modules/@esbuild/android-arm": { 78 | "version": "0.16.17", 79 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", 80 | "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", 81 | "cpu": [ 82 | "arm" 83 | ], 84 | "dev": true, 85 | "optional": true, 86 | "os": [ 87 | "android" 88 | ], 89 | "engines": { 90 | "node": ">=12" 91 | } 92 | }, 93 | "node_modules/@esbuild/android-arm64": { 94 | "version": "0.16.17", 95 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", 96 | "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", 97 | "cpu": [ 98 | "arm64" 99 | ], 100 | "dev": true, 101 | "optional": true, 102 | "os": [ 103 | "android" 104 | ], 105 | "engines": { 106 | "node": ">=12" 107 | } 108 | }, 109 | "node_modules/@esbuild/android-x64": { 110 | "version": "0.16.17", 111 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", 112 | "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", 113 | "cpu": [ 114 | "x64" 115 | ], 116 | "dev": true, 117 | "optional": true, 118 | "os": [ 119 | "android" 120 | ], 121 | "engines": { 122 | "node": ">=12" 123 | } 124 | }, 125 | "node_modules/@esbuild/darwin-arm64": { 126 | "version": "0.16.17", 127 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", 128 | "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", 129 | "cpu": [ 130 | "arm64" 131 | ], 132 | "dev": true, 133 | "optional": true, 134 | "os": [ 135 | "darwin" 136 | ], 137 | "engines": { 138 | "node": ">=12" 139 | } 140 | }, 141 | "node_modules/@esbuild/darwin-x64": { 142 | "version": "0.16.17", 143 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", 144 | "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", 145 | "cpu": [ 146 | "x64" 147 | ], 148 | "dev": true, 149 | "optional": true, 150 | "os": [ 151 | "darwin" 152 | ], 153 | "engines": { 154 | "node": ">=12" 155 | } 156 | }, 157 | "node_modules/@esbuild/freebsd-arm64": { 158 | "version": "0.16.17", 159 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", 160 | "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", 161 | "cpu": [ 162 | "arm64" 163 | ], 164 | "dev": true, 165 | "optional": true, 166 | "os": [ 167 | "freebsd" 168 | ], 169 | "engines": { 170 | "node": ">=12" 171 | } 172 | }, 173 | "node_modules/@esbuild/freebsd-x64": { 174 | "version": "0.16.17", 175 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", 176 | "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", 177 | "cpu": [ 178 | "x64" 179 | ], 180 | "dev": true, 181 | "optional": true, 182 | "os": [ 183 | "freebsd" 184 | ], 185 | "engines": { 186 | "node": ">=12" 187 | } 188 | }, 189 | "node_modules/@esbuild/linux-arm": { 190 | "version": "0.16.17", 191 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", 192 | "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", 193 | "cpu": [ 194 | "arm" 195 | ], 196 | "dev": true, 197 | "optional": true, 198 | "os": [ 199 | "linux" 200 | ], 201 | "engines": { 202 | "node": ">=12" 203 | } 204 | }, 205 | "node_modules/@esbuild/linux-arm64": { 206 | "version": "0.16.17", 207 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", 208 | "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", 209 | "cpu": [ 210 | "arm64" 211 | ], 212 | "dev": true, 213 | "optional": true, 214 | "os": [ 215 | "linux" 216 | ], 217 | "engines": { 218 | "node": ">=12" 219 | } 220 | }, 221 | "node_modules/@esbuild/linux-ia32": { 222 | "version": "0.16.17", 223 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", 224 | "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", 225 | "cpu": [ 226 | "ia32" 227 | ], 228 | "dev": true, 229 | "optional": true, 230 | "os": [ 231 | "linux" 232 | ], 233 | "engines": { 234 | "node": ">=12" 235 | } 236 | }, 237 | "node_modules/@esbuild/linux-loong64": { 238 | "version": "0.16.17", 239 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", 240 | "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", 241 | "cpu": [ 242 | "loong64" 243 | ], 244 | "dev": true, 245 | "optional": true, 246 | "os": [ 247 | "linux" 248 | ], 249 | "engines": { 250 | "node": ">=12" 251 | } 252 | }, 253 | "node_modules/@esbuild/linux-mips64el": { 254 | "version": "0.16.17", 255 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", 256 | "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", 257 | "cpu": [ 258 | "mips64el" 259 | ], 260 | "dev": true, 261 | "optional": true, 262 | "os": [ 263 | "linux" 264 | ], 265 | "engines": { 266 | "node": ">=12" 267 | } 268 | }, 269 | "node_modules/@esbuild/linux-ppc64": { 270 | "version": "0.16.17", 271 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", 272 | "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", 273 | "cpu": [ 274 | "ppc64" 275 | ], 276 | "dev": true, 277 | "optional": true, 278 | "os": [ 279 | "linux" 280 | ], 281 | "engines": { 282 | "node": ">=12" 283 | } 284 | }, 285 | "node_modules/@esbuild/linux-riscv64": { 286 | "version": "0.16.17", 287 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", 288 | "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", 289 | "cpu": [ 290 | "riscv64" 291 | ], 292 | "dev": true, 293 | "optional": true, 294 | "os": [ 295 | "linux" 296 | ], 297 | "engines": { 298 | "node": ">=12" 299 | } 300 | }, 301 | "node_modules/@esbuild/linux-s390x": { 302 | "version": "0.16.17", 303 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", 304 | "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", 305 | "cpu": [ 306 | "s390x" 307 | ], 308 | "dev": true, 309 | "optional": true, 310 | "os": [ 311 | "linux" 312 | ], 313 | "engines": { 314 | "node": ">=12" 315 | } 316 | }, 317 | "node_modules/@esbuild/linux-x64": { 318 | "version": "0.16.17", 319 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", 320 | "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", 321 | "cpu": [ 322 | "x64" 323 | ], 324 | "dev": true, 325 | "optional": true, 326 | "os": [ 327 | "linux" 328 | ], 329 | "engines": { 330 | "node": ">=12" 331 | } 332 | }, 333 | "node_modules/@esbuild/netbsd-x64": { 334 | "version": "0.16.17", 335 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", 336 | "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", 337 | "cpu": [ 338 | "x64" 339 | ], 340 | "dev": true, 341 | "optional": true, 342 | "os": [ 343 | "netbsd" 344 | ], 345 | "engines": { 346 | "node": ">=12" 347 | } 348 | }, 349 | "node_modules/@esbuild/openbsd-x64": { 350 | "version": "0.16.17", 351 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", 352 | "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", 353 | "cpu": [ 354 | "x64" 355 | ], 356 | "dev": true, 357 | "optional": true, 358 | "os": [ 359 | "openbsd" 360 | ], 361 | "engines": { 362 | "node": ">=12" 363 | } 364 | }, 365 | "node_modules/@esbuild/sunos-x64": { 366 | "version": "0.16.17", 367 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", 368 | "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", 369 | "cpu": [ 370 | "x64" 371 | ], 372 | "dev": true, 373 | "optional": true, 374 | "os": [ 375 | "sunos" 376 | ], 377 | "engines": { 378 | "node": ">=12" 379 | } 380 | }, 381 | "node_modules/@esbuild/win32-arm64": { 382 | "version": "0.16.17", 383 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", 384 | "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", 385 | "cpu": [ 386 | "arm64" 387 | ], 388 | "dev": true, 389 | "optional": true, 390 | "os": [ 391 | "win32" 392 | ], 393 | "engines": { 394 | "node": ">=12" 395 | } 396 | }, 397 | "node_modules/@esbuild/win32-ia32": { 398 | "version": "0.16.17", 399 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", 400 | "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", 401 | "cpu": [ 402 | "ia32" 403 | ], 404 | "dev": true, 405 | "optional": true, 406 | "os": [ 407 | "win32" 408 | ], 409 | "engines": { 410 | "node": ">=12" 411 | } 412 | }, 413 | "node_modules/@esbuild/win32-x64": { 414 | "version": "0.16.17", 415 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", 416 | "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", 417 | "cpu": [ 418 | "x64" 419 | ], 420 | "dev": true, 421 | "optional": true, 422 | "os": [ 423 | "win32" 424 | ], 425 | "engines": { 426 | "node": ">=12" 427 | } 428 | }, 429 | "node_modules/@parcel/source-map": { 430 | "version": "2.1.1", 431 | "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", 432 | "integrity": "sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==", 433 | "dev": true, 434 | "dependencies": { 435 | "detect-libc": "^1.0.3" 436 | }, 437 | "engines": { 438 | "node": "^12.18.3 || >=14" 439 | } 440 | }, 441 | "node_modules/balanced-match": { 442 | "version": "1.0.0", 443 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 444 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 445 | "dev": true 446 | }, 447 | "node_modules/brace-expansion": { 448 | "version": "1.1.11", 449 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 450 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 451 | "dev": true, 452 | "dependencies": { 453 | "balanced-match": "^1.0.0", 454 | "concat-map": "0.0.1" 455 | } 456 | }, 457 | "node_modules/concat-map": { 458 | "version": "0.0.1", 459 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 460 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 461 | "dev": true 462 | }, 463 | "node_modules/deep-equal": { 464 | "version": "1.0.1", 465 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", 466 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", 467 | "dev": true 468 | }, 469 | "node_modules/define-properties": { 470 | "version": "1.1.2", 471 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", 472 | "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", 473 | "dev": true, 474 | "dependencies": { 475 | "foreach": "^2.0.5", 476 | "object-keys": "^1.0.8" 477 | }, 478 | "engines": { 479 | "node": ">= 0.4" 480 | } 481 | }, 482 | "node_modules/defined": { 483 | "version": "1.0.0", 484 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 485 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 486 | "dev": true 487 | }, 488 | "node_modules/detect-libc": { 489 | "version": "1.0.3", 490 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 491 | "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", 492 | "dev": true, 493 | "bin": { 494 | "detect-libc": "bin/detect-libc.js" 495 | }, 496 | "engines": { 497 | "node": ">=0.10" 498 | } 499 | }, 500 | "node_modules/es-abstract": { 501 | "version": "1.12.0", 502 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", 503 | "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", 504 | "dev": true, 505 | "dependencies": { 506 | "es-to-primitive": "^1.1.1", 507 | "function-bind": "^1.1.1", 508 | "has": "^1.0.1", 509 | "is-callable": "^1.1.3", 510 | "is-regex": "^1.0.4" 511 | }, 512 | "engines": { 513 | "node": ">= 0.4" 514 | } 515 | }, 516 | "node_modules/es-to-primitive": { 517 | "version": "1.1.1", 518 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", 519 | "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", 520 | "dev": true, 521 | "dependencies": { 522 | "is-callable": "^1.1.1", 523 | "is-date-object": "^1.0.1", 524 | "is-symbol": "^1.0.1" 525 | }, 526 | "engines": { 527 | "node": ">= 0.4" 528 | } 529 | }, 530 | "node_modules/esbuild": { 531 | "version": "0.16.17", 532 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", 533 | "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", 534 | "dev": true, 535 | "hasInstallScript": true, 536 | "bin": { 537 | "esbuild": "bin/esbuild" 538 | }, 539 | "engines": { 540 | "node": ">=12" 541 | }, 542 | "optionalDependencies": { 543 | "@esbuild/android-arm": "0.16.17", 544 | "@esbuild/android-arm64": "0.16.17", 545 | "@esbuild/android-x64": "0.16.17", 546 | "@esbuild/darwin-arm64": "0.16.17", 547 | "@esbuild/darwin-x64": "0.16.17", 548 | "@esbuild/freebsd-arm64": "0.16.17", 549 | "@esbuild/freebsd-x64": "0.16.17", 550 | "@esbuild/linux-arm": "0.16.17", 551 | "@esbuild/linux-arm64": "0.16.17", 552 | "@esbuild/linux-ia32": "0.16.17", 553 | "@esbuild/linux-loong64": "0.16.17", 554 | "@esbuild/linux-mips64el": "0.16.17", 555 | "@esbuild/linux-ppc64": "0.16.17", 556 | "@esbuild/linux-riscv64": "0.16.17", 557 | "@esbuild/linux-s390x": "0.16.17", 558 | "@esbuild/linux-x64": "0.16.17", 559 | "@esbuild/netbsd-x64": "0.16.17", 560 | "@esbuild/openbsd-x64": "0.16.17", 561 | "@esbuild/sunos-x64": "0.16.17", 562 | "@esbuild/win32-arm64": "0.16.17", 563 | "@esbuild/win32-ia32": "0.16.17", 564 | "@esbuild/win32-x64": "0.16.17" 565 | } 566 | }, 567 | "node_modules/for-each": { 568 | "version": "0.3.3", 569 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 570 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 571 | "dev": true, 572 | "dependencies": { 573 | "is-callable": "^1.1.3" 574 | } 575 | }, 576 | "node_modules/foreach": { 577 | "version": "2.0.5", 578 | "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", 579 | "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", 580 | "dev": true 581 | }, 582 | "node_modules/fs.realpath": { 583 | "version": "1.0.0", 584 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 585 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 586 | "dev": true 587 | }, 588 | "node_modules/function-bind": { 589 | "version": "1.1.1", 590 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 591 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 592 | "dev": true 593 | }, 594 | "node_modules/glob": { 595 | "version": "7.1.2", 596 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 597 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 598 | "dev": true, 599 | "dependencies": { 600 | "fs.realpath": "^1.0.0", 601 | "inflight": "^1.0.4", 602 | "inherits": "2", 603 | "minimatch": "^3.0.4", 604 | "once": "^1.3.0", 605 | "path-is-absolute": "^1.0.0" 606 | }, 607 | "engines": { 608 | "node": "*" 609 | } 610 | }, 611 | "node_modules/has": { 612 | "version": "1.0.3", 613 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 614 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 615 | "dev": true, 616 | "dependencies": { 617 | "function-bind": "^1.1.1" 618 | }, 619 | "engines": { 620 | "node": ">= 0.4.0" 621 | } 622 | }, 623 | "node_modules/inflight": { 624 | "version": "1.0.6", 625 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 626 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 627 | "dev": true, 628 | "dependencies": { 629 | "once": "^1.3.0", 630 | "wrappy": "1" 631 | } 632 | }, 633 | "node_modules/inherits": { 634 | "version": "2.0.3", 635 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 636 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 637 | "dev": true 638 | }, 639 | "node_modules/is-callable": { 640 | "version": "1.1.4", 641 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 642 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", 643 | "dev": true, 644 | "engines": { 645 | "node": ">= 0.4" 646 | } 647 | }, 648 | "node_modules/is-date-object": { 649 | "version": "1.0.1", 650 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 651 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 652 | "dev": true, 653 | "engines": { 654 | "node": ">= 0.4" 655 | } 656 | }, 657 | "node_modules/is-regex": { 658 | "version": "1.0.4", 659 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 660 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 661 | "dev": true, 662 | "dependencies": { 663 | "has": "^1.0.1" 664 | }, 665 | "engines": { 666 | "node": ">= 0.4" 667 | } 668 | }, 669 | "node_modules/is-symbol": { 670 | "version": "1.0.1", 671 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", 672 | "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", 673 | "dev": true, 674 | "engines": { 675 | "node": ">= 0.4" 676 | } 677 | }, 678 | "node_modules/minimatch": { 679 | "version": "3.1.2", 680 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 681 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 682 | "dev": true, 683 | "dependencies": { 684 | "brace-expansion": "^1.1.7" 685 | }, 686 | "engines": { 687 | "node": "*" 688 | } 689 | }, 690 | "node_modules/minimist": { 691 | "version": "1.2.7", 692 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", 693 | "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", 694 | "dev": true, 695 | "funding": { 696 | "url": "https://github.com/sponsors/ljharb" 697 | } 698 | }, 699 | "node_modules/object-inspect": { 700 | "version": "1.6.0", 701 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", 702 | "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", 703 | "dev": true 704 | }, 705 | "node_modules/object-keys": { 706 | "version": "1.0.12", 707 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", 708 | "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", 709 | "dev": true, 710 | "engines": { 711 | "node": ">= 0.4" 712 | } 713 | }, 714 | "node_modules/once": { 715 | "version": "1.4.0", 716 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 717 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 718 | "dev": true, 719 | "dependencies": { 720 | "wrappy": "1" 721 | } 722 | }, 723 | "node_modules/path-is-absolute": { 724 | "version": "1.0.1", 725 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 726 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 727 | "dev": true, 728 | "engines": { 729 | "node": ">=0.10.0" 730 | } 731 | }, 732 | "node_modules/path-parse": { 733 | "version": "1.0.7", 734 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 735 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 736 | "dev": true 737 | }, 738 | "node_modules/resumer": { 739 | "version": "0.0.0", 740 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 741 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 742 | "dev": true, 743 | "dependencies": { 744 | "through": "~2.3.4" 745 | } 746 | }, 747 | "node_modules/string.prototype.trim": { 748 | "version": "1.1.2", 749 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", 750 | "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", 751 | "dev": true, 752 | "dependencies": { 753 | "define-properties": "^1.1.2", 754 | "es-abstract": "^1.5.0", 755 | "function-bind": "^1.0.2" 756 | }, 757 | "engines": { 758 | "node": ">= 0.4" 759 | } 760 | }, 761 | "node_modules/tape": { 762 | "version": "4.9.1", 763 | "resolved": "https://registry.npmjs.org/tape/-/tape-4.9.1.tgz", 764 | "integrity": "sha512-6fKIXknLpoe/Jp4rzHKFPpJUHDHDqn8jus99IfPnHIjyz78HYlefTGD3b5EkbQzuLfaEvmfPK3IolLgq2xT3kw==", 765 | "dev": true, 766 | "dependencies": { 767 | "deep-equal": "~1.0.1", 768 | "defined": "~1.0.0", 769 | "for-each": "~0.3.3", 770 | "function-bind": "~1.1.1", 771 | "glob": "~7.1.2", 772 | "has": "~1.0.3", 773 | "inherits": "~2.0.3", 774 | "minimist": "~1.2.0", 775 | "object-inspect": "~1.6.0", 776 | "resolve": "~1.7.1", 777 | "resumer": "~0.0.0", 778 | "string.prototype.trim": "~1.1.2", 779 | "through": "~2.3.8" 780 | }, 781 | "bin": { 782 | "tape": "bin/tape" 783 | } 784 | }, 785 | "node_modules/tape/node_modules/resolve": { 786 | "version": "1.7.1", 787 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", 788 | "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", 789 | "dev": true, 790 | "dependencies": { 791 | "path-parse": "^1.0.5" 792 | } 793 | }, 794 | "node_modules/through": { 795 | "version": "2.3.8", 796 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 797 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 798 | "dev": true 799 | }, 800 | "node_modules/wrappy": { 801 | "version": "1.0.2", 802 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 803 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 804 | "dev": true 805 | } 806 | }, 807 | "dependencies": { 808 | "@chialab/cjs-to-esm": { 809 | "version": "0.17.6", 810 | "resolved": "https://registry.npmjs.org/@chialab/cjs-to-esm/-/cjs-to-esm-0.17.6.tgz", 811 | "integrity": "sha512-3L/zkOeRK/BTHjo06nMj5KoOec9EU1kVApCSHcJppr22F+FjjvQzY/zdt7safvx3LL7VvArQFSMIT7HlHFax/A==", 812 | "dev": true, 813 | "requires": { 814 | "@chialab/estransform": "^0.17.3" 815 | } 816 | }, 817 | "@chialab/esbuild-plugin-commonjs": { 818 | "version": "0.17.2", 819 | "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-commonjs/-/esbuild-plugin-commonjs-0.17.2.tgz", 820 | "integrity": "sha512-C30UShSb89PJiO+mJTwAS1ei6aKVxMcxZQrl0g1JAJVd4d1AE6OjcbYlCs4847PcbmMNqYZrTcWpgIg+zRQL3Q==", 821 | "dev": true, 822 | "requires": { 823 | "@chialab/cjs-to-esm": "^0.17.0", 824 | "@chialab/esbuild-rna": "^0.17.0", 825 | "@chialab/node-resolve": "^0.17.0" 826 | } 827 | }, 828 | "@chialab/esbuild-rna": { 829 | "version": "0.17.3", 830 | "resolved": "https://registry.npmjs.org/@chialab/esbuild-rna/-/esbuild-rna-0.17.3.tgz", 831 | "integrity": "sha512-LwPGDBeRnCNSkUfCEXYWKf7iORt6JCbmCLvEaeZnqle2Tm5yNRtZjCnQHdG9D4UpY9YQgrlZQnF785yOnKwL7Q==", 832 | "dev": true, 833 | "requires": { 834 | "@chialab/estransform": "^0.17.2", 835 | "@chialab/node-resolve": "^0.17.0" 836 | } 837 | }, 838 | "@chialab/estransform": { 839 | "version": "0.17.3", 840 | "resolved": "https://registry.npmjs.org/@chialab/estransform/-/estransform-0.17.3.tgz", 841 | "integrity": "sha512-ZFCcDk85ZllhUiAMIzRFsLhrA2z8xwRyXyJBNMP/84+3czLEydSTzzBK9nXNGloOspwp+lL+EgPlFY8Ry693Bg==", 842 | "dev": true, 843 | "requires": { 844 | "@parcel/source-map": "^2.0.0" 845 | } 846 | }, 847 | "@chialab/node-resolve": { 848 | "version": "0.17.0", 849 | "resolved": "https://registry.npmjs.org/@chialab/node-resolve/-/node-resolve-0.17.0.tgz", 850 | "integrity": "sha512-u9VwO/0djjXsM70aH/Wrx0BrXjtjhfNOxaDks32xa1mZP9T0CXZ0be5Sv3hXI9g5UeJbfvCyFJS2hfJEu7Ifgg==", 851 | "dev": true 852 | }, 853 | "@esbuild/android-arm": { 854 | "version": "0.16.17", 855 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", 856 | "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", 857 | "dev": true, 858 | "optional": true 859 | }, 860 | "@esbuild/android-arm64": { 861 | "version": "0.16.17", 862 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", 863 | "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", 864 | "dev": true, 865 | "optional": true 866 | }, 867 | "@esbuild/android-x64": { 868 | "version": "0.16.17", 869 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", 870 | "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", 871 | "dev": true, 872 | "optional": true 873 | }, 874 | "@esbuild/darwin-arm64": { 875 | "version": "0.16.17", 876 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", 877 | "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", 878 | "dev": true, 879 | "optional": true 880 | }, 881 | "@esbuild/darwin-x64": { 882 | "version": "0.16.17", 883 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", 884 | "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", 885 | "dev": true, 886 | "optional": true 887 | }, 888 | "@esbuild/freebsd-arm64": { 889 | "version": "0.16.17", 890 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", 891 | "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", 892 | "dev": true, 893 | "optional": true 894 | }, 895 | "@esbuild/freebsd-x64": { 896 | "version": "0.16.17", 897 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", 898 | "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", 899 | "dev": true, 900 | "optional": true 901 | }, 902 | "@esbuild/linux-arm": { 903 | "version": "0.16.17", 904 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", 905 | "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", 906 | "dev": true, 907 | "optional": true 908 | }, 909 | "@esbuild/linux-arm64": { 910 | "version": "0.16.17", 911 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", 912 | "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", 913 | "dev": true, 914 | "optional": true 915 | }, 916 | "@esbuild/linux-ia32": { 917 | "version": "0.16.17", 918 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", 919 | "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", 920 | "dev": true, 921 | "optional": true 922 | }, 923 | "@esbuild/linux-loong64": { 924 | "version": "0.16.17", 925 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", 926 | "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", 927 | "dev": true, 928 | "optional": true 929 | }, 930 | "@esbuild/linux-mips64el": { 931 | "version": "0.16.17", 932 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", 933 | "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", 934 | "dev": true, 935 | "optional": true 936 | }, 937 | "@esbuild/linux-ppc64": { 938 | "version": "0.16.17", 939 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", 940 | "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", 941 | "dev": true, 942 | "optional": true 943 | }, 944 | "@esbuild/linux-riscv64": { 945 | "version": "0.16.17", 946 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", 947 | "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", 948 | "dev": true, 949 | "optional": true 950 | }, 951 | "@esbuild/linux-s390x": { 952 | "version": "0.16.17", 953 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", 954 | "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", 955 | "dev": true, 956 | "optional": true 957 | }, 958 | "@esbuild/linux-x64": { 959 | "version": "0.16.17", 960 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", 961 | "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", 962 | "dev": true, 963 | "optional": true 964 | }, 965 | "@esbuild/netbsd-x64": { 966 | "version": "0.16.17", 967 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", 968 | "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", 969 | "dev": true, 970 | "optional": true 971 | }, 972 | "@esbuild/openbsd-x64": { 973 | "version": "0.16.17", 974 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", 975 | "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", 976 | "dev": true, 977 | "optional": true 978 | }, 979 | "@esbuild/sunos-x64": { 980 | "version": "0.16.17", 981 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", 982 | "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", 983 | "dev": true, 984 | "optional": true 985 | }, 986 | "@esbuild/win32-arm64": { 987 | "version": "0.16.17", 988 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", 989 | "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", 990 | "dev": true, 991 | "optional": true 992 | }, 993 | "@esbuild/win32-ia32": { 994 | "version": "0.16.17", 995 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", 996 | "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", 997 | "dev": true, 998 | "optional": true 999 | }, 1000 | "@esbuild/win32-x64": { 1001 | "version": "0.16.17", 1002 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", 1003 | "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", 1004 | "dev": true, 1005 | "optional": true 1006 | }, 1007 | "@parcel/source-map": { 1008 | "version": "2.1.1", 1009 | "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", 1010 | "integrity": "sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==", 1011 | "dev": true, 1012 | "requires": { 1013 | "detect-libc": "^1.0.3" 1014 | } 1015 | }, 1016 | "balanced-match": { 1017 | "version": "1.0.0", 1018 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 1019 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 1020 | "dev": true 1021 | }, 1022 | "brace-expansion": { 1023 | "version": "1.1.11", 1024 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 1025 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 1026 | "dev": true, 1027 | "requires": { 1028 | "balanced-match": "^1.0.0", 1029 | "concat-map": "0.0.1" 1030 | } 1031 | }, 1032 | "concat-map": { 1033 | "version": "0.0.1", 1034 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1035 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 1036 | "dev": true 1037 | }, 1038 | "deep-equal": { 1039 | "version": "1.0.1", 1040 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", 1041 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", 1042 | "dev": true 1043 | }, 1044 | "define-properties": { 1045 | "version": "1.1.2", 1046 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", 1047 | "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", 1048 | "dev": true, 1049 | "requires": { 1050 | "foreach": "^2.0.5", 1051 | "object-keys": "^1.0.8" 1052 | } 1053 | }, 1054 | "defined": { 1055 | "version": "1.0.0", 1056 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 1057 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 1058 | "dev": true 1059 | }, 1060 | "detect-libc": { 1061 | "version": "1.0.3", 1062 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 1063 | "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", 1064 | "dev": true 1065 | }, 1066 | "es-abstract": { 1067 | "version": "1.12.0", 1068 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", 1069 | "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", 1070 | "dev": true, 1071 | "requires": { 1072 | "es-to-primitive": "^1.1.1", 1073 | "function-bind": "^1.1.1", 1074 | "has": "^1.0.1", 1075 | "is-callable": "^1.1.3", 1076 | "is-regex": "^1.0.4" 1077 | } 1078 | }, 1079 | "es-to-primitive": { 1080 | "version": "1.1.1", 1081 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", 1082 | "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", 1083 | "dev": true, 1084 | "requires": { 1085 | "is-callable": "^1.1.1", 1086 | "is-date-object": "^1.0.1", 1087 | "is-symbol": "^1.0.1" 1088 | } 1089 | }, 1090 | "esbuild": { 1091 | "version": "0.16.17", 1092 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", 1093 | "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", 1094 | "dev": true, 1095 | "requires": { 1096 | "@esbuild/android-arm": "0.16.17", 1097 | "@esbuild/android-arm64": "0.16.17", 1098 | "@esbuild/android-x64": "0.16.17", 1099 | "@esbuild/darwin-arm64": "0.16.17", 1100 | "@esbuild/darwin-x64": "0.16.17", 1101 | "@esbuild/freebsd-arm64": "0.16.17", 1102 | "@esbuild/freebsd-x64": "0.16.17", 1103 | "@esbuild/linux-arm": "0.16.17", 1104 | "@esbuild/linux-arm64": "0.16.17", 1105 | "@esbuild/linux-ia32": "0.16.17", 1106 | "@esbuild/linux-loong64": "0.16.17", 1107 | "@esbuild/linux-mips64el": "0.16.17", 1108 | "@esbuild/linux-ppc64": "0.16.17", 1109 | "@esbuild/linux-riscv64": "0.16.17", 1110 | "@esbuild/linux-s390x": "0.16.17", 1111 | "@esbuild/linux-x64": "0.16.17", 1112 | "@esbuild/netbsd-x64": "0.16.17", 1113 | "@esbuild/openbsd-x64": "0.16.17", 1114 | "@esbuild/sunos-x64": "0.16.17", 1115 | "@esbuild/win32-arm64": "0.16.17", 1116 | "@esbuild/win32-ia32": "0.16.17", 1117 | "@esbuild/win32-x64": "0.16.17" 1118 | } 1119 | }, 1120 | "for-each": { 1121 | "version": "0.3.3", 1122 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 1123 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 1124 | "dev": true, 1125 | "requires": { 1126 | "is-callable": "^1.1.3" 1127 | } 1128 | }, 1129 | "foreach": { 1130 | "version": "2.0.5", 1131 | "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", 1132 | "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", 1133 | "dev": true 1134 | }, 1135 | "fs.realpath": { 1136 | "version": "1.0.0", 1137 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1138 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 1139 | "dev": true 1140 | }, 1141 | "function-bind": { 1142 | "version": "1.1.1", 1143 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 1144 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 1145 | "dev": true 1146 | }, 1147 | "glob": { 1148 | "version": "7.1.2", 1149 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 1150 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 1151 | "dev": true, 1152 | "requires": { 1153 | "fs.realpath": "^1.0.0", 1154 | "inflight": "^1.0.4", 1155 | "inherits": "2", 1156 | "minimatch": "^3.0.4", 1157 | "once": "^1.3.0", 1158 | "path-is-absolute": "^1.0.0" 1159 | } 1160 | }, 1161 | "has": { 1162 | "version": "1.0.3", 1163 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 1164 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 1165 | "dev": true, 1166 | "requires": { 1167 | "function-bind": "^1.1.1" 1168 | } 1169 | }, 1170 | "inflight": { 1171 | "version": "1.0.6", 1172 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1173 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 1174 | "dev": true, 1175 | "requires": { 1176 | "once": "^1.3.0", 1177 | "wrappy": "1" 1178 | } 1179 | }, 1180 | "inherits": { 1181 | "version": "2.0.3", 1182 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 1183 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 1184 | "dev": true 1185 | }, 1186 | "is-callable": { 1187 | "version": "1.1.4", 1188 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 1189 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", 1190 | "dev": true 1191 | }, 1192 | "is-date-object": { 1193 | "version": "1.0.1", 1194 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 1195 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 1196 | "dev": true 1197 | }, 1198 | "is-regex": { 1199 | "version": "1.0.4", 1200 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 1201 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 1202 | "dev": true, 1203 | "requires": { 1204 | "has": "^1.0.1" 1205 | } 1206 | }, 1207 | "is-symbol": { 1208 | "version": "1.0.1", 1209 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", 1210 | "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", 1211 | "dev": true 1212 | }, 1213 | "minimatch": { 1214 | "version": "3.1.2", 1215 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1216 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1217 | "dev": true, 1218 | "requires": { 1219 | "brace-expansion": "^1.1.7" 1220 | } 1221 | }, 1222 | "minimist": { 1223 | "version": "1.2.7", 1224 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", 1225 | "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", 1226 | "dev": true 1227 | }, 1228 | "object-inspect": { 1229 | "version": "1.6.0", 1230 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", 1231 | "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", 1232 | "dev": true 1233 | }, 1234 | "object-keys": { 1235 | "version": "1.0.12", 1236 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", 1237 | "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", 1238 | "dev": true 1239 | }, 1240 | "once": { 1241 | "version": "1.4.0", 1242 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1243 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1244 | "dev": true, 1245 | "requires": { 1246 | "wrappy": "1" 1247 | } 1248 | }, 1249 | "path-is-absolute": { 1250 | "version": "1.0.1", 1251 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1252 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1253 | "dev": true 1254 | }, 1255 | "path-parse": { 1256 | "version": "1.0.7", 1257 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1258 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 1259 | "dev": true 1260 | }, 1261 | "resumer": { 1262 | "version": "0.0.0", 1263 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 1264 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 1265 | "dev": true, 1266 | "requires": { 1267 | "through": "~2.3.4" 1268 | } 1269 | }, 1270 | "string.prototype.trim": { 1271 | "version": "1.1.2", 1272 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", 1273 | "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", 1274 | "dev": true, 1275 | "requires": { 1276 | "define-properties": "^1.1.2", 1277 | "es-abstract": "^1.5.0", 1278 | "function-bind": "^1.0.2" 1279 | } 1280 | }, 1281 | "tape": { 1282 | "version": "4.9.1", 1283 | "resolved": "https://registry.npmjs.org/tape/-/tape-4.9.1.tgz", 1284 | "integrity": "sha512-6fKIXknLpoe/Jp4rzHKFPpJUHDHDqn8jus99IfPnHIjyz78HYlefTGD3b5EkbQzuLfaEvmfPK3IolLgq2xT3kw==", 1285 | "dev": true, 1286 | "requires": { 1287 | "deep-equal": "~1.0.1", 1288 | "defined": "~1.0.0", 1289 | "for-each": "~0.3.3", 1290 | "function-bind": "~1.1.1", 1291 | "glob": "~7.1.2", 1292 | "has": "~1.0.3", 1293 | "inherits": "~2.0.3", 1294 | "minimist": "~1.2.0", 1295 | "object-inspect": "~1.6.0", 1296 | "resolve": "~1.7.1", 1297 | "resumer": "~0.0.0", 1298 | "string.prototype.trim": "~1.1.2", 1299 | "through": "~2.3.8" 1300 | }, 1301 | "dependencies": { 1302 | "resolve": { 1303 | "version": "1.7.1", 1304 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", 1305 | "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", 1306 | "dev": true, 1307 | "requires": { 1308 | "path-parse": "^1.0.5" 1309 | } 1310 | } 1311 | } 1312 | }, 1313 | "through": { 1314 | "version": "2.3.8", 1315 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1316 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 1317 | "dev": true 1318 | }, 1319 | "wrappy": { 1320 | "version": "1.0.2", 1321 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1322 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1323 | "dev": true 1324 | } 1325 | } 1326 | } 1327 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mudder", 3 | "version": "2.1.1", 4 | "description": "Find lexicographical points between strings", 5 | "main": "dist/mudder.min.js", 6 | "module": "dist/mudder.min.mjs", 7 | "exports": { 8 | ".": { 9 | "browser": "./dist/mudder.min.mjs", 10 | "node": { 11 | "import": "./dist/mudder.min.mjs", 12 | "require": "./dist/mudder.cjs" 13 | }, 14 | "import": "./dist/mudder.min.mjs" 15 | }, 16 | "./package.json": "./package.json", 17 | "./dist/*": "./dist/*" 18 | }, 19 | "scripts": { 20 | "pretest": "node tangle.js", 21 | "test": "npm run pretest && tape 'test/**/*-test.js'", 22 | "compile": "node esbuild.mjs", 23 | "build": "npm run test && npm run compile" 24 | }, 25 | "keywords": [ 26 | "string", 27 | "mean", 28 | "lexicographical", 29 | "distance", 30 | "number", 31 | "radix", 32 | "base" 33 | ], 34 | "author": "Ahmed Fasih", 35 | "license": "Unlicense", 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/fasiha/mudderjs.git" 39 | }, 40 | "devDependencies": { 41 | "@chialab/esbuild-plugin-commonjs": "^0.17.2", 42 | "esbuild": "^0.16.17", 43 | "tape": "^4.6.0" 44 | } 45 | } -------------------------------------------------------------------------------- /tangle.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var fs = require('fs'); 3 | var file = fs.readFileSync('README.md', 'utf8'); 4 | var exportStrings = 5 | file.match(/~~~js\n([\S\s]*?)~~~/g) 6 | .filter(s => s.indexOf('< export') >= 0) 7 | .map(s => s.replace(/~~~$/, '').replace(/^~~~[a-z]*\s/, '')); 8 | fs.writeFileSync('index.js', exportStrings.join('\n')); 9 | -------------------------------------------------------------------------------- /test/mudder-test.js: -------------------------------------------------------------------------------- 1 | var tape = require("tape"); 2 | var mudder = require("../index"); 3 | 4 | tape("Reasonable values", function(test) { 5 | const decimal = new mudder.SymbolTable('0123456789'); 6 | const res = decimal.mudder('1', '2'); 7 | test.equal(res[0], '15', "decimal"); 8 | test.end(); 9 | }); 10 | 11 | tape("Reversing start/end reverses outputs: controlled cases", function(test) { 12 | const decimal = new mudder.SymbolTable('0123456789'); 13 | for (let num of Array.from(Array(12), (_, i) => i + 1)) { 14 | const fwd = decimal.mudder('1', '2', num); 15 | const rev = decimal.mudder('2', '1', num); 16 | test.equal(rev.slice().reverse().join(''), fwd.join(''), "fwd = rev, " + num); 17 | test.ok(fwd.reduce((accum, curr, i, arr) => arr[i - 1] ? accum && (arr[i - 1] < curr) : true, true), 18 | 'fwd all increasing'); 19 | test.ok(rev.reduce((accum, curr, i, arr) => arr[i - 1] ? accum && (arr[i - 1] > curr) : true, true), 20 | 'rev all decreasing'); 21 | } 22 | test.end(); 23 | }); 24 | 25 | tape("Constructor with objects/maps", function(test) { 26 | var arr = '_,I,II,III,IV,V'.split(','); 27 | var obj = {_: 0, I: 1, i: 1, II: 2, ii: 2, III: 3, iii: 3, IV: 4, iv: 4, V: 5, v: 5}; 28 | var map = new Map(Object.entries(obj)); 29 | var romanObj = new mudder.SymbolTable(arr, obj); 30 | var romanMap = new mudder.SymbolTable(arr, map); 31 | test.equal(romanMap.mudder(['i'], ['ii'], 10).join(','), romanObj.mudder(['i'], ['ii'], 10).join(',')); 32 | test.end(); 33 | }) 34 | 35 | tape("Matches parseInt/toString", function(test) { 36 | test.equal(mudder.base36.numberToString(123), (123).toString(36)); 37 | test.equal(mudder.base36.stringToNumber('FE0F'), parseInt('FE0F', 36)); 38 | test.end(); 39 | }) 40 | 41 | tape("Fixes #1: repeated recursive subdivision", function(test) { 42 | let right = 'z'; 43 | for (let i = 0; i < 50; i++) { 44 | let newr = mudder.alphabet.mudder('a', right)[0]; 45 | test.notEqual('a', newr); 46 | test.notEqual(right, newr); 47 | right = newr; 48 | } 49 | test.end(); 50 | }); 51 | 52 | tape('Fixes #2: throws when fed lexicographically-adjacent strings', function(test) { 53 | for (let i = 2; i < 10; i++) { 54 | test.throws(() => mudder.alphabet.mudder('x' + 55 | 'a'.repeat(i), 56 | 'xa')); 57 | test.throws(() => mudder.alphabet.mudder('xa', 'x' + 58 | 'a'.repeat(i))); 59 | } 60 | test.end(); 61 | }); 62 | 63 | tape('Fixes #3: allow calling mudder with just number', function(test) { 64 | for (const abc of [mudder.alphabet.mudder(100), mudder.base36.mudder(100), mudder.base62.mudder(100)]) { 65 | test.ok(abc.every((c, i) => (!i) || (abc[i - 1] < c))); 66 | } 67 | test.ok(mudder.alphabet.mudder()); 68 | test.end(); 69 | }); 70 | 71 | tape('More #3: no need to define start/end', function(test) { 72 | test.ok(mudder.base36.mudder('', 'foo', 30)); 73 | test.ok(mudder.base36.mudder('foo', '', 30)); 74 | test.end(); 75 | }); 76 | 77 | tape('Fix #7: specify number of divisions', t => { 78 | const decimal = new mudder.SymbolTable('0123456789'); 79 | { 80 | const fine = decimal.mudder('9', undefined, 100); 81 | const partialFine = decimal.mudder('9', undefined, 5, undefined, 101); 82 | const coarse = decimal.mudder('9', undefined, 5); 83 | 84 | t.ok(allLessThan(fine)); 85 | t.ok(allLessThan(partialFine)); 86 | t.ok(allLessThan(coarse)); 87 | t.deepEqual(fine.slice(0, 5), partialFine); 88 | t.equal(partialFine.length, coarse.length); 89 | t.notDeepEqual(partialFine, coarse); 90 | } 91 | // similarly working backwards 92 | { 93 | const fine = decimal.mudder('9', '8', 100); 94 | const partialFine = decimal.mudder('9', '8', 5, undefined, 101); 95 | const coarse = decimal.mudder('9', '8', 5); 96 | 97 | t.ok(allGreaterThan(fine)); 98 | t.ok(allGreaterThan(partialFine)); 99 | t.ok(allGreaterThan(coarse)); 100 | 101 | // omit last because when going from high to low, the final might be rounded 102 | t.deepEqual(fine.slice(0, 4), partialFine.slice(0, 4)); 103 | } 104 | t.end(); 105 | }); 106 | 107 | tape('Fix #8: better default end', t => { 108 | t.ok(mudder.base36.mudder('z'.repeat(10))[0] !== mudder.base36.mudder('z'.repeat(15))[0]); 109 | t.end(); 110 | }); 111 | 112 | tape('Fix #16, numStrings 0 should return empty', t => { 113 | t.ok(mudder.base62.mudder('hi', 'bye', 0).length === 0); 114 | t.end(); 115 | }) 116 | 117 | tape('Give more flexibility in rounding when counting backwards', t => { 118 | const numDivisions = [100, 1000, 10_000, 100_000]; 119 | { 120 | const res = numDivisions.map(numDivisions => mudder.base62.mudder('V', '0', 1, undefined, numDivisions)[0]); 121 | t.ok((new Set(res)).size === 1, 'by default, going backwards by a tiny amount gets rounded down to a short string'); 122 | } 123 | { 124 | const places_NEW = 6; 125 | const res = 126 | numDivisions.map(numDivisions => mudder.base62.mudder('V', '0', 1, undefined, numDivisions, places_NEW)[0]); 127 | t.ok((new Set(res)).size === numDivisions.length, 'but with a higher places count, they are unique'); 128 | } 129 | t.end(); 130 | }); 131 | 132 | function allLessThan(arr) { 133 | for (let i = 1; i < arr.length; i++) { 134 | if (arr[i - 1] > arr[i]) { return false; } 135 | } 136 | return true; 137 | } 138 | function allGreaterThan(arr) { return allLessThan(arr.slice().reverse()); } --------------------------------------------------------------------------------