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

How to convert a column number (e.g. 127) into an Excel column (e.g. AA)

提问人:robertkroll 提问时间:10/8/2008 最后编辑:Uwe Keimrobertkroll 更新时间:3/6/2023 访问量:294083

问:

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

Excel 2007 的可能范围为 1 到 16384,这是它支持的列数。结果值应采用 excel 列名的形式,例如 A、AA、AAA 等。

C# Excel

评论

2赞 Unsliced 10/8/2008
不要忘记可用的列数是有限制的。例如,* Excel 2003 (v11) 上升到 IV、2^8 或 256 列)。* Excel 2007 (v12) 增加到 XFD、2^14 或 16384 列。
1赞 Daniel Trebbien 6/19/2013
如何找到与给定整数对应的 Excel 列名的可能副本?
0赞 Tony_KiloPapaMikeGolf 12/14/2016
这个问题被标记为 C# 和 excel。我认为这个问题已经过时了,因为我们生活在 2016 年,并且有 EPPLUS。一个常用的 C# 库,用于在服务器上创建高级 Excel 电子表格。可在以下下获得:GNU 库通用公共许可证 (LGPL)。使用 EPPlus,您可以轻松获取 Column 字符串。
0赞 Slai 12/15/2016
请注意,行和列限制更多地取决于文件格式,而不是 Excel 版本,并且每个工作簿可能不同。即使对于同一工作簿,如果将其保存为较旧或较新的格式,它们也可以更改。
0赞 MetalMikester 6/22/2020
@Tony_KiloPapaMikeGolf我不认为这已经过时了。事实上,EPPLUS已经改变了他们的许可方式,由于各种原因,这可能并不适合所有人。另外,如果您只需要这么简单的东西,为什么还要带一个图书馆呢?我正在使用 OpenXML 以 Excel 格式导出数据,我只需要像这里问的那样的几种算法。为什么要在组合中添加库?需求各不相同。这个问题并没有过时,并且与简单的用例有关。也就是说,EPPLUS是一个非常酷的库。;)

答:

10赞 Franci Penov 10/8/2008 #1
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;

更新彼得的评论是对的。这就是我在浏览器中编写代码所得到的。:-)我的解决方案不是编译,它缺少最左边的字母,它正在以相反的顺序构建字符串 - 现在都已修复。

撇开错误不谈,该算法基本上是将数字从 10 基数转换为 26 基数。

更新 2Joel 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;

评论

0赞 Peter 10/8/2008
代码不会编译(sCol 未初始化)。如果是这样,它不会给出正确的答案。
6赞 Joel Coehoorn 11/22/2008
这个答案是错误的。Base26 还不够好。想想当你从 Z 换行到 AA 时会发生什么。如果 A 等于 0 位数字,那么它就像从 9 换行到 00。如果是 1 位数字,就像从 9 换行到 11。
5赞 JoeCool 7/28/2009
更新后我不清楚......现在这两种算法都正确吗?如果是这样,是哪一个,第二个?我会编辑它,让它对后代显而易见......
10赞 Peter 10/8/2008 #2

易于递归。

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 10/8/2008
..或循环。这里没有真正的理由使用递归。
2赞 Joel Coehoorn 11/22/2008
它不仅仅是以 26 为基数,因此递归解决方案要简单得多。
0赞 sgmoore 3/11/2010
这个例程实际上不起作用。例如,GetStandardExcelColumnName(26) 返回 @ GetStandardExcelColumnName(52) 返回 B@
2赞 Anonymous Type 7/23/2010
与“最简单”的解决方案相比,最清晰的解决方案是最好的。
28赞 RoMa 10/8/2008 #3

对不起,这是 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))

评论

0赞 KyloRen 7/21/2019
是的,我投了反对票,当问题是 C# 时不要发布 Python。是的,更多的人应该这样做。
5赞 Tim 9/16/2019
问题的标题并不意味着 C#,所以很多人可能来到这里,但并不擅长 C#。因此,感谢您在 Python 中分享!
1054赞 Graham 10/8/2008 #4

