Recursion Printing Balanced Parantheses - javascript

I have a recursion function which prints all the valid combinations of balanced parentheses as follows :
function addParen(upstock, downstock, sa)
{
if (upstock == 0 && downstock == 0)
{
Print(sa);
}
if (upstock > 0)
{
addParen(upstock - 1, downstock + 1, sa + "(");
}
if (downstock > 0)
{
addParen(upstock, downstock - 1, sa + ")");
}
}
It directly prints the result as strings like "((()))" or "()()()" for n=3 ( we assume 3 pairs, number of pairs is not important). However I want my recursive function to print one by one each parenthesis whenever the initial empty string concatenated with a "(" or ")".For instance for the first combination I want it to print like "(" then "(" then "(" then ")" then ")" and then ")".Then it can proceed with the same way for the second combination.Is it possible to do it ?

yours is not working with the balanced parenthesis because you need to make another checks. Bellow I am putting the result with the total number of combinations.
function addParen(n, upstock, downstock, sa){
var count = 0;
if(sa.length == 2 * n && upstock == downstock){
console.log(sa);
//number of valid combinations
return 1;
}
if(upstock >= downstock && upstock <= n){
count += addParen(n, upstock + 1, downstock, sa + "(");
}
if(downstock < upstock){
count += addParen(n, upstock, downstock + 1, sa + ")");
}
return count;
}
function numberOfBalancedParenthesis(n){
return addParen(n, 0, 0, "");
}
//try this on the console
numberOfBalancedParenthesis(2)

Hope this is what you're looking for!
function addParen( upstock, downstock ) {
if (upstock > 0 )
{
Print("(");
addParen(upstock - 1, downstock+1);
}
if (downstock > 0 )
{
Print(")");
addParen(upstock, downstock - 1);
}
}

Related

Remove invalid parenthesis - JS (Leetcode301)

