I need to generate the custom sequence as below using javascript based on input. For ex: if i provide the input isAA1 then output should be AA2 and if provided input as AA9 then the output should be AB0. I can do with the if else by creating the token's but it looks like we need to keep so many if else condition's. Wanted to know the more efficient way to handle this.
AA0
AA1
AA2
AA3
AA4
AA5
AA6
AA7
AA8
AA9
AB0
AB1
AB2
AB3
AB4
AB5
AB6
AB7
AB8
AB9
AC0
AC1
.
.
.
ZZ9
Using reduceRight() you can iterate over the spread string in reverse, incrementing each character as necessary using the character's charCodeAt() value adjusted by its offset from 0, and cycling it based on its remainder (%) for the appropriate cycle length (10 for integers, 26 for letters). The accumulator tracks whether to sum on the next iteration, as well as the result string.
const incrementAlphaNumeric = (str) => {
let iter, min, len;
return [...str]
.reduceRight((a, c, i) => {
let code = c.charCodeAt();
if (code >= 48 && code <= 57) { // [0-9]
min = 48;
len = 10;
} else if ((code >= 65 && code <= 90)) { // [A-Z]
min = 65;
len = 26;
}
iter = code - min + a.sum;
a.res[i] = String.fromCharCode(iter % len + min);
a.sum = Math.floor(iter / len);
return a;
}, { res: [], sum: 1 })
.res
.join('');
}
console.log(incrementAlphaNumeric('AA0')); // AA1
console.log(incrementAlphaNumeric('AA9')); // AB0
console.log(incrementAlphaNumeric('AZ9')); // BA0
console.log(incrementAlphaNumeric('ZZ9')); // AA0
I had a little time on my hands, and this seemed like a fun challenge, so I went ahead and built a function that accepts any string of uppercase letters and/or numbers. I realize it might be a little bit overkill for the requirements of the question, but it does satisfy all of the requirements, and someone else stumbling across this question in the future might find this helpful.
It works by converting the right-most character to its respective character code, incrementing it by 1, and checking if the resulting character code is outside of the A-Z or 0-9 range. If it is outside of its range, we reset it to its "base value" (A or 0) and set a carry flag (this is very similar to how binary adder circuits work). If the carry flag is set, we recursively call the function using the next-to-last character as our new "right-most" character until we no longer need to carry. At which point, we simply return the new string.
increment('AA0') > 'AA1
increment('AA9') > 'AB0
increment('ZZ9') > 'AA0
increment('AZ9BE') > 'AZ9BF
const A = 65
const Z = 90
const ZERO = 48
const NINE = 57
const isDigit = (char) => char >= 48 && char <= 57
const incDigit = (char) => char + 1 > NINE ? ZERO : char + 1
const incLetter = (char) => char + 1 > Z ? A : char + 1
const codeToChar = (code) => String.fromCharCode(code)
const setChar = (index, char, str) => {
const charArr = [...str]
charArr.splice(index, 1, char)
return charArr.join('')
}
const increment = (str, place = str.length - 1) => {
if (place < 0) return str;
const char = str.charCodeAt(place)
const nextChar = isDigit(char) ? incDigit(char) : incLetter(char)
const carry = nextChar - char !== 1;
str = setChar(place, codeToChar(nextChar), str)
if (carry)
return increment(str, --place)
else return str
}
let demoStr = 'AA0'
setInterval(() => {
demoStr = increment(demoStr)
console.log(demoStr)
}, 25)
You could use this next() function, which increments the letters of the input string by converting them to base 26 numeric strings and back whenever an overflow is detected from incrementing the decimal portion of the input string:
const next = (() => {
const charCodeA = 'A'.charCodeAt(0);
const to = (replacer, string) => string.replace(/./g, replacer);
const padStart = (string, { length }, pad) => string.padStart(length, pad);
const truncate = (string, { length }) => string.slice(-length);
const letter = (base26String) => String.fromCharCode(
parseInt(base26String, 26) + charCodeA
);
const base26 = (letterString) => (
letterString.charCodeAt(0) - charCodeA
).toString(26);
const increment = (numbersString, radix) => (
parseInt(numbersString, radix) + 1
).toString(radix);
return (prev) => {
const [, prevL, prevD] = prev.match(/^([A-Z]+)([0-9]+)$/);
const nextD = padStart(increment(prevD, 10), prevD, '0');
const carry = nextD.length > prevD.length;
const nextL = carry
? padStart(to(letter, increment(to(base26, prevL), 26)), prevL, 'A')
: prevL;
return truncate(nextL, prevL) + truncate(nextD, prevD);
};
})();
console.log(next('AA0')); // AA1
console.log(next('AZ9')); // BA0
console.log(next('ZZ9')); // AA0
console.log(next('AAA00')); // AAA01
console.log(next('AZZ09')); // AZZ10
console.log(next('AZZ99')); // BAA00
console.log(next('ZZZ99')); // AAA00
References
String.prototype.charCodeAt()
String.prototype.replace()
String.prototype.padStart()
String.prototype.slice()
String.fromCharCode()
parseInt()
Number.prototype.toString()
String.prototype.match()
You could take two function for gettign a deciaml or the fancy value and check if the last digit is nine.
function increment(value) {
const
decimal = s => Array
.from(s, v => parseInt(v, 36) - 10)
.reduce((s, v) => s * 26 + v, 0),
fancy = n => Array
.from(n.toString(26), v => (parseInt(v, 26) + 10).toString(36))
.join('')
.toUpperCase(),
[left, right] = value.match(/\D+|\d+/g);
if (value === 'ZZ9') return 'AA0';
return right === '9'
? fancy(decimal(left) + 1) + '0'
: left + (+right + 1);
}
console.log(increment('AA0')); // AA1
console.log(increment('AZ9')); // BA0
console.log(increment('ZZ9')); // AA0
Related
I have asked a similar question before: How to get this PRNG to generate numbers within the range?
How do I take that and make it work for a number which can be up to 5 bits (32 possible values)? I tried this but it is giving "invalid" values:
const fetch = (x, o) => {
if (x >= o) {
return x
} else {
const v = (x * x) % o
return (x <= o / 2) ? v : o - v
}
}
const fetch16 = (x) => fetch(x, 65519)
const fetch8 = (x) => fetch(x, 251)
// the last number can be anything.
// MODIFIED THIS
const build32 = (x, o) => fetch8((fetch8(x) + o) % 256 ^ 101) % 32
const j = 115; // If you don't want duplicates, either i or j should stay fixed
let i = 0
let invalid = [];
let valid = new Set;
while (i <= 32) { // <-- small fix here!
let x = build32(i, j); // To test, you can swap i and j here, and run again.
if (x > 31 || valid.has(x)) {
invalid.push([i, j, x]);
} else {
valid.add(x);
}
i++;
}
console.log("invalid:", invalid);
console.log("valid:", [...valid]);
console.log("count of valid:", valid.size);
The system should iterate through the numbers 0-31 without repeating, in what seems like random order.
I have a string where I need to strip out the letters and the numbers. There will only be one number in the string.
So for example this string:
"AM12" I would like to split into this:
['A','M',12]
What is the most efficient way to do this? I was able to do it before with dashes in the string separating them (A-M-12) but was asked to remove the dashes.
Here is code I used for with dashes:
let arrUrl = myString.split('-');
Thanks.
You could use /\d+|./. It will match consecutive numbers or individual characters.
const split = str => str.match(/\d+|./g)
console.log(split("AM12"))
console.log(split("Catch22"))
If you need the number portion to be numeric in your resulting array, you could try something like this
let test = 'AM12'
let res = []
let num = ''
test.split('').forEach(e=>isNaN(e)?res.push(e):num+=e)
res.push(parseInt(num))
console.log(res)
You can scan the input string linearly, char by char, and keep track of any running number, also you should pay attention to the negative numbers.
The following snippet handles negative numbers and also multiple numbers in the same input.
function isDigit(char) {
return char >= "0" && char <= "9";
}
function split(input) {
const result = [];
// keep track of the running number if any
let runningNum = 0;
let isNum = false;
let isNegative = false;
for (let i = 0; i < input.length; i++) {
const ch = input[i];
if (isDigit(ch)) {
// check for negative value
if (i > 0 && input[i - 1] === "-") {
isNegative = true;
}
runningNum *= 10;
runningNum += (isNegative ? -1 : 1) * (ch - "0");
isNum = true;
} else {
// push previous running number if any
if (isNum) {
result.push(runningNum);
runningNum = 0; // reset
isNum = false;
isNegative = false;
}
// if current char is a "-" sign and the following char is a digit continue,
// if not then it's a hyphen
const isLastChar = i === input.length - 1;
if (!isLastChar && input[i] === "-" && isDigit(input[i + 1])) {
continue;
}
result.push(ch);
}
}
// in case the number at the end of the input string
if (isNum) {
result.push(runningNum);
}
return result;
}
const inputs = ["AM-12", "AM-12-30", "AM-12B30", "30", "a3b", "ab", "-", "-abc", "a-12-"];
for (let input of inputs) {
console.log(`"${input}": `, split(input));
}
My question is how do I get the corresponding letter(s) from a number.
So
0 => 0 // 0 stays 0
1 => A
2 => B
3 => C
26 => Z
27 => AA
28 => AB
55 => AC
702 => ZZ
The number will definitely not be over 702.
Is there some kind of method I can use to do this?
Split the number into parts by doing a modulo by 26, then use String.fromCharCode to convert it to a character:
function numToExcelColumnName(n){
if(!n) return 0
n-- //Make 0 => A
const lower = n % 26
const upper = Math.floor(n / 26)
return (
upper
? String.fromCharCode(65 + upper - 1)
: ''
)
+ String.fromCharCode(65 + lower)
}
You can even extend this code to handle numbers above 702 as well by adding a loop:
function numToExcelColumnName(n){
if(!n) return 0
let output = ''
while(n){
n--
output = String.fromCharCode(65 + n % 26) + output
n = Math.floor(n / 26)
}
return output
}
For single characters like A,B... you can get the number easily by
num = ord(x)-65 where x is the corresponding character (A,B...)
For Double letters like (AB,BG,..)
num = ord(str[0]-65)*26+(str[1]-65) where str = xy where x and y are corresponding characters
I liked the challenge, so I wrote the following code:
function NumbersToLetters(num) {
var letArr=[];
function numToLet(letterArray, number) {
if(number>0) {
var whole=Math.floor((number-1)/26);
var reminder=(number-1)%26;
letterArray.unshift(String.fromCharCode(65+reminder));
numToLet(letterArray, whole);
}
};
numToLetters(letArr, num);
return letArr.length?letArr.join(""):"0";
}
To be used:
NumbersToLetters(num);
I am trying to generate a function that generates sequence of numbers and add 0 accordingly to its front. However, it's not outputting the right numbers:
const generatingNumber = (start, end) => {
let result = [];
let output = '';
for(let num = start; num <= end; num++){
if(num.toString().length === 1){
output = '000' + num.toString();
result.push(parseInt(output));
} else if (num.toString().length === 2){
output = '00' + num.toString();
result.push(parseInt(output));
} else if (num.toString() === 3){
output = '0' + num.toString();
result.push(parseInt(output));
}
result.push(num);
}
return result.join('\n');
};
console.log(generatingNumber(1, 3000));
Any idea what am I missing here why its not adding zeros?
Also is there a more succint way to do this?
You can use padStart method.
The padStart() method pads the current string with another string (multiple times, if needed) until the resulting string reaches the given length. The padding is applied from the start of the current string.
const generatingNumber = (start, end) => {
let result = [];
let output = '';
for (let num = start; num <= end; num++) {
result.push(num.toString().padStart(4, 0));
}
return result.join('\n');
};
console.log(generatingNumber(1, 10));
Is this something you want
const generatingNumber = (start, end) => {
let result = [];
let output = '';
for (let num = start; num <= end; num++) {
if (num.toString().length === 1) {
output = '000' + num.toString();
result.push(output);
} else if (num.toString().length === 2) {
output = '00' + num.toString();
result.push(output);
} else if (num.toString().length === 3) {
output = '0' + num.toString();
result.push(output);
} else {
result.push(num);
}
}
return result.join('\n');
};
console.log(generatingNumber(1, 3000));
parseInt('0001') will result in 1. parseInt will remove all the 0's before of actual number because 0001 & 1 is same in integer.
So you have to push only string in result.
parseInt() Removes all the leading zeros from the string before parsing it. You can change
output = '0' + num.toString();
result.push(parseInt(output));
to
output = '0' + num.toString();
result.push(output);
and all other similar states where you push to result array.
It will be storing strings but you cant store numbers with leading zeros.
What you want is not to modify the value, but the presentation of the value instead.
There's no numerical difference between "00000000" and "0"; both are still the value 0. So if your value is converted to a number type, you'll not be able to show those leading zeroes.
This is the line where you're converting the digits to a numeric value
result.push(parseInt(output));
If you want to preserve the leading zeroes in your output, do not convert it to number; keep it as a string.
Also, instead of having those manual ifs depending on the length, take a look at the padStart method. It can "fill" those zeroes at the front for however many digits you want, not just 4.
A generic way of doing that without bounds to specific length or range, using Array.from, map, and padStart:
const generatingNumber = (start, end) => {
const range = end - start + 1;
const maxLen = ('' + end).length;
return Array.from({length: range}).map((_, i) =>
(i + start).toString().padStart(maxLen, '0'));
};
console.log(generatingNumber(5, 3000).join('\n'));
In the first chunk of my code I have an ' if ' statement that is not working as it should, and I can't figure out why.
When using the argument 'hous', it should enter the first ' if ' statement and return 0. It returns -1 instead.
var firstUniqChar = function(s) {
for (let i = 0; i < s.length; i++){
let letter = s[i];
// console.log('s[i]: ' + letter);
// console.log(s.slice(1));
// console.log( 'i: ' + i);
if ((i = 0) && !(s.slice(1).includes(letter))) {
return 0;
}
if ((i = s.length - 1) && !(s.slice(0, i).includes(letter))) {
return 1;
}
if(!(s.slice(0, i).includes(letter)) && !(s.slice(i + 1).includes(letter))) {
return 2;
}
}
return -1;
};
console.log(firstUniqChar("hous"));
This is another way you can write your function:
const firstUniqChar = s => [...s].filter(c=>!(s.split(c).length-2))[0] || -1;
console.log(firstUniqChar("hous"));
console.log(firstUniqChar("hhoous"));
console.log(firstUniqChar("hhoouuss"));
Look up method for scattered repeated characters and functional find()-based approach
You may break your input string into array of characters (e.g. using spread syntax ...) and make use of Array.prototype.find() (to get character itserlf) or Array.prototype.findIndex() (to get non repeating character position) by finding the character that is different from its neighbors:
const src = 'hhoous',
getFirstNonRepeating = str =>
[...str].find((c,i,s) =>
(!i && c != s[i+1]) ||
(c != s[i-1] && (!s[i+1] || c != s[i+1])))
console.log(getFirstNonRepeating(src))
.as-console-wrapper{min-height:100%;}
Above will work perfectly when your repeating characters are groupped together, if not, I may recommend to do 2-passes over the array of characters - one, to count ocurrence of each character, and one more, to find out the first unique:
const src = 'hohuso',
getFirstUnique = str => {
const hashMap = [...str].reduce((r,c,i) =>
(r[c]=r[c]||{position:i, count:0}, r[c].count++, r), {})
return Object
.entries(hashMap)
.reduce((r,[char,{position,count}]) =>
((count == 1 && (!r.char || position < r.position)) &&
(r = {char, position}),
r), {})
}
console.log(getFirstUnique(src))
.as-console-wrapper{min-height:100%;}
function nonRepeat(str) {
return Array
.from(str)
.find((char) => str.match(newRegExp(char,'g')).length === 1);
}
console.log(nonRepeat('abacddbec')); // e
console.log(nonRepeat('1691992933')); // 6
console.log(nonRepeat('thhinkninw')); // t