如何将列号(例如127)转换为Excel列(例如AA)

558

如何在C#中将数字转换为Excel列名,而不使用自动化从Excel直接获取值。

Excel 2007的可能范围是1到16384,这是它支持的列数。转换后的值应该是Excel列名的形式,例如A、AA、AAA等。


2
不要忘记可用列数的限制。例如:* Excel 2003(v11)最多到IV,2^8或256列。* Excel 2007(v12)最多到XFD,2^14或16384列。 - Unsliced
1
可能是重复的问题:如何找到对应于给定整数的Excel列名? - Daniel Trebbien
这个问题被标记为C#和Excel。我将此问题标记为过时,因为我们生活在2016年,有EPPLUS。这是一个常用的C#库,可以在服务器上创建高级Excel电子表格。它是根据GNU图书馆通用公共许可证(LGPL)提供的。使用EPPlus,您可以轻松地获取列字符串。 - Tony_KiloPapaMikeGolf
请注意,行和列的限制更多取决于文件格式而不是 Excel 版本,并且对于每个工作簿可能会有所不同。如果将其保存为旧版或新版格式,即使是同一工作簿,它们也可能会发生变化。 - Slai
@Tony_KiloPapaMikeGolf 我认为这并不过时。事实上,EPPLUS已经更改了许可证,这可能不适合各种原因。另外,如果你所需的只是如此简单的东西,为什么要引入一个库呢?我正在使用OpenXML以Excel格式导出数据,只需要一些像这里提问的算法。为什么要混合使用库呢?需求因人而异。该问题并不过时,对于简单的用例仍然很相关。话虽如此,EPPLUS是一个非常酷的库。 ;) - MetalMikester
如果您需要用于Aspose Cells,它们有内置的帮助程序。请参考:https://docs.aspose.com/cells/net/names-and-indices/。 - Uwe Keim
60个回答

1054

以下是我的做法:

private string GetExcelColumnName(int columnNumber)
{
    string columnName = "";

    while (columnNumber > 0)
    {
        int modulo = (columnNumber - 1) % 26;
        columnName = Convert.ToChar('A' + modulo) + columnName;
        columnNumber = (columnNumber - modulo) / 26;
    } 

    return columnName;
}

76
这段代码运行良好。它假设列 A 是列号 1。我不得不进行快速更改,以考虑我的系统将列 A 用作列号 0。我将行 int dividend 改为 int dividend = columnNumber + 1;。 Keith - Keith Sirmons
16
@Jduv 刚刚使用了 StringBuilder 进行尝试,但需要的时间大约是之前的两倍。这是一个非常短的字符串(最多3个字符 - Excel 2010可以到列XFD),因此最多只能进行2次字符串连接。(我使用了将整数1到16384(即Excel列A到XFD)翻译100次作为测试)。 - Graham
21
为了更好地理解,我会用“A”代替65。 - Stef Heyenrath
11
我认为使用“A”代替数字65会更好。而26可以被计算为('Z' - 'A' + 1),例如: 常量int AlphabetLength = 'Z' - 'A' + 1; - Denis Gladkiy
10
虽然我加入的比较晚,但是这段代码远非最佳解决方案。特别地,你不必使用 modulo,调用 ToString(),以及执行 (int) 强制转换。考虑到在大多数情况下,C# 中你会从 0 开始计数,以下是我的修改:<!-- language: c# --> public static string GetColumnName(int index) // 以 0 为起点 { const byte BASE = 'Z' - 'A' + 1; var nameBuilder = new StringBuilder();while (index > 0) { index--; nameBuilder.Insert(0, (char)('A' + index % BASE)); index /= BASE; } return nameBuilder.ToString();} - Herman Kan
显示剩余12条评论

67

如果有人需要在Excel中不使用VBA来完成此操作,这里有一个方法:

=SUBSTITUTE(ADDRESS(1;colNum;4);"1";"")

其中colNum是列号。

在VBA中:

Function GetColumnName(colNum As Integer) As String
    Dim d As Integer
    Dim m As Integer
    Dim name As String
    d = colNum
    name = ""
    Do While (d > 0)
        m = (d - 1) Mod 26
        name = Chr(65 + m) + name
        d = Int((d - m) / 26)
    Loop
    GetColumnName = name
End Function

2
=SUBSTITUTE(TEXT(ADDRESS(1,1000,4),""),"1","") - Dolph
是的,我在一个使用分号代替逗号作为Excel函数参数分隔符的区域中使用Excel。感谢您指出这一点。 - vzczc
2
我没有使用TEXT函数完成这个任务。那么,TEXT函数的作用是什么? - dayuloli
2
@dayuloli,自从撰写此答案以来已经很长时间了,你是正确的,TEXT函数在这里没有任何作用。会更新答案。 - vzczc

28
抱歉,这是Python而不是C#,但至少结果是正确的:
def ColIdxToXlName(idx):
    if idx < 1:
        raise ValueError("Index is too small")
    result = ""
    while True:
        if idx > 26:
            idx, r = divmod(idx - 1, 26)
            result = chr(r + ord('A')) + result
        else:
            return chr(idx + ord('A') - 1) + result