我是这样做的:

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赞 Keith Sirmons 8/7/2009
此代码运行良好。它假定列 A 是列编号 1。我必须快速更改以使用列 A 作为列编号 0 来考虑我的系统。我将行 int dividend 更改为 int dividend = columnNumber + 1;基思
16赞 Graham 5/5/2011
@Jduv刚刚尝试了一下。它大约需要两倍的时间。这是一个非常短的字符串(最多 3 个字符 - Excel 2010 上升到 XFD 列),因此最多 2 个字符串连接。(我使用了 100 次迭代将整数 1 转换为 16384,即 Excel 列 A 到 XFD,作为测试)。StringBuilder
21赞 Stef Heyenrath 11/11/2011
为了更好地理解,我会用“A”替换 65
11赞 Denis Gladkiy 11/3/2013
我认为最好使用“A”而不是 65。26 可以计算为 ('Z' - 'A' + 1),例如:const int AlphabetLength = 'Z' - 'A' + 1;
10赞 Herman Kan 8/18/2016
虽然我迟到了,但代码远非最佳。特别是,您不必使用 、 调用和应用强制转换。考虑到在 C# 世界中的大多数情况下,您会从 0 开始编号,这是我的修订版: <!--语言: c# --> public static string GetColumnName(int index) // 从零开始 { const byte BASE = 'Z' - 'A' + 1; string name = String.Empty; do { name = Convert.ToChar('A' + index % BASE) + name; index = index / BASE - 1; } while (index >= 0); return name; }moduloToString()(int)
4赞 rubberduckie 11/4/2008 #5
private String getColumn(int c) {
    String s = "";
    do {
        s = (char)('A' + (c % 26)) + s;
        c /= 26;
    } while (c-- > 0);
    return s;
}

它不完全以 26 为基数,系统中没有 0。如果有,“Z”后面跟着“BA”,而不是“AA”。

67赞 vzczc 1/30/2009 #6

如果有人需要在没有VBA的情况下在Excel中执行此操作,请执行以下操作:

=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赞 Dolph 10/14/2010
示例:=SUBSTITUTE(TEXT(ADDRESS(1,1000,4),“”),“1”,“”)
0赞 vzczc 10/14/2010
是的,我在 ;用于代替 ,以分隔 Excel 中的函数参数。感谢您指出这一点。
2赞 dayuloli 1/29/2014
我在没有 TEXT 函数的情况下这样做了。TEXT 函数的用途是什么?
2赞 vzczc 1/29/2014
@dayuloli 自从写这个答案以来很久了,你是对的,TEXT 函数在这里没有用。将更新答案。
2赞 ShloEmi 7/27/2009 #7

