如何在C#中将数字转换为Excel列名,而不使用自动化从Excel直接获取值。
Excel 2007的可能范围是1到16384,这是它支持的列数。转换后的值应该是Excel列名的形式,例如A、AA、AAA等。
如何在C#中将数字转换为Excel列名,而不使用自动化从Excel直接获取值。
Excel 2007的可能范围是1到16384,这是它支持的列数。转换后的值应该是Excel列名的形式,例如A、AA、AAA等。
虽然已经有很多有效的答案1,但没有一个涉及到其背后的理论。
Excel列名是它们编号的双射基数26表示法。这与普通的基数26(没有前导零)非常不同,我真的建议阅读维基百科条目以了解其中的区别。例如,十进制值702
(分解为26*26 + 26
)在“普通”的基数26中由110
表示(即1x26^2 + 1x26^1 + 0x26^0
),而在双射基数26中由ZZ
表示(即26x26^1 + 26x26^0
)。
m
的双射基-k
表示中最后一位数字(索引为0)的通用公式为(f
为向上取整函数减1):m - (f(m / k) * k)
f(m / k)
的结果来找到的。 我们知道对于最后一位数字(即具有最高索引的数字),f(m / k)
为0。k
中找到每个连续数字的迭代的基础。 伪代码如下所示(digit()
将十进制整数映射到其在双射基数中的表示 - 例如,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);
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);
}
}
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# 的例子,因为这是所要求的,但我从未学习过也从未使用过这种语言。我已经验证它可以编译和运行,但如果需要,请根据最佳实践/一般约定来适应它。
3 此示例仅旨在正确和易于理解;如果性能至关重要,则可以进行优化(例如使用尾递归,但似乎在 C# 中需要 trampolining),并使其更安全(例如通过验证参数)。
..并转换为php:
function GetExcelColumnName($columnNumber) {
$columnName = '';
while ($columnNumber > 0) {
$modulo = ($columnNumber - 1) % 26;
$columnName = chr(65 + $modulo) . $columnName;
$columnNumber = (int)(($columnNumber - $modulo) / 26);
}
return $columnName;
}
ord('A')
代替65。 - Mulli这里提供一个简单的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);
}
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
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;
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;
}
如果您只想用单元格公式而不是代码实现,这里提供一个公式:
IF(COLUMN()>=26,CHAR(ROUND(COLUMN()/26,1)+64)&CHAR(MOD(COLUMN(),26)+64),CHAR(COLUMN()+64))
private String getColumn(int c) {
String s = "";
do {
s = (char)('A' + (c % 26)) + s;
c /= 26;
} while (c-- > 0);
return s;
}
它并不完全是26进制,因为该系统中没有0。如果有的话,'Z'将被'B A'而不是'AA'所跟随。
虽然有点晚了,但这是我在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));
}
IndexOf
很慢,最好预先计算反向映射。 - Vlad不知道是否有用,这是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;
}