如何在c#中将数值转换为Excel列名,而不使用直接从Excel中获取值的自动化。
Excel 2007的范围可能是1到16384,这是它支持的列数。结果值应以excel列名的形式出现,例如A、AA、AAA等。
如何在c#中将数值转换为Excel列名,而不使用直接从Excel中获取值的自动化。
Excel 2007的范围可能是1到16384,这是它支持的列数。结果值应以excel列名的形式出现,例如A、AA、AAA等。
当前回答
这是编程测试中常见的问题。 它有一些约束条件: 每行最大列数= 702 输出应该有行号+列名,例如703的答案是2A。 (注意:我只是从另一个答案修改了现有的代码) 下面是相同的代码:
static string GetExcelColumnName(long columnNumber)
{
//max number of column per row
const long maxColPerRow = 702;
//find row number
long rowNum = (columnNumber / maxColPerRow);
//find tierable columns in the row.
long dividend = columnNumber - (maxColPerRow * rowNum);
string columnName = String.Empty;
long modulo;
while (dividend > 0)
{
modulo = (dividend - 1) % 26;
columnName = Convert.ToChar(65 + modulo).ToString() + columnName;
dividend = (int)((dividend - modulo) / 26);
}
return rowNum+1+ columnName;
}
}
其他回答
我是这样做的:
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;
}
我想在我使用的静态类中加入,用于在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
虽然已经有了一堆有效的答案,但没有一个能深入到它背后的理论。
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#中进行蹦蹦跳),并变得更安全(例如通过验证参数)。
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'。
这是我在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;
}