优化原始解决方案(在 C# 中):

public static class ExcelHelper
{
    private static Dictionary<UInt16, String> l_DictionaryOfColumns;

    public static ExcelHelper() {
        l_DictionaryOfColumns = new Dictionary<ushort, string>(256);
    }

    public static String GetExcelColumnName(UInt16 l_Column)
    {
        UInt16 l_ColumnCopy = l_Column;
        String l_Chars = "0ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        String l_rVal = "";
        UInt16 l_Char;


        if (l_DictionaryOfColumns.ContainsKey(l_Column) == true)
        {
            l_rVal = l_DictionaryOfColumns[l_Column];
        }
        else
        {
            while (l_ColumnCopy > 26)
            {
                l_Char = l_ColumnCopy % 26;
                if (l_Char == 0)
                    l_Char = 26;

                l_ColumnCopy = (l_ColumnCopy - l_Char) / 26;
                l_rVal = l_Chars[l_Char] + l_rVal;
            }
            if (l_ColumnCopy != 0)
                l_rVal = l_Chars[l_ColumnCopy] + l_rVal;

            l_DictionaryOfColumns.ContainsKey(l_Column) = l_rVal;
        }

        return l_rVal;
    }
}
9赞 Stephen Fuhry 12/18/2009 #8

..并转换为php:

function GetExcelColumnName($columnNumber) {
    $columnName = '';
    while ($columnNumber > 0) {
        $modulo = ($columnNumber - 1) % 26;
        $columnName = chr(65 + $modulo) . $columnName;
        $columnNumber = (int)(($columnNumber - $modulo) / 26);
    }
    return $columnName;
}

评论

0赞 Mulli 7/7/2019
使用代替 65。ord('A')
2赞 Rob 3/29/2010 #9

下面是一个 Actionscript 版本:

private var columnNumbers:Array = ['A', 'B', 'C', 'D', 'E', 'F' , 'G', 'H', 'I', 'J', 'K' ,'L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];

    private function getExcelColumnName(columnNumber:int) : String{
        var dividend:int = columnNumber;
        var columnName:String = "";
        var modulo:int;

        while (dividend > 0)
        {
            modulo = (dividend - 1) % 26;
            columnName = columnNumbers[modulo] + columnName;
            dividend = int((dividend - modulo) / 26);
        } 

        return columnName;
    }
27赞 Arent Arntzen 4/16/2010 #10

您可能需要两种方式进行转换,例如从 AAZ 等 Excel 列地址到整数,以及从任何整数到 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赞 John 8/10/2010 #11

我在第一篇文章中发现了一个错误,所以我决定坐下来算一算。我发现用于识别 Excel 列的数字系统不是另一个人发布的以 26 为基数的系统。在基数 10 中考虑以下内容。您也可以使用字母表中的字母来执行此操作。

空间:。。。。。。。。。。。。。。。。。。。。。。。。。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 使用 base 26 对各个字母空间中的列进行编号。您可以看到,一般来说,状态空间级数是 a, a^2, a^3, ...对于某些基数 a,状态总数为 a + a^2 + a^3 + ... 。

假设您想在前 N 个空格中查找状态 A 的总数。这样做的公式是 A = (a)(a^N - 1 )/(a-1)。这很重要,因为我们需要找到与索引 K 相对应的空间 N。如果我想找出 K 在数字系统中的位置,我需要用 K 替换 A 并求解 N。解为 N = log{base a} (A (a-1)/a +1)。如果我使用 a = 10 和 K = 192 的例子,我知道 N = 2.23804...。这告诉我 K 位于第三个空间的开头,因为它比 2 大一点。

下一步是准确找到我们在当前空间中走了多远。要找到这一点,请从 K 中减去使用 N 的下限生成的 A。在此示例中,N 的下限为 2。因此,A = (10)(10^2 – 1)/(10-1) = 110,正如您合并前两个空间的状态时所预期的那样。这需要从 K 中减去,因为前 110 个状态已经在前两个空间中被考虑在内。这给我们留下了 82 个州。因此,在这个数字系统中,以 192 为基数的 10 表示是 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赞 Anonymous Type 8/10/2010
哦,我不知道为什么,但我喜欢这个解决方案。没什么花哨的,只是善于运用逻辑......易于阅读的代码,适用于程序员级别。不过有一件事,我相信在 C# 中将空字符串指定为 string range = string 的最佳做法。空;
0赞 Marius 9/3/2013
是的,非常好的解释。但是你也可以说它也不是以 27 为基数吗?你的解释在研究时表明了这一点,但在顶部快速提及可能会为其他一些人节省一些时间。
4赞 Matt Lewis 8/24/2010 #12

如果您只想将其用于没有代码的单元格公式,这里有一个公式:

IF(COLUMN()>=26,CHAR(ROUND(COLUMN()/26,1)+64)&CHAR(MOD(COLUMN(),26)+64),CHAR(COLUMN()+64))
10赞 Balaji.N.S 12/10/2010 #13

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; 
    }  
4赞 JRL 8/25/2011 #14

在德尔福(帕斯卡)中:

function GetExcelColumnName(columnNumber: integer): string;
var
  dividend, modulo: integer;
begin
  Result := '';
  dividend := columnNumber;
  while dividend > 0 do begin
    modulo := (dividend - 1) mod 26;
    Result := Chr(65 + modulo) + Result;
    dividend := (dividend - modulo) div 26;
  end;
end;
3赞 user932708 10/13/2011 #15

在查看了这里提供的所有版本后,我决定自己做一个,使用递归。

这是我 vb.net 版本:

Function CL(ByVal x As Integer) As String
    If x >= 1 And x <= 26 Then
        CL = Chr(x + 64)
    Else
        CL = CL((x - x Mod 26) / 26) & Chr((x Mod 26) + 1 + 64)
    End If
End Function

评论

1赞 KekuSemau 6/29/2021
这是错误的。27 => AB 代替 AA,105 => DB 代替 DA 等。
4赞 Kelly L 1/5/2012 #16

游戏有点晚了,但这是我使用的代码(在 C# 中):

private static readonly string _Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static int ColumnNameParse(string value)
{
    // assumes value.Length is [1,3]
    // assumes value is uppercase
    var digits = value.PadLeft(3).Select(x => _Alphabet.IndexOf(x));
    return digits.Aggregate(0, (current, index) => (current * 26) + (index + 1));
}

评论

2赞 nurettin 2/28/2012
你做了与要求相反的事情,但你的 lambda-fu 为 +1。
1赞 Vlad 5/25/2012
IndexOf很慢,你最好预先计算反向映射。
4赞 Myforwik 4/18/2012 #17

在 perl 中,对于 1 (A)、27 (AA) 等的输入。

sub excel_colname {
  my ($idx) = @_;       # one-based column number
  --$idx;               # zero-based column index
  my $name = "";
  while ($idx >= 0) {
    $name .= chr(ord("A") + ($idx % 26));
    $idx   = int($idx / 26) - 1;
  }
  return scalar reverse $name;
}
10赞 user2023861 2/9/2013 #18

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

这是我的解决方案,它以恒定时间运行(无循环)。此解决方案适用于所有可能的 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赞 bernard paulus 7/10/2013
仅供参考:@Graham的答案(可能还有其他答案)比您的答案更笼统:它们在列名中支持 4+ 个字符。这正是它们迭代的原因。
0赞 bernard paulus 7/10/2013
事实上,如果他们使用无限的整数而不是 s,则生成的列名可能会任意长(例如,python 答案就是这种情况)int
2赞 user2023861 7/10/2013
如果我的数据对于 16,384 列的电子表格来说太大了,我会开枪打自己的脑袋。无论如何,Excel 甚至不支持所有可能的三个字母列(它在 XFD 处截断,遗漏了 1,894 列)。反正现在。我将根据需要更新我的答案。
0赞 bernard paulus 7/10/2013
:)不知道!我的评论是关于不同算法的理论特性。
0赞 Juan 7/11/2018
这可能是最简单、最清晰的解决方案。
13赞 MaVRoSCy 3/28/2013 #19

这个答案在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;
}
1赞 Ian Atkin 5/2/2013 #20

