要点
将数字(0,1,2...)转为excel的列名(A,B,C...)
/**
* @param number num 0,1,2,3...
* @return string A-Z,AA-ZZ,AAA-ZZZ....(excel的列名)
*/
const getColumnByNum = (num) => {
let offset = 26
const getCode = (num) => String.fromCharCode('A'.charCodeAt(0) + num)
const digits = 26
let length = 1//至少有一位字母
while (num >= offset) {
num = num - offset//累减 -(26+26^2+...+26^N)
offset = offset * 26
length++
}
let str = getCode(num % digits)
while (num >= digits) {
num = Math.floor(num / digits)
str = getCode(num % digits) + str
}
str = 'A'.repeat(length - str.length) + str//往前补加A
return str
};
/**
* @param number end 0,1,2,3...
* @param number start 0,1,2,3...
* @return string[] [A,B,C,D...,XXX](excel的列名)
*/
const getColumns = (end,start)=>{
let arr = start ? [getColumnByNum(start)]:['A']
const getNextColumn = (column)=>{
let str = ''
let differ = 1
for(let i=column.length-1;i>-1;i--){
if(column[i]==='Z' && differ!==0){
str ='A' + str
}else{
str = String.fromCharCode(column[i].charCodeAt(0) + differ) + str
differ = 0
}
}
differ === 1 &&(str = 'A' + str )//说明column为Z+
return str
}
for(let i = 0,len = end-(start || 0) ;i<len;i++){
arr.push(getNextColumn(arr[i]))
}
return arr
}
//测试
((start, end) => {
let arr = getColumns(end,start)
for (let i = start; i < end; i++) {
let str = getColumnByNum(i)
let nextStr = getColumnByNum(i + 1)
if(arr[i-start] !== str){
throw new Error(`第 "${i}" 列 "${str}"、"${arr[i-start]}" 出错`)
}
if (/[^A-Z]/.test(str)) {
throw new Error(`第 "${i}" 列 "${str}" 含有错误字符`)
}
if (str.length === nextStr.length) {
//如果长度相等
let differ = 1
for (let j = str.length - 1; j > -1; j--) {
//如果当前结尾为Z
if (str.charAt(j) === 'Z' && nextStr.charAt(j) === 'A') {
} else if (nextStr.charCodeAt(j) - str.charCodeAt(j) === differ) {
differ = 0
} else {
throw new Error(`第 "${i}" 列 "${str}" 发生错误,与下一列 "${nextStr}" 未相邻`)
}
}
if (differ !== 0) {
throw new Error(`第 "${i}" 列 "${str}" 发生错误,与下一列 "${nextStr}" 未相邻`)
}
} else //如果长度不相等,则str肯定是Z+,nextStr肯定是A+,且A+比Z+多一个
if (!(/^[Z]+$/.test(str)
&& /^[A]+$/.test(nextStr)
&& nextStr.length - str.length === 1)) {
throw new Error(`第 "${i}" 列 "${str}" 发生错误,与下一列 "${nextStr}" 未相邻`)
}
}
console.log('测试通过')
console.log(arr)
})(10000, 1000000);//此处改变测试范围
1 进制转换
在之前,用excel.js写过一个转换,本来打算自己生成列名(将数字(0,1,2...)转为excel的列名(A,B,C...))。后来试了两次居然都失败了,没办法,只好用它提供的方法了,并乖乖复习一下进制转换的内容。
1.1 10进制转换为其他进制
转换为其他进制表现为满“N”进一
const parseDecToDigits = (num: number, digits: number, codeArr: string[])
: string => {
if (digits === 10) {
return String(num)
}
const getCode = (num: number): string => codeArr[num]
let str: string = getCode(num % digits)
while (num >= digits) {
num = Math.floor(num / digits)
str = getCode(num % digits) + str
}
return str
}
1.2 其他进制转为10进制
const parseToDec = (str: string, digits: number, codeArr: string[])
:number => {
if (digits === 10) {
return Number(str) || 0
}
const getNum = (code: string): number => Number(codeArr.findIndex(c => c === code)) || 0
let sum = str.split('').reduce(
(preSum: number, code, index) => preSum
+ getNum(code)
* Math.pow(digits, str.length - index - 1)
, 0)
return sum
}
1.3 测试
const parseDecToDigits = (num, digits, codeArr) => {
if (digits === 10) {
return String(num)
}
const getCode = (num) => codeArr[num]
let str = getCode(num % digits)
while (num >= digits) {
num = Math.floor(num / digits)
str = getCode(num % digits) + str
}
return str
}
const parseToDec = (str, digits, codeArr) => {
if (digits === 10) {
return Number(str) || 0
}
const getNum = (code) => Number(codeArr.findIndex(c => c === code))
let sum = str.toString().split('').reduce(
(preSum, code, index) => preSum
+ getNum(code)
* Math.pow(digits, str.length - index - 1)
, 0)
return sum
}
//测试 将10进制转换为其他进制,再转换为10进制,相等即说明无问题
((minNum, maxNum, minDigits, maxDigits) => {
minDigits < 2 && (minDigits = 2)
for (let digit = minDigits; digit < maxDigits; digit++) {
let codeArr = []
for (let i = 0; i < digit; i++) {
//判断是否大于9,大于9就用字母表示某位
if (i < 10) {
codeArr.push(String(i))
} else {
codeArr.push(String.fromCharCode('A'.charCodeAt(0) + i - 10))
}
}
for (let cnum = minNum; cnum < maxNum; cnum++) {
let str = parseDecToDigits(cnum, digit,codeArr)
let num = parseToDec(str, digit,codeArr)
if (cnum !== num) {
throw new Error(` "${cnum}" 的 "${maxDigits}" 进制转换出错`)
}
}
}
console.log(`${minDigits}-${maxNum}的${minDigits}进制-${maxDigits}进制相互转换,测试通过`)
})(0, 100000, 35, 64)
1.4 任意进制转换为任意进制
const parseDigit = //转换成10进制,再从10进制转换成其他进制
(fromStr: string | number,
fromCodes: string[],
fromDigit: number,
toDigit: number,
toCodes: string[]) => parseDecToDigits(
parseToDec(fromStr.toString(), fromDigit, fromCodes),
toDigit,
toCodes
)
2 将数字(0,1,2...)转为excel的列名(A,B,C...)
2.1 错误版
一开始认为,A-Z 26个字母,转为26进制不就ok了,结果错了
const parseDecToDigits = (num, digits, codeArr) => {
if (digits === 10) {
return String(num)
}
const getCode = (num) => codeArr[num]
let str = getCode(num % digits)
while (num >= digits) {
num = Math.floor(num / digits)
str = getCode(num % digits) + str
}
return str
}
//形成闭包,将[A,B,C...Z]存起来
const getColumnByNum = (() => {
let codeArr = []
//将codeArr变为[A,B,C...Z]
for (let i = 0; i < 26; i++) {
codeArr.push(String.fromCharCode('A'.charCodeAt(0) + i))
}
return (num)=>parseDecToDigits(num, 26, codeArr)
})();//立即执行函数后加分号,不然易报错
//测试
((start, end) => {
let arr = []
for (let i = start; i < end; i++) {
let str = getColumnByNum(i)
let nextStr = getColumnByNum(i + 1)
arr.push(str)
if (/[^A-Z]/.test(str)) {
throw new Error(`第 "${i}" 列 "${str}" 含有错误字符`)
}
if (str.length === nextStr.length) {
//如果长度相等
let differ = 1
for (let j = str.length - 1; j > -1; j--) {
//如果当前结尾为Z
if (str.charAt(j) === 'Z' && nextStr.charAt(j) === 'A') {
} else if (nextStr.charCodeAt(j) - str.charCodeAt(j) === differ) {
if (differ === 1) {
differ = 0
}
} else {
throw new Error(`第 "${i}" 列 "${str}" 发生错误,与下一列 "${nextStr}" 未相邻`)
}
}
if(differ !== 0){
throw new Error(`第 "${i}" 列 "${str}" 发生错误,与下一列 "${nextStr}" 未相邻`)
}
} else //如果长度不相等,则str肯定是Z+,nextStr肯定是A+,且长度...
if (!(/^[Z]+$/.test(str)
&& /^[A]+$/.test(nextStr)
&& nextStr.length - str.length === 1)) {
throw new Error(`第 "${i}" 列 "${str}" 发生错误,与下一列 "${nextStr}" 未相邻`)
}
}
console.log('测试通过')
console.log(arr)
})(0, 1000000);//此处改变测试范围
结果,错了。
2.2 正确版
仔细想了想,excel的列不是26进制,也不是27进制,当A位于左边第一位时,代表1,位于右边时,代表0
且有一位数时,A-Z有26种组合,两位数时AA-ZZ有26*26种组合,即N位有26^N种组合
而10进制数,0-9有10种组合,10-99有100-10种组合,即N位有10^N-10^(N-1)中组合
也就是说,26进制表示方法,N位数值都是从26^N-1开始,到26^N结束
而excel的列,无论几位数,都是从0(即A*A(个数为位数))开始,到26^N(Z*Z)结束
即如下所示:
26进制:0,1,2...25,26...26*26-1,26*26...26*26*26-1...
excel列:0...25,0....26*26-1,0...26*26*26-1
excel的列高于一位时,比26进制多出来26^N-1+...+26个数值,即要减去这个数值
所以编程时,我们先确定excel的列起始位置重置为0,即累减每一个字母该减的数字,
并依此确定列名(A-Z+)的长度
//0-25,一位,26-26*26+25,两位,...上一位的结束数值+1,...上一位的结束数值+26^N
let offset = 26
let length = 0
while(num >= offset){
num = num - offset
offset = offset*26
length++
}
然后,因为重置为0了,把A也是当作0来处理的,前面位数可能会少A,需要补加A
let str = parseDecToDigits(num, 26, codeArr)
str = 'A'.repeat(length+1-str.length) + str//追加A
最后,代码如下
/**
* @param number num 0,1,2,3...
* @return string A-Z,AA-ZZ,AAA-ZZZ....(excel的列名)
*/
const getColumnByNum = (() => {
//形成闭包,将[A,B,C...Z]存起来
let codeArr = []
//将codeArr变为[A,B,C...Z]
for (let i = 0; i < 26; i++) {
codeArr.push(String.fromCharCode('A'.charCodeAt(0) + i))
}
const parseDecToDigits = (num, digits, codeArr) => {
if (digits === 10) {
return String(num)
}
const getCode = (num) => codeArr[num]
let str = getCode(num % digits)
while (num >= digits) {
num = Math.floor(num / digits)
str = getCode(num % digits) + str
}
return str
}
return (num) => {
let offset = 26
let i = 0
while (num >= offset) {
num = num - offset
offset = offset * 26
i++
}
let str = parseDecToDigits(num, 26, codeArr)
str = 'A'.repeat(i + 1 - str.length) + str//追加A
return str
}
})();
//测试
((start, end) => {
let arr = []
for (let i = start; i < end; i++) {
let str = getColumnByNum(i)
let nextStr = getColumnByNum(i + 1)
arr.push(str)
if (/[^A-Z]/.test(str)) {
throw new Error(`第 "${i}" 列 "${str}" 含有错误字符`)
}
if (str.length === nextStr.length) {
//如果长度相等
let differ = 1
for (let j = str.length - 1; j > -1; j--) {
//如果当前结尾为Z
if (str.charAt(j) === 'Z' && nextStr.charAt(j) === 'A') {
} else if (nextStr.charCodeAt(j) - str.charCodeAt(j) === differ) {
if (differ === 1) {
differ = 0
}
} else {
throw new Error(`第 "${i}" 列 "${str}" 发生错误,与下一列 "${nextStr}" 未相邻`)
}
}
if (differ !== 0) {
throw new Error(`第 "${i}" 列 "${str}" 发生错误,与下一列 "${nextStr}" 未相邻`)
}
} else //如果长度不相等,则str肯定是Z+,nextStr肯定是A+,且长度...
if (!(/^[Z]+$/.test(str)
&& /^[A]+$/.test(nextStr)
&& nextStr.length - str.length === 1)) {
throw new Error(`第 "${i}" 列 "${str}" 发生错误,与下一列 "${nextStr}" 未相邻`)
}
}
console.log('测试通过')
console.log(arr)
})(0, 1000000);//此处改变测试范围
2.3 获取列名数组
const getColumnsByNum = (num)=>{
let arr = ['A']
const getNextColumn = (column)=>{
let str = ''
let differ = 1
for(let i=column.length-1;i>-1;i--){
if(column[i]==='Z' && differ !== 0){
str ='A' + str
}else{
str = String.fromCharCode(column[i].charCodeAt(0) + differ) + str
differ = 0
}
}
differ === 1 &&(str = 'A' + str )//说明column为Z+
return str
}
for(let i=0;i<num;i++){
arr.push(getNextColumn(arr[i]))
}
return arr
}