在C#中生成Excel列字母的最快函数
什么是最快的C#函数和int,并返回一个string包含一个或多个字母在Excel函数中使用? 例如,1返回“A”,26返回“Z”,27返回“AA”等。
这被称为成千上万次,并且需要25%的时间来生成具有许多公式的大型电子表格。
public string Letter(int intCol) { int intFirstLetter = ((intCol) / 676) + 64; int intSecondLetter = ((intCol % 676) / 26) + 64; int intThirdLetter = (intCol % 26) + 65; char FirstLetter = (intFirstLetter > 64) ? (char)intFirstLetter : ' '; char SecondLetter = (intSecondLetter > 64) ? (char)intSecondLetter : ' '; char ThirdLetter = (char)intThirdLetter; return string.Concat(FirstLetter, SecondLetter, ThirdLetter).Trim(); }
我目前使用Excel 2007
public static string ExcelColumnFromNumber(int column) { string columnString = ""; decimal columnNumber = column; while (columnNumber > 0) { decimal currentLetterNumber = (columnNumber - 1) % 26; char currentLetter = (char)(currentLetterNumber + 65); columnString = currentLetter + columnString; columnNumber = (columnNumber - (currentLetterNumber + 1)) / 26; } return columnString; }
和
public static int NumberFromExcelColumn(string column) { int retVal = 0; string col = column.ToUpper(); for (int iChar = col.Length - 1; iChar >= 0; iChar--) { char colPiece = col[iChar]; int colNum = colPiece - 64; retVal = retVal + colNum * (int)Math.Pow(26, col.Length - (iChar + 1)); } return retVal; }
正如其他职位所述,结果可以被caching。
我可以告诉你,最快的function不会是最漂亮的function。 这里是:
private string[] map = new string[] { "A", "B", "C", "D", "E" ............. }; public string getColumn(int number) { return map[number]; }
不要转换它。 Excel可以使用R1C1表示法,也可以使用A1表示法。
所以(道歉使用VBA而不是C#):
Application.Worksheets("Sheet1").Range("B1").Font.Bold = True
可以简单写成:
Application.Worksheets("Sheet1").Cells(1, 2).Font.Bold = True
Range
属性取A1表示法,而Cells
属性取(行号,列号)。
若要select多个单元格: Range(Cells(1, 1), Cells(4, 6))
(NB将需要某种对象限定符,如果不使用活动工作表)而不是Range("A1:F4")
Columns
属性可以是一个字母(例如F)或一个数字(例如6)
这是我的版本:这没有任何限制,如2个字母或3个字母。 只需传入所需的数字(从0开始)将传回的数字返回Excel列标题,如字母顺序:
private string GenerateSequence(int num) { string str = ""; char achar; int mod; while (true) { mod = (num % 26) + 65; num = (int)(num / 26); achar = (char)mod; str = achar + str; if (num > 0) num--; else if (num == 0) break; } return str; }
我没有testing过这个performance,如果有人能做到这一点,将会对其他人很好。 (对不起懒惰):)
干杯!
你可以预先生成所有的值到一个string数组。 这将需要很less的内存,可以计算在第一个电话。
一旦你的函数运行,让它caching结果到一个字典。 这样,就不用再做计算了。
例如Convert(27)将检查27是否被映射/存储在字典中。 如果没有,则进行计算,并在字典中存储“AA”与27。
绝对最快,将大写Excel电子表格只有固定数量的列,所以你会做一个查找表。 声明一个包含256个条目的常量string数组,并使用从“A”到“IV”的string预填充它。 然后你只需要做一个直接的索引查找。
试试这个function。
// Returns name of column for specified 0-based index. public static string GetColumnName(int index) { var name = new char[3]; // Assumes 3-letter column name max. int rem = index; int div = 17576; // 26 ^ 3 for (int i = 2; i >= 0; i++) { name[i] = alphabet[rem / div]; rem %= div; div /= 26; } if (index >= 676) return new string(name, 3); else if (index >= 26) return new string(name, 2); else return new string(name, 1); }
现在它不应该占用那么多的内存来为每个索引预先生成每个列名并将它们存储在一个巨大的数组中,所以您不需要为任何列查找两次名称。
如果我能想到更进一步的优化,我会稍后添加它们,但是我相信这个function应该是相当快的,而且我怀疑你甚至需要这样的速度。
你的第一个问题是你在方法中声明了6个variables。 如果一个方法被调用了几千次,那么把它们移动到类作用域而不是函数作用域可能会使你的处理时间减less一半以上。
这是用Java编写的,但基本上是一样的。
以下代码用于计算列的标签(大写),并使用基于0的索引:
public static String findColChars(long index) { char[] ret = new char[64]; for (int i = 0; i < ret.length; ++i) { int digit = ret.length - i - 1; long test = index - powerDown(i + 1); if (test < 0) break; ret[digit] = toChar(test / (long)(Math.pow(26, i))); } return new String(ret); } private static char toChar(long num) { return (char)((num % 26) + 65); }
下面是从大写标签中为列计算基于0的索引的代码:
public static long findColIndex(String col) { long index = 0; char[] chars = col.toCharArray(); for (int i = 0; i < chars.length; ++i) { int cur = chars.length - i - 1; index += (chars[cur] - 65) * Math.pow(26, i); } return index + powerDown(chars.length); } private static long powerDown(int limit) { long acc = 0; while (limit > 1) acc += Math.pow(26, limit-- - 1); return acc; }
@尼尔N – 不错的代码我认为第三个信件应该有一个+64而不是+65? 我对吗?
public string Letter(int intCol) { int intFirstLetter = ((intCol) / 676) + 64; int intSecondLetter = ((intCol % 676) / 26) + 64; int intThirdLetter = (intCol % 26) + 65; ' SHOULD BE + 64? char FirstLetter = (intFirstLetter > 64) ? (char)intFirstLetter : ' '; char SecondLetter = (intSecondLetter > 64) ? (char)intSecondLetter : ' '; char ThirdLetter = (char)intThirdLetter; return string.Concat(FirstLetter, SecondLetter, ThirdLetter).Trim(); }
这是一个使用LINQ的简洁实现。
static IEnumerable<string> GetExcelStrings() { string[] alphabet = { string.Empty, "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" }; return from c1 in alphabet from c2 in alphabet from c3 in alphabet.Skip(1) // c3 is never empty where c1 == string.Empty || c2 != string.Empty // only allow c2 to be empty if c1 is also empty select c1 + c2 + c3; }
这产生A
到Z
,然后AA
到ZZ
,然后AAA
到ZZZ
。
在我的PC上,调用GetExcelStrings().ToArray()
大约需要30毫秒。 此后,如果需要数千次的话,可以参考这个string数组。
虽然caching确实会将10,000,000个随机调用的运行时间降低到其1/3的值,
static Dictionary<int, string> LetterDict = new Dictionary<int, string>(676); public static string LetterWithCaching(int index) { int intCol = index - 1; if (LetterDict.ContainsKey(intCol)) return LetterDict[intCol]; int intFirstLetter = ((intCol) / 676) + 64; int intSecondLetter = ((intCol % 676) / 26) + 64; int intThirdLetter = (intCol % 26) + 65; char FirstLetter = (intFirstLetter > 64) ? (char)intFirstLetter : ' '; char SecondLetter = (intSecondLetter > 64) ? (char)intSecondLetter : ' '; char ThirdLetter = (char)intThirdLetter; String s = string.Concat(FirstLetter, SecondLetter, ThirdLetter).Trim(); LetterDict.Add(intCol, s); return s; }
(sizeof(int)= 4 + sizeof(char)* 3 + string overhead = 2),我认为在最坏的情况下caching(每个值都达到)不能超过250kb
它是recursion的。 快速,正确的:
class ToolSheet { //Not the prettyest but surely the fastest : static string[] ColName = new string[676]; public ToolSheet() { ColName[0] = "A"; for (int index = 1; index < 676; ++index) Recurse(index, index); } private int Recurse(int i, int index) { if (i < 1) return 0; ColName[index] = ((char)(65 + i % 26)).ToString() + ColName[index]; return Recurse(i / 26, index); } public string GetColName(int i) { return ColName[i - 1]; } }
对不起有一个转变。 纠正。
class ToolSheet { //Not the prettyest but surely the fastest : static string[] ColName = new string[676]; public ToolSheet() { for (int index = 0; index < 676; ++index) { Recurse(index, index); } } private int Recurse(int i, int index) { if (i < 1) { if (index % 26 == 0 && index > 0) ColName[index] = ColName[index - 1].Substring(0, ColName[index - 1].Length - 1) + "Z"; return 0; } ColName[index] = ((char)(64 + i % 26)).ToString() + ColName[index]; return Recurse(i / 26, index); } public string GetColName(int i) { return ColName[i - 1]; } }
我的解决scheme
static class ExcelHeaderHelper { public static string[] GetHeaderLetters(uint max) { var result = new List<string>(); int i = 0; var columnPrefix = new Queue<string>(); string prefix = null; int prevRoundNo = 0; uint maxPrefix = max / 26; while (i < max) { int roundNo = i / 26; if (prevRoundNo < roundNo) { prefix = columnPrefix.Dequeue(); prevRoundNo = roundNo; } string item = prefix + ((char)(65 + (i % 26))).ToString(CultureInfo.InvariantCulture); if (i <= maxPrefix) { columnPrefix.Enqueue(item); } result.Add(item); i++; } return result.ToArray(); } }
barrowc的想法比任何转换function都方便快捷! 我已经将他的想法转换为我使用的实际C#代码:
var start = m_xlApp.Cells[nRow1_P, nCol1_P]; var end = m_xlApp.Cells[nRow2_P, nCol2_P]; // cast as Range to prevent binding errors m_arrRange = m_xlApp.get_Range(start as Range, end as Range); object[] values = (object[])m_arrRange.Value2;
我们为什么不试试阶乘?
public static string GetColumnName(int index) { const string letters = "ZABCDEFGHIJKLMNOPQRSTUVWXY"; int NextPos = (index / 26); int LastPos = (index % 26); if (LastPos == 0) NextPos--; if (index > 26) return GetColumnName(NextPos) + letters[LastPos]; else return letters[LastPos] + ""; }
private String columnLetter(int column) { if (column <= 0) return ""; if (column <= 26){ return (char) (column + 64) + ""; } if (column%26 == 0){ return columnLetter((column/26)-1) + columnLetter(26) ; } return columnLetter(column/26) + columnLetter(column%26) ; }
根据Allen Wyatt( https://excel.tips.net/T003254_Alphabetic_Column_Designation.html ),只需使用Excel公式而不是用户定义函数(UDF)或其他程序:
=SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),"")
(在我的组织中,使用UDF将是非常痛苦的。)