it-roy-ru.com

Самая быстрая функция для генерации букв столбцов Excel в C #

Какова самая быстрая функция c #, которая принимает и 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();
}
34
Old Man

Я в настоящее время использую это, с 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;
        }

Как уже упоминалось в других сообщениях, результаты могут быть кэшированы.

48
Adriaan Stander

Я могу сказать вам, что самая быстрая функция не будет самой красивой функцией. Вот:

private string[] map = new string[]
    { 
        "A", "B", "C", "D", "E" .............
    };

public string getColumn(int number)
{
    return map[number];
}
19
womp

Не конвертируйте это вообще. 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 (номер строки, номер столбца).

Чтобы выбрать несколько ячеек: Range(Cells(1, 1), Cells(4, 6)) (NB понадобится какой-то квалификатор объекта, если не используется активная рабочая таблица), а не Range("A1:F4")

Свойство Columns может принимать либо букву (например, F), либо число (например, 6)

13
barrowc

Вот моя версия: Это не имеет никаких ограничений, как, например, двухбуквенное или трехбуквенное. Просто введите нужное число (начиная с 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
Vijay Wadnere

Вы можете предварительно сгенерировать все значения в массив строк. Это займет очень мало памяти и может быть рассчитано при первом вызове.

4
JDunkerley

Это написано на 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;
}
2
Dasmowenator

Абсолютный БЫСТРЫЙ, с большой буквы, что в таблице Excel только фиксированное число столбцов, поэтому вы должны сделать таблицу поиска. Объявите постоянный массив строк из 256 записей и заполните его строками от «A» до «IV». Тогда вы просто делаете прямой поиск по индексу.

2
Doug

Попробуйте эту функцию.

// 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);
}

Теперь не нужно занимать что много памяти, чтобы предварительно сгенерировать имя каждого столбца для каждого индекса и сохранить их в одном огромном массиве, поэтому вам не нужно искать имя для любого столбца дважды.

Если я смогу подумать о каких-либо дальнейших оптимизациях, я добавлю их позже, но я считаю, что эта функция должна быть довольно быстрой, и я сомневаюсь, что вам даже нужна такая скорость, если вы выполняете предварительную генерацию.

2
Noldorin

Как только ваша функция запустится, пусть она кеширует результаты в словарь. Так что, это не будет делать вычисления снова.

например Convert (27) проверит, сопоставлено ли 27/сохранено в словаре. Если нет, сделайте расчет и сохраните «AA» против 27 в словаре.

2
shahkalpesh

Ваша первая проблема - вы объявляете 6 переменных в методе. Если метод будет вызываться тысячи раз, простое перемещение его в область видимости вместо области видимости, вероятно, сократит время обработки более чем наполовину.

2
Neil N

Вот краткая реализация с использованием 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 мс. После этого вы можете ссылаться на этот массив строк, если вам это понадобится тысячи раз.

1
Matthew Strawbridge

@Neil 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();
}
1
Eab

Мое решение:

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();
    }
}
0
jaccso
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) ;        
}
0
Ali

извините, что произошла смена. исправленный.

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];
    }



}
0
Bison

Кэширование действительно сокращает время выполнения 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) 

0
foson

идея Барроуца намного удобнее и быстрее, чем любая функция преобразования! я преобразовал его идеи в реальный код 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;
0
k3nn

Просто используйте формулу Excel вместо пользовательской функции (UDF) или другой программы, согласно Аллену Уайетту ( https://Excel.tips.net/T003254_Alphabetic_Column_Designation.html ):

=SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),"")

(В моей организации использование UDF было бы очень болезненным.)

0
A.C. Wilson

Почему бы нам не попробовать факториал?

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] + "";
}
0
singapore saravanan

Код, который я предоставляю, НЕ является C # (вместо Python), но логику можно использовать для любого языка.

Большинство предыдущих ответов верны. Вот еще один способ преобразования номера столбца в столбцы Excel .. Решение довольно простое, если рассматривать это как базовое преобразование. Просто преобразуйте номер столбца в основание 26, поскольку в нем всего 26 букв .... Вот как это можно сделать: 

шаги:

  • установить столбец как частное

  • вычтите одно из фактор-переменной (из предыдущего шага), потому что нам нужно оказаться в таблице ascii с 97, являющимся a.

  • разделите на 26 и получите остаток.

  • добавьте +97 к остатку и преобразуйте в char (поскольку в таблице ASCII указано 97 «a»)
  • частное становится новым частным/26 (так как мы могли бы перейти за 26 столбец)
  • продолжайте делать это, пока частное не станет больше 0, а затем верните результат

вот код, который делает это :)

def convert_num_to_column(column_num):
    result = ""
    quotient = column_num
    remainder = 0
    while (quotient >0):
        quotient = quotient -1
        remainder = quotient%26
        result = chr(int(remainder)+97)+result
        quotient = int(quotient/26)
    return result

print("--",convert_num_to_column(1).upper())
0
grepit

Это рекурсивно. Быстро и правильно:

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];
    }



}
0
Bison