I've been taking leetcode challenges and come across this interesting problem: 301. Remove Invalid Parentheses:
Given a string s that contains parentheses and letters, remove the minimum number of invalid parentheses to make the input string valid.
Return all the possible results. You may return the answer in any order.
Example 1:
Input: s = "()())()"
Output: ["(())()","()()()"]
Example 2:
Input: s = "(a)())()"
Output: ["(a())()","(a)()()"]
Example 3:
Input: s = ")("
Output: [""]
Constraints:
1 <= s.length <= 25
s consists of lowercase English letters and parentheses '(' and ')'.
There will be at most 20 parentheses in s.
I tried to solve it using recursion, but something is not right about my code, it's returning an empty array instead of the results.
Here is the code:
var removeInvalidParentheses = function(s) {
const validExpressions = [];
let minimumRemoved = 0;
function recurse(s, index, leftCount, rightCount, expression, removedCount) {
let possibleAnswer;
if (index === s.length) {
if (leftCount === rightCount) {
if (removedCount <= minimumRemoved) {
possibleAnswer = expression.join("");
if (removedCount < minimumRemoved) {
validExpressions.length = 0;
minimumRemoved = removedCount;
}
validExpressions.push(possibleAnswer);
}
}
} else {
let currentCharacter = s[index];
let length = expression.length;
if (currentCharacter !== '(' && currentCharacter !== ')') {
expression.push(currentCharacter);
recurse(s, index + 1, leftCount, rightCount, expression, removedCount);
expression.splice(length, 1);
} else {
recurse(s, index + 1, leftCount, rightCount, expression, removedCount + 1);
expression.push(currentCharacter);
if (currentCharacter == '(') {
recurse(s, index + 1, leftCount + 1, rightCount, expression, removedCount);
} else if (rightCount < leftCount) {
recurse(s, index + 1, leftCount, rightCount + 1, expression, removedCount);
}
expression.splice(length, 1);
}
}
}
recurse(s, 0, 0, 0, [], 0);
return validExpressions;
}
The problem is that you initialise minimumRemoved = 0 which makes it impossible to ever satisfy the condition if (removedCount <= minimumRemoved) { except if removeCount is 0. But if there is no match in that condition, nothing will be ever pushed to validExpressions.
You'll want to mimic the worst situation possible, so that any solution will be considered to be better than that. Therefore initialise removedCount as a high number, like s.length + 1 or -- why not -- Infinity.
There is a second issue in your code: it may collect duplicate values in validExpressions, so make your return statement like this:
return [...new Set(validExpressions)];
This will make it work. Now, this is not a very fast solution. You may want to try to find improvements.
The problems are correctly highlihted by trincot. Here is the final code:
let removeInvalidParentheses = function (s) {
const validExpressions = [];
let minimumRemoved = Number.MAX_SAFE_INTEGER;
function recurse(
s,
index,
leftCount,
rightCount,
expression,
removedCount
) {
let possibleAnswer;
// If we have reached the end of string.
if (index === s.length) {
// If the current expression is valid.
if (leftCount === rightCount) {
// If the current count of removed parentheses is <= the current minimum count
if (removedCount <= minimumRemoved) {
// Convert StringBuilder to a String. This is an expensive operation.
// So we only perform this when needed.
possibleAnswer = expression.join("");
// If the current count beats the overall minimum we have till now
if (removedCount < minimumRemoved) {
validExpressions.length = 0;
minimumRemoved = removedCount;
}
validExpressions.push(possibleAnswer);
}
}
} else {
let currentCharacter = s[index];
let length = expression.length;
// If the current character is neither an opening bracket nor a closing one,
// simply recurse further by adding it to the expression StringBuilder
if (currentCharacter !== "(" && currentCharacter !== ")") {
expression.push(currentCharacter);
recurse(
s,
index + 1,
leftCount,
rightCount,
expression,
removedCount
);
expression.splice(length, 1);
} else {
// Recursion where we delete the current character and move forward
recurse(
s,
index + 1,
leftCount,
rightCount,
expression,
removedCount + 1
);
expression.push(currentCharacter);
// If it's an opening parenthesis, consider it and recurse
if (currentCharacter == "(") {
recurse(
s,
index + 1,
leftCount + 1,
rightCount,
expression,
removedCount
);
} else if (rightCount < leftCount) {
// For a closing parenthesis, only recurse if right < left
recurse(
s,
index + 1,
leftCount,
rightCount + 1,
expression,
removedCount
);
}
// Undoing the append operation for other recursions.
expression.splice(length, 1);
}
}
}
recurse(s, 0, 0, 0, [], 0);
return Array.from(new Set(validExpressions));
};

How can I reverse this alphabet id getting system to go the other way around?

So this function makes it possible to get an alphabet character or multiple depending on what number you send in. I want to reverse this function so that when I send in the character, I receive a number. I have been able to achieve this with single letters.
I have already created an alphabetic id system by first generating a number then converting it into a letter in the alphabet using this id as the index. But what I want to do is after 26 characters have been used i would like for it to continue with AA, AB, AC and so on.
EDIT: I want to clarify that my thought was that the converter should extend beyond
A-series. So after AZ it goes to BA BB.. BZ, CA CB CC and so on.
Also what i want get return is a number and not the letter. The original function gives me the letters, now I need to reverse it to get back the number i sent in the previous function
function convertNumberToId (number) {
var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('');
if (number < alphabet.length) {
return alphabet[number];
} else {
return (
convertNumberToId(Math.floor(number / alphabet.length) - 1)
+
convertNumberToId(number % alphabet.length)
);
}
}
In the if statement you get a letter back if the number does not exceed the length of the array but once it does you'll get AA, AB, AC and so on. Now I want to reverse this.
This is what I have achieved so far:
function convertIdToNumber(id){
var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('');
if($.inArray(id , alphabet) !== -1){
return alphabet.indexOf(id);
}
else{
}
}
REVISED (Start at 0 Index)
Now we calculate value from right to left. As you move to the left, the value of the character is multiplied by the length of the alphabet raised to the number of digits you've moved left. Handle offset of 1.
function convertNumberToId (number) {
var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('');
if (number < alphabet.length) {
return alphabet[number];
} else {
return (
convertNumberToId(Math.floor(number / alphabet.length) - 1)
+
convertNumberToId(number % alphabet.length)
);
}
}
function convertIdToNumber(id){
var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('');
if(id.length == 1){
return alphabet.indexOf(id) >= 0 ? alphabet.indexOf(id) : -1;
}
else if (id.length > 0){
let t = 0;
for (let i = 0; i < id.length; i++) {
t += (1 + convertIdToNumber(id.charAt(id.length - 1 - i))) * Math.pow(alphabet.length, i);
}
t--;
return t;
} else {
return -1;
}
}
console.log(convertIdToNumber('b'));
console.log(convertNumberToId(1));
console.log(convertIdToNumber('abc'));
console.log(convertNumberToId(730));
Find the value of each character in the id, add the length of the alphabet for each additional character, and handle the offset of 1 when you have multiple characters.
Start at index 0
function convertIdToNumber(id){
var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('');
if(id.length == 1){
return alphabet.indexOf(id);
}
else if (id.length > 0) {
return convertIdToNumber(id.split('')[0])
+ alphabet.length
+ convertIdToNumber(id.slice(1))
+ 1;
} else {
return -1;
}
}
console.log(convertIdToNumber('ac'));
Start at index 1
function convertIdToNumber(id){
var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('');
if(id.length == 1){
return alphabet.indexOf(id) >= 0 ? alphabet.indexOf(id) + 1 : -1;
}
else if (id.length > 0) {
return convertIdToNumber(id.split('')[0])
+ alphabet.length
+ convertIdToNumber(id.slice(1))
- 1;
} else {
return -1;
}
}
console.log(convertIdToNumber('ac'));
function convertIdToNumber(id){
const alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('');
if ($.inArray(id , alphabet) !== -1){
// Can remove the add one here if you want 'a' = 0
return alphabet.indexOf(id) + 1;
} else {
return (Math.pow(26, id.length - 1) * (alphabet.indexOf(id.charAt(0)) + 1)) + convertIdToNumber(id.slice(1));
}
}
console.log(convertIdToNumber("aa")); // 27
console.log(convertIdToNumber("ab")); // 28
console.log(convertIdToNumber("ac")); // 29
console.log(convertIdToNumber("zz")); // 702
console.log(convertIdToNumber("abc")); // 731
JQuery live demo: https://jsfiddle.net/chkt65n7/5/
I used your original function but I assumed "a" in this case was 1 as you mentioned "aa" was 27 but if that part is different then you can minus the one from the ending recursive case if you want.
Revised to handle more than 2 characters in a string

How to split a string based on commas except inside parenthesis

I have a string that contains a create query, and I'm trying to split it based on commas. Unfortunately, some of the lines have commas in them surrounded by parenthesis.
Example:
dbo.Display_Test1.Column,
CASE WHEN CHARINDEX(' To ', Test3) > 0 AND CHARINDEX('XVR', Test3) = 0 THEN LEFT(Test3, (CHARINDEX(' To ', Test3) - 12)) ELSE Test3 END AS Test3,
dbo.Display_Test2.Column,
ISNULL((CASE WHEN [2-Display-Test4].[Total Number] > 0 AND [1-Display-Test5].SumOfNumber = 0 THEN 0 ELSE (([2-Display-Test4].[Total Number] * 1000) / [1-Display-Test5].SumOfNumber) END), 0) AS Test6,
I want to split my string based on commas that aren't inside of (possibly multiple) parenthesis. For reference, my example should be split where the line breaks are (although my string doesn't have the line breaks in it).
I've tried a number of different solutions, but none work quite right:
/(?:\(*[^()]*\)|[^,])+/g works on lines 1,3, and 4, but fails on line 2. It breaks up the line into multiple matches.
/((?:[^,(]+|(\((?:[^()]+)|$1\)))+)/g works on lines 1,2, and 3, but fails on line 4. It also breaks up the line into multiple matches.
I can't quite seem to make it work. Any help is appreciated.
One solution possible : ( note : we remove the separations comas )
var str = "dbo.Display_Test1.Column, CASE WHEN CHARINDEX(' To ', Test3) > 0 AND CHARINDEX('XVR', Test3) = 0 THEN LEFT(Test3, (CHARINDEX(' To ', Test3) - 12)) ELSE Test3 END AS Test3, dbo.Display_Test2.Column, ISNULL((CASE WHEN [2-Display-Test4].[Total Number] > 0 AND [1-Display-Test5].SumOfNumber = 0 THEN 0 ELSE (([2-Display-Test4].[Total Number] * 1000) / [1-Display-Test5].SumOfNumber) END), 0) AS Test6,";
// if the string don't end by a coma , we add it.
var strArr = str.replace(/,*\s*$/ , ',').split('');
var res;
res = strArr.reduce(function( trans , charValue ){
if(charValue === '(') {
trans.deep++;
}
if(charValue === ')') {
trans.deep--;
}
if( trans.deep === 0){
if(charValue===',') {
trans.arr.push( trans.str);
trans.str = '';
}else{
trans.str += charValue;
}
}else{
trans.str += charValue;
}
return trans;
}, { arr : [] , str : '' ,deep : 0});
document.write('<pre>' + JSON.stringify(res.arr , null , ' ') + '</pre>');
Edit :
As suggested by Thriggle in the comments i've added the case where the string don't end by a coma.
changed variables name .
Of course if we want to work with unlimited nesting of parentheses then it would be impossible to avoid a loop. Still for some bounded nesting we do can write a regular expression capturing it.
For up to 1 parentheses level it could be
/((?:[^(),]|\([^()]*\))+)/g
For up to 2 parentheses level (probably it's your case)
/((?:[^(),]|\((?:[^()]|\([^()]*\))*\))+)/g
And so on, recursively substituting [^()]* with (?:[^()]|\([^()]*\))*
Regex won't get you where you want and still allow arbitrary complexity.
One approach is to iterate through each character in the string, keeping track of when you hit open and close parentheses, and only splitting on commas when the numbers of open and close parentheses cancel each other out (so you know you're not within a parenthetical statement).
function splitQuery(input){
var arr = [];
var lastStart = 0;
var open = 0;
for(var i = 0, len = input.length; i < len; i++){
var curr = input[i];
if(curr === "("){open += 1;}
else if(curr === ")"){ open = open < 1 ? 0 :open -=1;}
else if(curr === ","){
if(open === 0){
arr.push(input.substring(lastStart,i));
lastStart = i+1;
}
}else if(i+1 === len){
arr.push(input.substring(lastStart,i+1));
}
}
return arr;
}
Just be aware that this approach can be expensive (from a performance perspective) when dealing with especially large strings.
Here's a working example:
var query = "dbo.Display_Test1.Column, CASE WHEN CHARINDEX(' To ', Test3) > 0 AND CHARINDEX('XVR', Test3) = 0 THEN LEFT(Test3, (CHARINDEX(' To ', Test3) - 12)) ELSE Test3 END AS Test3, dbo.Display_Test2.Column, ISNULL((CASE WHEN [2-Display-Test4].[Total Number] > 0 AND [1-Display-Test5].SumOfNumber = 0 THEN 0 ELSE (([2-Display-Test4].[Total Number] * 1000) / [1-Display-Test5].SumOfNumber) END), 0) AS Test6,";
var split = splitQuery(query);
var output = document.getElementById("output")
for(var el in split){
output.insertAdjacentHTML("beforeend",el + ": "+ split[el]+"<hr/>");
}
function splitQuery(input){
var arr = [];
var lastStart = 0;
var open = 0;
for(var i = 0, len = input.length; i < len; i++){
var curr = input[i];
if(curr === "("){open += 1;}
else if(curr === ")"){ open = open < 1 ? 0 :open -=1;}
else if(curr === ","){
if(open === 0){
arr.push(input.substring(lastStart,i));
lastStart = i+1;
}
}else if(i+1 === len){
arr.push(input.substring(lastStart,i+1));
}
}
return arr;
}
<div id="output"></div>

Inserting into a number string

Have the function DashInsert(num) insert dashes ('-') between each two odd numbers in num. For example: if num is 454793 the output should be 4547-9-3. Don't count zero as an odd number.
Here is my code (not working). When I run it, I get the same response as an infinite loop where I have to kill the page but I can't see why. I know there are ways to do this by keeping it as a string but now I'm wondering why my way isn't working. Thanks...
function DashInsert(num) {
num = num.split("");
for (i = 1; i < num.length; i++) {
if (num[i - 1] % 2 != 0 && num[i] % 2 != 0) {
num.splice(i, 0, "-");
}
}
num = num.join("");
return num;
}
Using num.splice you are inserting new entries into the array, therefor increasing its length – and that makes the value of i “running behind” the increasing length of the array, so the break condition is never met.
And apart from that, on the next iteration after inserting a -, num[i-1] will be that - character, and therefor you are practically trying to check if '-' % 2 != 0 … that makes little sense as well.
So, when you insert a - into the array, you have to increase i by one as well – that will a) account for the length of the array having increased by one, and also it will check the next digit after the - on the next iteration:
function DashInsert(num) {
num = num.split("");
for (i = 1; i < num.length; i++) {
if (num[i - 1] % 2 != 0 && num[i] % 2 != 0) {
num.splice(i, 0, "-");
i++; // <- this is the IMPORTANT part!
}
}
num = num.join("");
return num;
}
alert(DashInsert("454793"));
http://jsfiddle.net/37wA9/
Once you insert a dash -, the if statement is checking this '-'%2 != 0 which is always true and thus inserts another dash, ad infinitum.
Here's one way to do it with replace using a regex and function:
function DashInsert(n) {
var f = function(m,i,s) { return m&s[i+1]&1 ? m+'-' : m; };
return String(n).replace(/\d/g,f);
}
DashInsert(454793) // "4547-9-3"
When you are adding a dash, this dash will be processed as a number on the next iteration. You need to forward one step.
function DashInsert(num) {
var num = num.split("");
for (var i = 1; i < num.length; i++) {
if ((num[i - 1] % 2 != 0) && (num[i] % 2 != 0)) {
num.splice(i, 0, "-");
i++; // This is the only thing that needs changing
}
}
num = num.join("");
return num;
}
It's because there are cases when you use the % operator on dash '-' itself, e.g. right after you splice a dash into the array.
You can correct this behavior by using a clone array.
function DashInsert(num) {
num = num.split("");
var clone = num.slice(0);
var offset = 0;
for (i = 1; i < num.length; i++) {
if (num[i - 1] % 2 != 0 && num[i] % 2 != 0) {
clone.splice(i + offset, 0, "-");
offset++;
}
}
return clone.join("");
}
alert(DashInsert("45739"));
Output: 45-7-3-9
Demo: http://jsfiddle.net/262Bf/
To complement the great answers already given, I would like to share an alternative implementation, that doesn't modify arrays in-place:
function DashInsert(num) {
var characters = num.split("");
var numbers = characters.map(function(chr) {
return parseInt(chr, 10);
});
var withDashes = numbers.reduce(function(result, current) {
var lastNumber = result[result.length - 1];
if(lastNumber == null || current % 2 === 0 || lastNumber % 2 === 0) {
return result.concat(current);
} else {
return result.concat("-", current);
}
}, []);
return withDashes.join("");
}
It's longer, but IMHO reveals the intention better, and avoids the original issue.

Convert column index into corresponding column letter

I need to convert a Google Spreadsheet column index into its corresponding letter value, for example, given a spreadsheet:
I need to do this (this function obviously does not exist, it's an example):
getColumnLetterByIndex(4); // this should return "D"
getColumnLetterByIndex(1); // this should return "A"
getColumnLetterByIndex(6); // this should return "F"
Now, I don't recall exactly if the index starts from 0 or from 1, anyway the concept should be clear.
I didn't find anything about this on gas documentation.. am I blind? Any idea?
Thank you
I wrote these a while back for various purposes (will return the double-letter column names for column numbers > 26):
function columnToLetter(column)
{
var temp, letter = '';
while (column > 0)
{
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
function letterToColumn(letter)
{
var column = 0, length = letter.length;
for (var i = 0; i < length; i++)
{
column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
}
return column;
}
This works good
=REGEXEXTRACT(ADDRESS(ROW(); COLUMN()); "[A-Z]+")
even for columns beyond Z.
Simply replace COLUMN() with your column number. The value of ROW() doesn't matter.
No need to reinvent the wheel here, use the GAS range instead:
var column_index = 1; // your column to resolve
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange(1, column_index, 1, 1);
Logger.log(range.getA1Notation().match(/([A-Z]+)/)[0]); // Logs "A"
=SUBSTITUTE(ADDRESS(1,COLUMN(),4), "1", "")
This takes your cell, gets it's address as e.g. C1, and removes the "1".
How it works
COLUMN() gives the number of the column of the cell.
ADDRESS(1, ..., <format>) gives an address of a cell, in format speficied by <format> parameter. 4 means the address you know - e.g. C1.
The row doesn't matter here, so we use 1.
See ADDRESS docs
Finally, SUBSTITUTE(..., "1", "") replaces the 1 in the address C1, so you're left with the column letter.
This works on ranges A-Z
formula =char(64+column())
js String.fromCharCode(64+colno)
an google spreadsheet appscript code, based on #Gardener would be:
function columnName(index) {
var cname = String.fromCharCode(65 + ((index - 1) % 26));
if (index > 26)
cname = String.fromCharCode(64 + (index - 1) / 26) + cname;
return cname;
}
In javascript:
X = (n) => (a=Math.floor(n/26)) >= 0 ? X(a-1) + String.fromCharCode(65+(n%26)) : '';
console.assert (X(0) == 'A')
console.assert (X(25) == 'Z')
console.assert (X(26) == 'AA')
console.assert (X(51) == 'AZ')
console.assert (X(52) == 'BA')
Adding to #SauloAlessandre's answer, this will work for columns up from A-ZZ.
=if(column() >26,char(64+(column()-1)/26),) & char(65 + mod(column()-1,26))
I like the answers by #wronex and #Ondra Žižka. However, I really like the simplicity of #SauloAlessandre's answer.
So, I just added the obvious code to allow #SauloAlessandre's answer to work for wider spreadsheets.
As #Dave mentioned in his comment, it does help to have a programming background, particularly one in C where we added the hex value of 'A' to a number to get the nth letter of the alphabet as a standard pattern.
Answer updated to catch the error pointed out by #Sangbok Lee. Thank you!
I was looking for a solution in PHP. Maybe this will help someone.
<?php
$numberToLetter = function(int $number)
{
if ($number <= 0) return null;
$temp; $letter = '';
while ($number > 0) {
$temp = ($number - 1) % 26;
$letter = chr($temp + 65) . $letter;
$number = ($number - $temp - 1) / 26;
}
return $letter;
};
$letterToNumber = function(string $letters) {
$letters = strtoupper($letters);
$letters = preg_replace("/[^A-Z]/", '', $letters);
$column = 0;
$length = strlen($letters);
for ($i = 0; $i < $length; $i++) {
$column += (ord($letters[$i]) - 64) * pow(26, $length - $i - 1);
}
return $column;
};
var_dump($numberToLetter(-1));
var_dump($numberToLetter(26));
var_dump($numberToLetter(27));
var_dump($numberToLetter(30));
var_dump($letterToNumber('-1A!'));
var_dump($letterToNumber('A'));
var_dump($letterToNumber('B'));
var_dump($letterToNumber('Y'));
var_dump($letterToNumber('Z'));
var_dump($letterToNumber('AA'));
var_dump($letterToNumber('AB'));
Output:
NULL
string(1) "Z"
string(2) "AA"
string(2) "AD"
int(1)
int(1)
int(2)
int(25)
int(26)
int(27)
int(28)
Simple way through Google Sheet functions, A to Z.
=column(B2) : value is 2
=address(1, column(B2)) : value is $B$1
=mid(address(1, column(B2)),2,1) : value is B
It's a complicated way through Google Sheet functions, but it's also more than AA.
=mid(address(1, column(AB3)),2,len(address(1, column(AB3)))-3) : value is AB
I also was looking for a Python version here is mine which was tested on Python 3.6
def columnToLetter(column):
character = chr(ord('A') + column % 26)
remainder = column // 26
if column >= 26:
return columnToLetter(remainder-1) + character
else:
return character
A comment on my answer says you wanted a script function for it. All right, here we go:
function excelize(colNum) {
var order = 1, sub = 0, divTmp = colNum;
do {
divTmp -= order; sub += order; order *= 26;
divTmp = (divTmp - (divTmp % 26)) / 26;
} while(divTmp > 0);
var symbols = "0123456789abcdefghijklmnopqrstuvwxyz";
var tr = c => symbols[symbols.indexOf(c)+10];
return Number(colNum-sub).toString(26).split('').map(c=>tr(c)).join('');
}
This can handle any number JS can handle, I think.
Explanation:
Since this is not base26, we need to substract the base times order for each additional symbol ("digit"). So first we count the order of the resulting number, and at the same time count the number to substract. And then we convert it to base 26 and substract that, and then shift the symbols to A-Z instead of 0-P.
Anyway, this question is turning into a code golf :)
Java Apache POI
String columnLetter = CellReference.convertNumToColString(columnNumber);
This will cover you out as far as column AZ:
=iferror(if(match(A2,$A$1:$AZ$1,0)<27,char(64+(match(A2,$A$1:$AZ$1,0))),concatenate("A",char(38+(match(A2,$A$1:$AZ$1,0))))),"No match")
A function to convert a column index to letter combinations, recursively:
function lettersFromIndex(index, curResult, i) {
if (i == undefined) i = 11; //enough for Number.MAX_SAFE_INTEGER
if (curResult == undefined) curResult = "";
var factor = Math.floor(index / Math.pow(26, i)); //for the order of magnitude 26^i
if (factor > 0 && i > 0) {
curResult += String.fromCharCode(64 + factor);
curResult = lettersFromIndex(index - Math.pow(26, i) * factor, curResult, i - 1);
} else if (factor == 0 && i > 0) {
curResult = lettersFromIndex(index, curResult, i - 1);
} else {
curResult += String.fromCharCode(64 + index % 26);
}
return curResult;
}
function lettersFromIndex(index, curResult, i) {
if (i == undefined) i = 11; //enough for Number.MAX_SAFE_INTEGER
if (curResult == undefined) curResult = "";
var factor = Math.floor(index / Math.pow(26, i));
if (factor > 0 && i > 0) {
curResult += String.fromCharCode(64 + factor);
curResult = lettersFromIndex(index - Math.pow(26, i) * factor, curResult, i - 1);
} else if (factor == 0 && i > 0) {
curResult = lettersFromIndex(index, curResult, i - 1);
} else {
curResult += String.fromCharCode(64 + index % 26);
}
return curResult;
}
document.getElementById("result1").innerHTML = lettersFromIndex(32);
document.getElementById("result2").innerHTML = lettersFromIndex(6800);
document.getElementById("result3").innerHTML = lettersFromIndex(9007199254740991);
32 --> <span id="result1"></span><br> 6800 --> <span id="result2"></span><br> 9007199254740991 --> <span id="result3"></span>
In python, there is the gspread library
import gspread
column_letter = gspread.utils.rowcol_to_a1(1, <put your col number here>)[:-1]
If you cannot use python, I suggest looking the source code of rowcol_to_a1() in https://github.com/burnash/gspread/blob/master/gspread/utils.py
Here's a two liner which works beyond ZZ using recursion:
Python
def col_to_letter(n):
l = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
return col_to_letter((n-1)//26) + col_to_letter(n%26) if n > 26 else l[n-1]
Javascript
function colToLetter(n) {
l = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
return n > 26 ? colToLetter(Math.floor((n-1)/26)) + colToLetter(n%26) : l[n-1]
}
If you need a version directly in the sheet, here a solution:
For the colonne 4, we can use :
=Address(1,4)
I keep the row number to 1 for simplicty.
The above formula returns $D$1 which is not what you want.
By modifying the formula a little bit we can remove the dollar signs in the cell reference.
=Address(1,4,4)
Adding four as the third argument tells the formula that we are not looking for absolute cell reference.
Now the returns is : D1
So you only need to remove the 1 to get the colonne lettre if you need, for example with :
=Substitute(Address(1,4,4),"1","")
That returns D.
This is a way to convert column letters to column numbers.
=mmult(ArrayFormula(ifna(vlookup(substitute(mid(rept(" ",3-len(filter(A:A,A:A<>"")))&filter(A:A,A:A<>""),sequence(1,3),1)," ",""),{char(64+sequence(26)),sequence(26)},2,0),0)*{676,26,1}),sequence(3,1,1,0))
Screenshot of the Google Sheet
Don't use 26 radix. Like below.
const n2c = n => {
if (!n) return '';
// Column number to 26 radix. From 0 to p.
// Column number starts from 1. Subtract 1.
return [...(n-1).toString(26)]
// to ascii number
.map(c=>c.charCodeAt())
.map((c,i,arr)=> {
// last digit
if (i===arr.length-1) return c;
// 10 -> p
else if (arr.length - i > 2 && arr[i+1]===48) return c===49 ? null : c-2;
// 0 -> p
else if (c===48) return 112;
// a-1 -> 9
else if (c===97) return 57;
// Subtract 1 except last digit.
// Look at 10. This should be AA not BA.
else return c-1;
})
.filter(c=>c!==null)
// Convert with the ascii table. [0-9]->[A-J] and [a-p]->[K-Z]
.map(a=>a>96?a-22:a+17)
// to char
.map(a=>String.fromCharCode(a))
.join('');
};
const table = document.createElement('table');
table.border = 1;
table.cellPadding = 3;
for(let i=0, row; i<1380; i++) {
if (i%5===0) row = table.insertRow();
row.insertCell().textContent = i;
row.insertCell().textContent = n2c(i);
}
document.body.append(table);
td:nth-child(odd) { background: gray; color: white; }
td:nth-child(even) { background: silver; }
Simple typescript functional approach
const integerToColumn = (integer: number): string => {
const base26 = (x: number): string =>
x < 26
? String.fromCharCode(65 + x)
: base26((x / 26) - 1) + String.fromCharCode(65 + x % 26)
return base26(integer)
}
console.log(integerToColumn(0)) // "A"
console.log(integerToColumn(1)) // "B"
console.log(integerToColumn(2)) // "C"
Here is a general version written in Scala. It's for a column index start at 0 (it's simple to modify for an index start at 1):
def indexToColumnBase(n: Int, base: Int): String = {
require(n >= 0, s"Index is non-negative, n = $n")
require(2 <= base && base <= 26, s"Base in range 2...26, base = $base")
def digitFromZeroToLetter(n: BigInt): String =
('A' + n.toInt).toChar.toString
def digitFromOneToLetter(n: BigInt): String =
('A' - 1 + n.toInt).toChar.toString
def lhsConvert(n: Int): String = {
val q0: Int = n / base
val r0: Int = n % base
val q1 = if (r0 == 0) (n - base) / base else q0
val r1 = if (r0 == 0) base else r0
if (q1 == 0)
digitFromOneToLetter(r1)
else
lhsConvert(q1) + digitFromOneToLetter(r1)
}
val q: Int = n / base
val r: Int = n % base
if (q == 0)
digitFromZeroToLetter(r)
else
lhsConvert(q) + digitFromZeroToLetter(r)
}
def indexToColumnAtoZ(n: Int): String = {
val AtoZBase = 26
indexToColumnBase(n, AtoZBase)
}
In PowerShell:
function convert-IndexToColumn
{
Param
(
[Parameter(Mandatory)]
[int]$col
)
"$(if($col -gt 26){[char][int][math]::Floor(64+($col-1)/26)})$([char](65 + (($col-1) % 26)))"
}
Here is a 0-indexed JavaScript function without a maximum value, as it uses a while-loop:
function indexesToA1Notation(row, col) {
const letterCount = 'Z'.charCodeAt() - 'A'.charCodeAt() + 1;
row += 1
let colName = ''
while (col >= 0) {
let rem = col % letterCount
colName = String.fromCharCode('A'.charCodeAt() + rem)
col -= rem
col /= letterCount
}
return `${colName}${row}`
}
//Test runs:
console.log(indexesToA1Notation(0,0)) //A1
console.log(indexesToA1Notation(37,9)) //J38
console.log(indexesToA1Notation(5,747)) //ABT6
I wrote it for a web-app, so I'm not 100% sure it works in Google Apps Script, but it is normal JavaScript, so I assume it will.
For some reason I cant get the snippet to show its output, but you can copy the code to some online playground if you like
Here's a zero-indexed version (in Python):
letters = []
while column >= 0:
letters.append(string.ascii_uppercase[column % 26])
column = column // 26 - 1
return ''.join(reversed(letters))

Categories