for i in xrange(1, 1024):
    print "%4d : %s" % (i, ColIdxToXlName(i))

是的,我投了反对票,请不要在问题是C#时发布Python。而且,是的,更多的人应该这样做。 - KyloRen
5
问题的标题并没有暗示使用C#,因此可能会有许多不希望看到C#内容的人前来。因此,感谢您分享Python的信息! - Tim-Erwin

27

你可能需要双向转换,例如从Excel列地址(如AAZ)到整数,以及从任何整数到Excel。下面的两种方法将实现此操作。假定基于1的索引,您的“数组”中的第一个元素是元素编号1。 在这里没有大小限制,因此您可以使用地址如ERROR,那将是第2613824列...

public static string ColumnAdress(int col)
{
  if (col <= 26) { 
    return Convert.ToChar(col + 64).ToString();
  }
  int div = col / 26;
  int mod = col % 26;
  if (mod == 0) {mod = 26;div--;}
  return ColumnAdress(div) + ColumnAdress(mod);
}

public static int ColumnNumber(string colAdress)
{
  int[] digits = new int[colAdress.Length];
  for (int i = 0; i < colAdress.Length; ++i)
  {
    digits[i] = Convert.ToInt32(colAdress[i]) - 64;
  }
  int mul=1;int res=0;
  for (int pos = digits.Length - 1; pos >= 0; --pos)
  {
    res += digits[pos] * mul;
    mul *= 26;
  }
  return res;
}

14
我在第一篇文章中发现了一个错误,所以我决定坐下来计算。我发现用于标识Excel列的数字系统不是另一个人发布的基数26系统。请考虑以下十进制。你也可以用字母表中的字母做同样的事情。
空格:S1、S2、S3:S1、S2、S3 0,00,000:A、AA、AAA 1,01,001:B、AB、AAB ……,……,……:……,……,…… 9,99,999:Z、ZZ、ZZZ 状态总数:10、100、1000:26、676、17576 总状态数:1110…………18278
Excel使用基数为26的方法将列编号为各个字母空间内的数字。通常情况下,你可以看到状态空间的进展是a、a²、a³、...对于某些基础a,而总状态数是a+a²+a³+…。
假设你想找出前N个空间中状态A的总数。计算公式为A=(a)(a^N-1)/(a-1)。这很重要,因为我们需要找到与索引K对应的空间N。如果我想找出K在数字系统中的位置,我需要将A替换为K并解决N。解决方法是N=log{base a}(A(a-1)/a+1)。如果我使用a=10和K=192的例子,我知道N=2.23804……。这告诉我K位于第三个空间的开头,因为它比二大一点。
下一步是确定当前空间中有多远。要找到这个值,请从K中减去使用N的底部生成的A。在这个例子中,N的底部是两个。所以,A=(10)(10²-1)/(10-1)=110,就像在前两个空间中合并状态时所预期的那样。因为这些前面的110个状态已经在前两个空间中考虑过了,所以需要从K中减去这个数值。这让我们剩下82种状态。因此,在这个数字系统中,192的十进制表示法是082。
C#代码使用基础索引为零。
    private string ExcelColumnIndexToName(int Index)
    {
        string range = string.Empty;
        if (Index < 0 ) return range;
        int a = 26;
        int x = (int)Math.Floor(Math.Log((Index) * (a - 1) / a + 1, a));
        Index -= (int)(Math.Pow(a, x) - 1) * a / (a - 1);
        for (int i = x+1; Index + i > 0; i--)
        {
            range = ((char)(65 + Index % a)).ToString() + range;
            Index /= a;
        }
        return range;
    }

//旧帖

这是一个使用C#的从零开始的解决方案。

    private string ExcelColumnIndexToName(int Index)
    {
        string range = "";
        if (Index < 0 ) return range;
        for(int i=1;Index + i > 0;i=0)
        {
            range = ((char)(65 + Index % 26)).ToString() + range;
            Index /= 26;
        }
        if (range.Length > 1) range = ((char)((int)range[0] - 1)).ToString() + range.Substring(1);
        return range;
    }

1
哦,我不知道为什么,但我喜欢这个解决方案。没有花哨的东西,只是很好地运用了逻辑……对于各种程序员来说,代码易读性很高。不过,我认为在C#中指定一个空字符串是最佳实践:string range = string.Empty; - Anonymous Type
是的,非常好的解释。但您能否简单说明它也不是27进制?当研究时,您的解释表明了这一点,但在顶部快速提及可能会为其他人节省一些时间。 - Marius

13

这个答案是用 JavaScript 编写的:

function getCharFromNumber(columnNumber){
    var dividend = columnNumber;
    var columnName = "";
    var modulo;

    while (dividend > 0)
    {
        modulo = (dividend - 1) % 26;
        columnName = String.fromCharCode(65 + modulo).toString() + columnName;
        dividend = parseInt((dividend - modulo) / 26);
    } 
    return  columnName;
}

10

我很惊讶到目前为止所有的解决方案都包含迭代或递归。

