Excel 的字母列号与数字互转思路与 Java 实现

 很早之前碰到过这个题了,当时是学校的一个 ACM 比赛,很遗憾在赛场上时没有解出来。不过这个题还是蛮有意思的,一次偶然无聊的时候,又想起了这个题,当时莫名就有思路了,很快写出了相应的代码实现。不过思路才是最重要的。

题目

先了解一下这个题是什么意思。用过 Excel 都知道,它是由行和列来组织数据的,行号是从 1 开始的数字,列号则是大写字母,如图 1 所示。

那么众所周知,英文字母只有 26 个,当列数超过 26 列时怎么表示呢?

从上图可以看出,第 27 列是由 AA 表示的,也就是说,Z 的下一位,前面增加一个 A,而最后一位回到 A。为什么这么说呢?因为当位数不够时,只有增加位数才能表示更大的值,读者不妨先试想一下 AZ 的下一位为什么是 BA,而 ZZ 的下一位为什么是 AAA。在了解这个字母序列之后,读者应该明白了,所谓的 Excel 列号转数字不过就是几个大写字母列号转换为数字列号,即得到它是第几列,比如 AB 28。反之亦然,数字转 Excel 列号也不言而喻了。

一开始,我以为可以用进制来进行转化。不过,很明显有许多地方是不符合进制的特点的,因此关于进制这里不作介绍,主要讲讲 Excel 列号的特点。为了便于叙述,后文将 Excel 列号简称为字母列号。

思路分析

笔者的分析思路是将不同长度的字母列号分开分析。也就是分为以下几种情况:

(1)当只有一位时,A、B、C...Z,很明显,只有 26 个,序号就是从 1  到 26。我们可以称它是一维的或是一条“线”

(2)当有两位时,AA、AB、AC...AZ,还有 BA、BB、BC...BZ,以及 CA...CZ,DA...DZ,一直到 ZA...ZZ。这时,从 AA 到 AZ 有 26 个,BA 到 BZ 有 26 个,而第一位的字母从 A 到 Z 有 26 个,也就是总共有 26*26 = 676 个,这可以称为二维或“面”。但是序号是从只有一位的情况开始的,也就是说这里需要加上之前的那一条“线”,即加上 26,那么从 AA 到 ZZ 的序号即是从 27(即1+26) 到 702(即 676+26)。

(3)当有三位时,AAA、AAB... ...ZAA... ... ZZA...ZZZ。即有 26*26*26 = 17576 个,即三维或“体”。同时序号需要加上前面的一维“线”和二维“面”,即从 7031+26+676)开始的。

... ...

(n)当有 n 位时,则有 26^n 个,即 n 维。同时序号需要加上前面的所有维度的和,即前 n-1 维的和。

字母列号转数字

那么,字母列号转数字就非常明确了,根据字母位数就可以知道它处于第几维。计算出它当前维度所在的具体位置,再加上前面的维度总和即是它的序号了。举一个三位字母(BCF)的例子:

根据上文可知,先看“体”,从下往上数,第 1 层表示第一位为 A,第 2 层表示第一位为 B;再看“面”,第 1 行表示第二位为 A,第 3 行则表示第二位为 C;最后看“线”,第 1 列表示第三位为 A,第 6 列表示第三位为 F,综合可知图中 BCF 的具体位置。

首先,因为它有三位字母,所以它是处于第三维度中,那么前两个维度肯定是完整的,直接加上 702(26^1+26^2),剩下的就是计算它在第三维度中所处的具体位置了,因为它的第三维并不是完整的