这是我在PHP中的超级后期实现。这个是递归的。我在找到这篇文章之前就写了它。我想看看其他人是否已经解决了这个问题......

public function GetColumn($intNumber, $strCol = null) {

    if ($intNumber > 0) {
        $intRem = ($intNumber - 1) % 26;
        $strCol = $this->GetColumn(intval(($intNumber - $intRem) / 26), sprintf('%s%s', chr(65 + $intRem), $strCol));
    }

    return $strCol;
}
2赞 Ally 10/3/2013 #21

JavaScript 解决方案

/**
 * Calculate the column letter abbreviation from a 1 based index
 * @param {Number} value
 * @returns {string}
 */
getColumnFromIndex = function (value) {
    var base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
    var remainder, result = "";
    do {
        remainder = value % 26;
        result = base[(remainder || 26) - 1] + result;
        value = Math.floor(value / 26);
    } while (value > 0);
    return result;
};

评论

1赞 Eric Rini 7/11/2015
尝试索引 26 和 27。它非常接近,但相差一个。
0赞 Henry Liu 4/12/2019
值 = Math.floor(值 / 26);应为 value = Math.ceil(value / 26) - 1;
2赞 Paul Ma 2/21/2014 #22

另一种VBA方式

Public Function GetColumnName(TargetCell As Range) As String
    GetColumnName = Split(CStr(TargetCell.Cells(1, 1).Address), "$")(1)