这是我的解决方案,它可以在常数时间内运行(没有循环)。此解决方案适用于所有可能的Excel列,并检查输入是否可以转换为Excel列。 可能的列位于范围[A,XFD]或[1,16384]内。(这取决于您的Excel版本)

private static string Turn(uint col)
{
    if (col < 1 || col > 16384) //Excel columns are one-based (one = 'A')
        throw new ArgumentException("col must be >= 1 and <= 16384");

    if (col <= 26) //one character
        return ((char)(col + 'A' - 1)).ToString();

    else if (col <= 702) //two characters
    {
        char firstChar = (char)((int)((col - 1) / 26) + 'A' - 1);
        char secondChar = (char)(col % 26 + 'A' - 1);

        if (secondChar == '@') //Excel is one-based, but modulo operations are zero-based
            secondChar = 'Z'; //convert one-based to zero-based

        return string.Format("{0}{1}", firstChar, secondChar);
    }

    else //three characters
    {
        char firstChar = (char)((int)((col - 1) / 702) + 'A' - 1);
        char secondChar = (char)((col - 1) / 26 % 26 + 'A' - 1);
        char thirdChar = (char)(col % 26 + 'A' - 1);

        if (thirdChar == '@') //Excel is one-based, but modulo operations are zero-based
            thirdChar = 'Z'; //convert one-based to zero-based

        return string.Format("{0}{1}{2}", firstChar, secondChar, thirdChar);
    }
}

2
FYI:@Graham的答案(以及可能的其他答案)比你的更通用:它们支持列名中的4个或更多字符。这正是它们迭代的原因。 - bernard paulus
2
如果我的数据超过了16384列的电子表格,我就会自杀。不管怎样,Excel甚至不支持所有可能的三个字母的列(它在XFD处截断,留下了1894列)。现在就是这样。如有需要,我将在未来更新我的答案。 - user2023861
:) 没想到!我的评论是关于不同算法的理论属性的。 - bernard paulus
太过复杂了。循环是保持代码简短易读的合法方式。 - Mulli
+1 表示显示最大单元格引用,你不会相信在网上找到这个信息有多难。 - controlbox
显示剩余4条评论

10
int nCol = 127;
string sChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string sCol = "";
while (nCol >= 26)
{
    int nChar = nCol % 26;
    nCol = (nCol - nChar) / 26;
    // You could do some trick with using nChar as offset from 'A', but I am lazy to do it right now.
    sCol = sChars[nChar] + sCol;
}
sCol = sChars[nCol] + sCol;

更新: Peter的评论是正确的。这就是在浏览器中编写代码的结果。:-) 我的解决方案没有编译,缺少最左边的字母,并且它以相反的顺序构建字符串-现在全部修复。

除了错误之外,算法基本上是将一个数字从10进制转换为26进制。

更新2: Joel Coehoorn 是对的-上面的代码将返回27的AB。如果它是真正的26进制数,则AA将等于A,Z后面的下一个数字将是BA。

int nCol = 127;
string sChars = "0ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string sCol = "";
while (nCol > 26)
{
    int nChar = nCol % 26;
    if (nChar == 0)
        nChar = 26;
    nCol = (nCol - nChar) / 26;
    sCol = sChars[nChar] + sCol;
}
if (nCol != 0)
    sCol = sChars[nCol] + sCol;

代码无法编译(sCol未初始化)。即使编译成功,也不会得到正确的答案。 - Peter
6
这个答案是错误的。Base26不够好。考虑一下当你从Z转到AA时会发生什么。如果A等同于0,那么这就像从9到00的包裹一样。如果它是1位数字,那么就像从9到11的包裹一样。 - Joel Coehoorn
5
更新后我不太清楚……这两个算法现在是否正确?如果是,哪一个正确,第二个算法吗?我会编辑这个并让它明显易懂,以便后人参考。 - JoeCool

10

使用递归轻松实现。

public static string GetStandardExcelColumnName(int columnNumberOneBased)
{
  int baseValue = Convert.ToInt32('A');
  int columnNumberZeroBased = columnNumberOneBased - 1;

  string ret = "";

  if (columnNumberOneBased > 26)
  {
    ret = GetStandardExcelColumnName(columnNumberZeroBased / 26) ;
  }

  return ret + Convert.ToChar(baseValue + (columnNumberZeroBased % 26) );
}

2
这里没有必要使用递归,使用循环即可。 - Blorgbeard
2
不仅是基数26,因此递归解决方案要简单得多。 - Joel Coehoorn
这个例程实际上不起作用。 例如,GetStandardExcelColumnName(26)会返回@, GetStandardExcelColumnName(52)会返回B@。 - sgmoore
2
最清晰的解决方案才是最好的,而不是“最简单”的解决方案。 - Anonymous Type

10

Java中的同样实现

public String getExcelColumnName (int columnNumber) 
    {     
        int dividend = columnNumber;   
        int i;
        String columnName = "";     
        int modulo;     
        while (dividend > 0)     
        {        
            modulo = (dividend - 1) % 26;         
            i = 65 + modulo;
            columnName = new Character((char)i).toString() + columnName;        
            dividend = (int)((dividend - modulo) / 26);    
        }       
        return columnName; 
    }  

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接