I need a js function, that increments Strings lexicographically, by incrementing or appending letters(A-Z, a-z) only, it gets passed an initial string, and the amount of increments n. It returns an array with n lexicographically ascending strings.
Special characters should not be modified, they may however be appended too or removed when decrementing
Increments should also be the smallest step possible, within the minimum at least required string length
The total amount of increments, could be passed as an argument, since it is determined in advance.
Incrementation Examples:
"x", 2 times: [y,z]
"x", 53 times [xA, xB, xC,...,xz, y, yA]
"$y$", 1 time: ["$z$"]
"$y$", >53 times: [$y$A - $y$Z, $y$a - $y$z, $z$, $z$A,....]
I managed to create an increment function, If the question ever gets opened again I would post it as a Reply.
I also require a decrement function. Just like increment, it gets passed an initial string and n. It returns an array, just like increment.
Letters may be incremented/deleted/appended, special characters only deleted.
It should decrement the string with using as few additional digits as possible and never go below the empty string
Minimum digits used for decrementing is the length of the initial string, it may be more if necessary, see example below.
The total amount of decrements, could be passed as an argument, since it is determined in advance.
Assume, that we don't pass initial strings that are empty, or only containing the smallest letter "A"
Example: (we assume our letters to only be a,b for these examples)
"ab" decrement 8 times
-> 4 digits required minimum
result: ["aabb","aaba","aab","aaaa","aaa","aa","a",""]
"b$b" decrement 10 times
-> 3 digits are enough
result: ["b$a", "b$", "b", "abb", "aba", "ab", "aaa", "aa", "a", ""]
You can use the replace
function with regular expression argument to quickly identify the terminating sequence of "z" characters, so you can turn them all into "A".
For decrementing, the logic is to identify a terminating sequence of "A" characters. Those must turn to "z".
And of course, in both cases the digit that precedes that terminating sequence must increment/decrement.
If there is a foreign character in the input, it should remain unaltered. With this logic it will not be possible to ensure that both functions mirror each other exactly. For instance, if the input is "-A-", then the output of decrementString()
would remove characters from the end of the string. If that output is given as input to incrementString()
, then there is no information anymore about that lost hyphen, so that you would get "-A-" again.
To ensure that the lexical order is ensured, more letters may need to be appended at the end of the string. For instance, without such intervention, the input "B" can only be decremented twice ("A" and empty string). By appending a character we can get "Az", "Ay", ... "AA", "A", "".
Here is a solution. The runnable snippet allows you to input some characters and the number of required results, and it will display the output of calling both functions with that input:
const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const digits = upper + upper.toLowerCase();
const add = (ch, unit) => digits[digits.indexOf(ch) + unit];
const lettersIn = string => string.replace(/[^A-Za-z]/g, "");
const valueOf = letters => Array.from(letters).reduce(
(value, letter) => value * digits.length + digits.indexOf(letter),
0
);
function incrementString(string, length) {
const letters = lettersIn(string);
return digits.length ** letters.length - 1 - valueOf(letters) < length // Not enough digits?
? [string += digits[0], ...incrementString(string, length - 1)] // Add a digit, and retry...
: Array.from({length}, () =>
string = string.replace(/([A-Za-y])([^A-Za-y]*)$/, (_, digit, zz) =>
add(digit, 1) + zz.replaceAll(digits.at(-1), digits[0])
)
);
}
function decrementString(string, length) {
const letters = lettersIn(string);
const letterValue = valueOf(letters);
// Count also the possibility to remove characters from the end (string.length):
return letterValue + string.length < length && letterValue // Need more digits?
? decrementString(string + digits[0], length) // Add a digit, and retry...
: Array.from({length}, () =>
string = string.replace(/([B-Za-z])?([^B-Za-z]*)$/, (match, digit, aa) =>
digit ? add(digit, -1) + aa.replaceAll(digits[0], digits.at(-1)) : aa.slice(0, -1)
)
);
}
// I/O management
const [inpString, inpCount, outPrev, outNext] = document.querySelectorAll("input, pre");
inpString.addEventListener("input", refresh);
inpCount.addEventListener("input", refresh);
function refresh() {
const format = (arr) => arr.map(JSON.stringify).join(", ");
const s = inpString.value;
const count = +inpCount.value;
outPrev.textContent = format(decrementString(s, count));
outNext.textContent = format(incrementString(s, count));
}
refresh();
pre { white-space: pre-wrap;}
String: <input value="_Z_"><br>
Count: <input value="29" type="number" min="1" max="100"><br>
<table>
<tr><td>Prev:</td><td><pre></pre></td></tr>
<tr><td>Next:</td><td><pre></pre></td></tr>
</table>