End Function
6赞 t3dodson 9/26/2014 #23

我想加入我使用的静态类,用于在 col index 和 col Label 之间进行互操作。我为我的 ColumnLabel 方法使用修改后的接受答案

public static class Extensions
{
    public static string ColumnLabel(this int col)
    {
        var dividend = col;
        var columnLabel = string.Empty;
        int modulo;

        while (dividend > 0)
        {
            modulo = (dividend - 1) % 26;
            columnLabel = Convert.ToChar(65 + modulo).ToString() + columnLabel;
            dividend = (int)((dividend - modulo) / 26);
        } 

        return columnLabel;
    }
    public static int ColumnIndex(this string colLabel)
    {
        // "AD" (1 * 26^1) + (4 * 26^0) ...
        var colIndex = 0;
        for(int ind = 0, pow = colLabel.Count()-1; ind < colLabel.Count(); ++ind, --pow)
        {
            var cVal = Convert.ToInt32(colLabel[ind]) - 64; //col A is index 1
            colIndex += cVal * ((int)Math.Pow(26, pow));
        }
        return colIndex;
    }
}

像这样使用这个...

30.ColumnLabel(); // "AD"
"AD".ColumnIndex(); // 30
2赞 S_R 4/10/2015 #24

这些我的代码将特定数字(索引从 1 开始)转换为 Excel 列。

    public static string NumberToExcelColumn(uint number)
    {
        uint originalNumber = number;

        uint numChars = 1;
        while (Math.Pow(26, numChars) < number)
        {
            numChars++;

            if (Math.Pow(26, numChars) + 26 >= number)
            {
                break;
            }               
        }

        string toRet = "";
        uint lastValue = 0;

        do
        {
            number -= lastValue;

            double powerVal = Math.Pow(26, numChars - 1);
            byte thisCharIdx = (byte)Math.Truncate((columnNumber - 1) / powerVal);
            lastValue = (int)powerVal * thisCharIdx;

            if (numChars - 2 >= 0)
            {
                double powerVal_next = Math.Pow(26, numChars - 2);
                byte thisCharIdx_next = (byte)Math.Truncate((columnNumber - lastValue - 1) / powerVal_next);
                int lastValue_next = (int)Math.Pow(26, numChars - 2) * thisCharIdx_next;

                if (thisCharIdx_next == 0 && lastValue_next == 0 && powerVal_next == 26)
                {
                    thisCharIdx--;
                    lastValue = (int)powerVal * thisCharIdx;
                }
            }

            toRet += (char)((byte)'A' + thisCharIdx + ((numChars > 1) ? -1 : 0));

            numChars--;
        } while (numChars > 0);

        return toRet;
    }

我的单元测试:

    [TestMethod]
    public void Test()
    {
        Assert.AreEqual("A", NumberToExcelColumn(1));
        Assert.AreEqual("Z", NumberToExcelColumn(26));
        Assert.AreEqual("AA", NumberToExcelColumn(27));
        Assert.AreEqual("AO", NumberToExcelColumn(41));
        Assert.AreEqual("AZ", NumberToExcelColumn(52));
        Assert.AreEqual("BA", NumberToExcelColumn(53));
        Assert.AreEqual("ZZ", NumberToExcelColumn(702));
        Assert.AreEqual("AAA", NumberToExcelColumn(703));
        Assert.AreEqual("ABC", NumberToExcelColumn(731));
        Assert.AreEqual("ACQ", NumberToExcelColumn(771));
        Assert.AreEqual("AYZ", NumberToExcelColumn(1352));
        Assert.AreEqual("AZA", NumberToExcelColumn(1353));
        Assert.AreEqual("AZB", NumberToExcelColumn(1354));
        Assert.AreEqual("BAA", NumberToExcelColumn(1379));
        Assert.AreEqual("CNU", NumberToExcelColumn(2413));
        Assert.AreEqual("GCM", NumberToExcelColumn(4823));
        Assert.AreEqual("MSR", NumberToExcelColumn(9300));
        Assert.AreEqual("OMB", NumberToExcelColumn(10480));
        Assert.AreEqual("ULV", NumberToExcelColumn(14530));
        Assert.AreEqual("XFD", NumberToExcelColumn(16384));
    }