那么,图 6 清晰明了,在 BCF 之前,有 1B - 1)个完整的“面”(1*26^2),还有 2C - 1)个完整的“线”(2*26^1),最后剩下的就是 6F)了。(这里把 A 看成 1,那么 B - 1 = 2 - 1 = 1C - 1 = 3 - 1 = 2

那么由此可以看出,BCF 的序号就是 (B - 1) * 26^2 + (C - 1) * 26^1 + F 再加上 26^2 加上 26^1,即 B * 26^2 + C * 26^1 + F

这个式子还可以写成 B * 26^2 + C * 26^1 + F * 26^0。它所代表的意义就是该字母序号具有 B 个完整的“面”C 个完整的“线”,和剩下的 F 了(读者也可以理解为剩下的 F 个“点”)。

这个式子恰好是 26 进制10 进制的公式,只不过它并不是真正意义上的 26 进制罢了(因为 26 进制的数字范围是 0~25,这里的范围却是 1~26)。那么对应的 Java 代码也很简单:

数字转字母列号

同样,也是先确定该序号所处的维度,那么,数字如何确定它所处的维度呢?再使用上面那个例子,序数为 1436,先算出第一维度的大小(26),与 1436 作比较,发现 1436 是大于 26 的,说明 1436 不处于第一维度(废话嘛),再算出第二维度(26^2)加上第一维度(26)得到 702,再与 1436 做比较,那么 1436 仍然大于 702,所以 1436 也不处于第二维度,继续算出第三维度(26^3)加上前面的维度和(702)得到 18278,这时 1436 是小于等于 18278 的,所以 1436 是处于第三维度中的。这才得出 1436 是处于第三维度的结论。(至于为什么是小于等于,那是因为当等于的时候,恰好是第三维度中最后的那个,即 ZZZ)

现在知道了 1436 所处的维度,也就相当于知道了字母序号的位数(为3),所以它至少是以 AAA 起步的。根据前面的公式可以知道各位字母所表示的具体的含义,所以接下来就是要找出它具有多少个完整的“面”,多少个完整的“线”,然后剩下的就是“点”了。假设算出它具有 2 个完整的“面”,那么第一位字母就是 B,具有 3 个完整的“线”,第二位字母就是 C,剩下 6“点”就代表第三位字母是 F。关于如何计算有多少个完整的“面”,只需整除以 26^2 就可以了;然后把“面”的那部分数量减去,再整除以 26^1 就是完整的“线”的数量。关于什么是整除,这边建议您出门左拐复习下小学数学......简单说就是正常做除法,除完以后只取整数,小数部分直接扔掉,也不要四舍五入,直接无视小数就是整除了,比如 7 整除以 3 的结果是 2,为啥呢,因为小数部分扔了啊。这个例子也说明了 7 只有 2 个完整的 3,不可能有第三个 3 了。

那么对应的 Java 代码如下:

到这里就结束了吗?当然不是,上面这个代码是有问题的,BCF 具有 2 个完整的“面”3 个完整的“线”,和剩下的 6 个“点”,但若是恰好有 2 个完整的“面”4 个完整的“线”呢?没有多余的任何“点”。这时通过推理可以知道,在 BCF 后面把空位补齐使余出的“点”凑成一条完整的“线”,就有了 4完整的“线”,对应的字母应该是 BCZ。看下程序运行结果:

结果显然是不正确的。分析下其中的原理,乍一看 BCZ 具有 B完整的“面”C完整的“线”,和一条 Z“点”凑成的完整的“线”,也就是说其实它是把最后一条“线”分离出来作为 Z“点”了,那为什么不直接让 C1 变成 D 呢?那样就是 BD 了,只有两位,维度为 2,也就是上图中的结果,很明显是错误的,因为维度为 3,所以它必须分出一条“线”放在第三位上作为 Z“点”

所以需要增加一些判断,如果在做除法的时候,发现它被整除后没有余数,说明需要留出一条完整的“线”作为下个维度的“点”,这样想貌似没有问题,但应对 BZZ 时会出问题,BZZ 表示 B完整的“面”Z“线”Z“点”,问题出在 Z“线”也是一个“面”Z“点”也是一个“线”,即其实是有 C“面”A“线”,在对“面”做除法的时候余数A线,余数不为 0,结果也是不正确的。

整除取余失效了,那么怎么才能解出数字对应的字母呢?读者不妨换个角度想想,既然得出了维度为 3 的结论,那么就一定存在完整的二维完整的一维,先把这两部分减去,剩下的就是第三维度中的具体位置了,可以对照图 5 和图 6 来理解。这样就可以方便地求整除和余数了。

当然,以上均为个人思路,不见得空间时间上能有多大的效率。很久之前的题了,今天整理一下。

代码

文末贴上代码,方便复制。对于特别大的值,需要注意整型的溢出问题,不然会出现无限循环。


    /**
     * Excel 列号转数字
     *
     * @param excelNum Excel 列号
     * @return 数字
     */
    public static int excelNum2Digit(String excelNum) {
        char[] chs = excelNum.toCharArray();
        int digit = 0;

        /*
         *   B*26^2 + C*26^1 + F*26^0
         * = ((0*26 + B)*26 + C)*26 + F
         */
        for (char ch : chs) {
            digit = digit * 26 + (ch - 'A' + 1);
        }
        return digit;
    }

    /**
     * 数字转 Excel 列号
     *
     * @param digit 数字
     * @return Excel 列号
     */
    public static String digit2ExcelNum(int digit) {
        /*
         * 找到 digit 所处的维度 len, 它同时表示字母的位数
         * power 表示 26^n, 这里 n 分别等于 1, 2, 3
         * pre 表示 前 n 个维度的总和, 即 26^1 + 26^2 + 26^3
         */
        int len = 0, power = 1, pre = 0;
        for (; pre < digit; pre += power) {
            power *= 26;
            len++;
        }
        // 确定字母位数
        char[] excelNum = new char[len];
        /*
         * pre 包含 digit 所处的维度
         * pre - power 则是 digit 前面的维度总和
         * digit 先减去前面维度和
         */
        digit -= pre - power;
        /*
         * 比较难以理解的是这里为什么要自减 1
         * 其实是相对 (digit / power + 'A') 这句代码来的
         * 本应该是 (digit / power + 'A' - 1),
         * digit / power 的结果是完整的维度个数, 它加上 'A' - 1 后需要再加一
         * 当最后剩下的 6 个加上 'A' - 1 是应当的, 不需要做修改
         * 而当 (digit / power + 'A') 中没有减 1 后,
         * digit / power 的结果不需要再加一了
         * 相对于 digit / power 的结果, 最后剩下的 6 需要减 1
         */
        digit--;
        for (int i = 0; i < len; i++) {
            power /= 26;
            excelNum[i] = (char) (digit / power + 'A');
            digit %= power;
        }
        return String.valueOf(excelNum);
    }

  • 30
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
Matlab是一种强大的数学计算和工程模拟软件,可以用于各种自动化任务。在实现Excel与DBC(Database Container)文件的互转方面,Matlab提供了一些便利的工具和函数来简化处理过程。 首先,如果我们想要将Excel文件换为DBC文件,我们可以使用Matlab的Table数据结构和DBC工具箱。我们可以通过读取Excel文件并将其换为Matlab的Table对象来获取数据。然后,使用DBC工具箱中的函数将Table对象换为DBC格式,并将其保存为DBC文件。 另一方面,如果我们想要将DBC文件换为Excel文件,我们同样可以使用Matlab的DBC工具箱。我们可以使用DBC工具箱中的函数将DBC文件读取为DBC对象,然后使用Matlab的表格操作函数将DBC对象换为Matlab的Table对象。最后,我们可以使用Matlab的表格写入函数将Table对象保存为Excel文件。 无论是从Excel到DBC还是从DBC到Excel换,关键是将数据正确地解析和映射到对应的数据结构中。在换过程中,我们可能需要处理一些数据类型换、映射规则和缺失数据的情况。此外,我们还可以使用Matlab的一些图形化界面工具来增加用户友好性,使用户能够更轻松地进行换操作。 总之,Matlab提供了丰富的工具和函数来实现Excel与DBC文件的互转。使用这些工具和函数,我们可以轻松地将Excel中的数据换为DBC格式,或者将DBC文件换为Excel文件,实现数据的互相迁移和处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值