Самая быстрая функция для генерации букв столбцов Excel в С#
Какова самая быстрая функция С#, которая принимает и int, и возвращает строку, содержащую букву или буквы для использования в функции 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();
}
Ответы
Ответ 1
В настоящее время я использую это, с 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;
}
Как упоминалось в других сообщениях, результаты могут быть кэшированы.
Ответ 2
Я могу сказать вам, что самая быстрая функция не будет самой красивой функцией. Вот он:
private string[] map = new string[]
{
"A", "B", "C", "D", "E" .............
};
public string getColumn(int number)
{
return map[number];
}
Ответ 3
Не конвертируйте его вообще. Excel может работать в нотации R1C1 так же, как и в нотации A1.
Итак (извинения за использование VBA, а не С#):
Application.Worksheets("Sheet1").Range("B1").Font.Bold = True
можно легко записать так:
Application.Worksheets("Sheet1").Cells(1, 2).Font.Bold = True
Свойство Range
принимает обозначение A1, тогда как свойство Cells
принимает (номер строки, номер столбца).
Чтобы выбрать несколько ячеек: Range(Cells(1, 1), Cells(4, 6))
(для использования NB потребуется какой-то спецификатор объектов, если не использовать активный лист), а не Range("A1:F4")
Свойство Columns
может принимать либо букву (например, F), либо число (например, 6)
Ответ 4
Здесь моя версия: у этого нет никаких ограничений как на такое 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;
}
Я не тестировал это для производительности, если кто-то может это сделать, это будет отлично для других.
(Извините за ленивость):)
Ура!
Ответ 5
Вы можете предварительно сгенерировать все значения в массив строк. Это займет очень мало памяти и может быть рассчитано при первом вызове.
Ответ 6
Абсолютный FASTEST, будет заглавным, что таблица Excel содержит только фиксированное количество столбцов, так что вы будете делать таблицу поиска. Объявите постоянный массив строк из 256 записей и предварительно заполните его строками от "А" до "IV". Затем вы просто выполняете прямой поиск по индексу.
Ответ 7
Попробуйте эту функцию.
// 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);
}
Теперь он не должен занимать столько памяти, чтобы предварительно генерировать каждое имя столбца для каждого индекса и хранить их в одном огромном массиве, поэтому вам не нужно дважды искать имя для любого столбца.
Если я могу подумать о дальнейших оптимизациях, я добавлю их позже, но я считаю, что эта функция должна быть довольно быстрой, и я сомневаюсь, что вам даже нужна такая скорость, если вы делаете предварительное поколение.
Ответ 8
Как только ваша функция запустится, дайте ей кешировать результаты в словарь. Таким образом, ему больше не придется выполнять вычисления.
например. Convert (27) проверяет, отображается ли 27 в словаре. Если нет, выполните вычисления и сохраните "AA" против 27 в словаре.
Ответ 9
Ваша первая проблема заключается в том, что вы объявляете 6 переменных в методе. Если metd будет называться тысячи раз, просто переместить их в область класса вместо области действия, возможно, сократит время обработки более чем на половину с места в карьер.
Ответ 10
Это написано на 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;
}
Ответ 11
@Neil N - хороший код Я думаю, что у третьегоLetter должен быть +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();
}
Ответ 12
Вот краткая реализация с использованием 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
.
На моем ПК вызов GetExcelStrings().ToArray()
занимает около 30 мс. После этого вы можете обратиться к этому массиву строк, если вам это нужно тысячи раз.
Ответ 13
Кэширование действительно сокращает время выполнения 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;
}
Я думаю, что кэширование в худшем случае (ударить каждое значение) не может занимать больше 250 кб (17576 возможных значений * (sizeof (int) = 4 + sizeof (char) * 3 + строка служебных сообщений = 2 )
Ответ 14
Это рекурсивный. Быстро и правильно:
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];
}
}
Ответ 15
Извините, произошла смена. исправлена.
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];
}
}
Ответ 16
Мое решение:
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();
}
}
Ответ 17
Идея barrowc намного удобнее и быстрее, чем любая функция преобразования! я перевел свои идеи на фактический код С#, который я использую:
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;
Ответ 18
Почему бы нам не попробовать факториал?
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] + "";
}
Ответ 19
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) ;
}
Ответ 20
Просто используйте формулу Excel вместо пользовательской функции (UDF) или другой программы, для Allen Wyatt (https://excel.tips.net/T003254_Alphabetic_Column_Designation.html)
=SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),"")
(В моей организации использование UDF было бы очень болезненным.)