评论

0赞 controlbox 12/15/2019
+1 用于显示测试中的最大单元格参考值 (XFD) - 您不会相信在网络上找到该信息是多么困难。
3赞 Herman Kan 8/18/2016 #25

虽然我迟到了,但格雷厄姆的答案远非最佳。特别是,您不必使用 、 调用和应用强制转换。考虑到在 C# 世界中,在大多数情况下,您将从 0 开始编号,这是我的修订版:moduloToString()(int)

public static string GetColumnName(int index) // zero-based
{
    const byte BASE = 'Z' - 'A' + 1;
    string name = String.Empty;

    do
    {
        name = Convert.ToChar('A' + index % BASE) + name;
        index = index / BASE - 1;
    }
    while (index >= 0);

    return name;
}

评论

0赞 David Heffernan 4/20/2022
这似乎与格雷厄姆的答案在循环主体中具有相同的两个数学运算
9赞 Extragorey 1/24/2017 #26

只需使用递归引入一个简单的两行 C# 实现,因为这里的所有答案似乎都比必要的要复杂得多。

/// <summary>
/// Gets the column letter(s) corresponding to the given column number.
/// </summary>
/// <param name="column">The one-based column index. Must be greater than zero.</param>
/// <returns>The desired column letter, or an empty string if the column number was invalid.</returns>
public static string GetColumnLetter(int column) {
    if (column < 1) return String.Empty;
    return GetColumnLetter((column - 1) / 26) + (char)('A' + (column - 1) % 26);
}
2赞 DataEngineer 11/6/2017 #27

对不起,这是 Python 而不是 C#,但至少结果是正确的:

def excel_column_number_to_name(column_number):
    output = ""
    index = column_number-1
    while index >= 0:
        character = chr((index%26)+ord('A'))
        output = output + character
        index = index/26 - 1

    return output[::-1]


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

通过了以下测试用例:

  • 列号:494286 => ABCDZ
  • 列号:27 => AA
  • 列号:52 => AZ
3赞 user1098928 10/13/2018 #28

值得一提的是,以下是 Graham 在 Powershell 中的代码:

function ConvertTo-ExcelColumnID {
    param (
        [parameter(Position = 0,
            HelpMessage = "A 1-based index to convert to an excel column ID. e.g. 2 => 'B', 29 => 'AC'",
            Mandatory = $true)]
        [int]$index
    );

    [string]$result = '';
    if ($index -le 0 ) {
        return $result;
    }

    while ($index -gt 0) {
        [int]$modulo = ($index - 1) % 26;
        $character = [char]($modulo + [int][char]'A');
        $result = $character + $result;
        [int]$index = ($index - $modulo) / 26;
    }

    return $result;
}
3赞 AlishahNovin 7/10/2019 #29

已经有 30 多种解决方案,但这是我的单行 C# 解决方案......

public string IntToExcelColumn(int i)
{
    return ((i<16926? "" : ((char)((((i/26)-1)%26)+65)).ToString()) + (i<2730? "" : ((char)((((i/26)-1)%26)+65)).ToString()) + (i<26? "" : ((char)((((i/26)-1)%26)+65)).ToString()) + ((char)((i%26)+65)));
}
9赞 desseim 5/8/2022 #30

虽然已经有一堆有效的答案1,但没有一个进入其背后的理论。

Excel 列名是其编号的双射 base-26 表示形式。这与普通的以 26 为基数(没有前导零)完全不同,我真的建议您阅读维基百科条目以掌握差异。例如,十进制值(分解为 )以 () 表示为“普通”基数 26,以 (即 ) 表示,以双射基数 26 表示为 (即 )。70226*26 + 261101x26^2 + 1x26^1 + 0x26^0ZZ26x26^1 + 26x26^0

撇开差异不谈,双射计数是一种位置表示法,因此我们可以使用迭代(或递归)算法执行转换,该算法在每次迭代中都会找到下一个位置的数字(类似于普通的基本转换算法)。

