Подтвердить что ты не робот

Самая быстрая функция для генерации букв столбцов 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();
}
4b9b3361

Ответ 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 было бы очень болезненным.)