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

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


当前回答

巧合和优雅的Ruby版本:

def col_name(col_idx)
    name = ""
    while col_idx>0
        mod     = (col_idx-1)%26
        name    = (65+mod).chr + name
        col_idx = ((col_idx-mod)/26).to_i
    end
    name
end

其他回答

f#版本的各种方式

let rec getExcelColumnName x  = if x<26 then int 'A'+x|>char|>string else (x/26-1|>c)+ c(x%26)

对不起,最小化,正在开发一个更好的https://stackoverflow.com/a/4500043/57883版本

相反的方向:

// return values start at 0
let getIndexFromExcelColumnName (x:string) =
    let a = int 'A'
    let fPow len i =
        Math.Pow(26., len - 1 - i |> float)
        |> int

    let getValue len i c = 
        int c - a + 1 * fPow len i
    let f i = getValue x.Length i x.[i]
    [0 .. x.Length - 1]
    |> Seq.map f
    |> Seq.sum
    |> fun x -> x - 1

我是这样做的:

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;
}

如果你只是想要一个没有代码的单元格公式,这里有一个公式:

IF(COLUMN()>=26,CHAR(ROUND(COLUMN()/26,1)+64)&CHAR(MOD(COLUMN(),26)+64),CHAR(COLUMN()+64))

如果有人需要在没有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

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

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#中进行蹦蹦跳),并变得更安全(例如通过验证参数)。