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

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


当前回答

这个片段适用于A到ZZ列名

string columnName = columnNumber > 26 ? Convert.ToChar(64 + (columnNumber / 26)).ToString() + Convert.ToChar(64 + (columnNumber % 26)) : Convert.ToChar(64 + columnNumber).ToString();

其他回答

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

这是我的解,在常数时间内运行(没有循环)。此解决方案适用于所有可能的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);
    }
}
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的评论是正确的。这就是我在浏览器中编写代码的结果。:-)我的解决方案是不编译,它是最左边的字母,它是在反向顺序构建字符串-现在都固定了。

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

更新2:Joel Coehoorn是对的-上面的代码将返回AB为27。如果它是一个以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;

以下是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;

}

虽然已经有了一堆有效的答案,但没有一个能深入到它背后的理论。

Excel列名是其数字的以26为基数的双射表示。这与普通的26进制有很大的不同(没有前导零),我真的建议阅读维基百科的条目来了解区别。例如,十进制值702(分解为26*26 + 26)以“普通”底数26 × 110表示(即1x26^2 + 1x26^1 + 0x26^0),以双射底数26 × ZZ表示(即26x26^1 + 26x26^0)。

除了区别之外,双射计数是一种位置符号,因此我们可以使用迭代(或递归)算法来执行转换,该算法在每次迭代中查找下一个位置的数字(类似于普通的基数转换算法)。

获得十进制数m的双射base-k表示的最后一个位置(索引为0的位置)的数字的一般公式是(f是天花板函数- 1):

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

下一个位置的数字(即下标为1的数字)可以通过对f(m / k)的结果应用相同的公式来求得。我们知道,对于最后一位数字(即下标最高的数字),f(m / k)为0。

这构成了迭代的基础,该迭代查找十进制数的双射进制k中的每个连续数字。在伪代码中,它看起来像这样(digit()将一个十进制整数映射到它在双射进制中的表示——例如,digit(1)将在双射进制26中返回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,以获得一个通用的“conversion to bijective base-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的最大列索引是16384 / XFD,但是这段代码可以转换任何正数。

作为一个额外的奖励,我们现在可以很容易地转换为任何双射基。例如,以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#中实现了一个例子,因为这是这里要求的,但我从未学习或使用过这种语言。我已经验证了它的编译和运行,但如果有必要,请调整它以适应语言的最佳实践/一般惯例。

这个例子只是为了正确和容易理解;它可以也应该优化性能(例如使用尾递归——但这似乎需要在c#中进行蹦蹦跳),并变得更安全(例如通过验证参数)。

这些我的代码转换特定的数字(索引从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));
    }