在十进制数的双射基数表示的最后一个位置(索引为 0)处获得数字的一般公式是(即上限函数减去 1):kmf

m - (f(m / k) * k)

通过对 的结果应用相同的公式来找到下一个位置的数字(即索引为 1 的数字)。我们知道,最后一个数字(即索引最高的数字)是 0。f(m / k)f(m / k)

这构成了迭代的基础,该迭代在十进制数的双射基数中查找每个连续的数字。在伪代码中,它看起来像这样( 将十进制整数映射到它在双射基数中的表示形式——例如 将以 bijective base-26 返回):kdigit()digit(1)A

fun conv(m)
    q = f(m / k)
    a = m - (q * k)
    if (q == 0)
        return digit(a)
    else
        return conv(q) + digit(a);

因此,我们可以将其转换为 C#2 以获得通用的3“转换为双射基 k”例程:ToBijective()

class BijectiveNumeration {
    private int baseK;
    private Func<int, char> getDigit;
    public BijectiveNumeration(int baseK, Func<int, char> getDigit) {
        this.baseK = baseK;
        this.getDigit = getDigit;
    }

    public string ToBijective(double decimalValue) {
        double q = f(decimalValue / baseK);
        double a = decimalValue - (q * baseK);
        return ((q > 0) ? ToBijective(q) : "") + getDigit((int)a);
    }

    private static double f(double i) {
        return (Math.Ceiling(i) - 1);
    }
}

现在转换为双射 base-26(我们的“Excel 列名”用例):

static void Main(string[] args)
{
    BijectiveNumeration bijBase26 = new BijectiveNumeration(
        26,
        (value) => Convert.ToChar('A' + (value - 1))
    );

    Console.WriteLine(bijBase26.ToBijective(1));     // prints "A"
    Console.WriteLine(bijBase26.ToBijective(26));    // prints "Z"
    Console.WriteLine(bijBase26.ToBijective(27));    // prints "AA"
    Console.WriteLine(bijBase26.ToBijective(702));   // prints "ZZ"
    Console.WriteLine(bijBase26.ToBijective(16384)); // prints "XFD"
}

Excel 的最大列索引是 / ,但此代码将转换任何正数。16384XFD

作为额外的奖励,我们现在可以很容易地转换为任何双射基。例如,对于双射基数 10

static void Main(string[] args)
{
    BijectiveNumeration bijBase10 = new BijectiveNumeration(
        10,
        (value) => value < 10 ? Convert.ToChar('0'+value) : 'A'
    );

    Console.WriteLine(bijBase10.ToBijective(1));     // prints "1"
    Console.WriteLine(bijBase10.ToBijective(10));    // prints "A"
    Console.WriteLine(bijBase10.ToBijective(123));   // prints "123"
    Console.WriteLine(bijBase10.ToBijective(20));    // prints "1A"
    Console.WriteLine(bijBase10.ToBijective(100));   // prints "9A"
    Console.WriteLine(bijBase10.ToBijective(101));   // prints "A1"
    Console.WriteLine(bijBase10.ToBijective(2010));  // prints "19AA"
}

1 这个通用答案最终可以简化为另一个正确的具体答案,但我发现如果没有一般双射数背后的形式理论,就很难完全掌握解决方案的逻辑。这也很好地证明了它的正确性。此外,有几个类似的问题与这个问题有关,有些是与语言无关的或更通用的。这就是为什么我认为添加这个答案是有道理的,而且这个问题是一个很好的地方。

2 C#免责声明:我用C#实现了一个示例,因为这就是这里要问的,但我从未学习或使用过该语言。我已经验证了它确实可以编译和运行,但如有必要,请对其进行调整以适应语言最佳实践/一般约定。

3 这个例子只是为了正确和易懂;它可以而且应该对性能进行优化(例如,使用尾递归 - 但这似乎需要在 C# 中蹦床),并使其更安全(例如通过验证参数)。

评论

0赞 Stuart Grassie 8/4/2022
这个答案应该得到更多的支持,因为它提供了理论。