programing

C#에서 Excel 열 문자를 가장 빠르게 생성하는 기능

easyjava 2023. 4. 29. 10:03
반응형

C#에서 Excel 열 문자를 가장 빠르게 생성하는 기능

엑셀 함수에서 사용하기 위해 문자가 포함된 문자열을 가져다가 들여보내고 반환하는 가장 빠른 c# 함수는 무엇입니까?예를 들어, 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;
        }

다른 게시물에서 언급한 것처럼 결과를 캐시할 수 있습니다.

가장 빠른 기능이 가장 예쁜 기능은 아닐 것이라고 말씀드릴 수 있습니다.여기 있습니다.

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

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

전혀 변환하지 마십시오.Excel은 A1 표기법뿐만 아니라 R1 C1 표기법에서도 작동할 수 있습니다.

그래서 (C#이 아닌 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))활성 를 사용하지 경우 어떤 합니다.)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;
}

저는 성능 테스트를 하지 않았습니다, 만약 누군가가 그것을 할 수 있다면 다른 사람들에게 좋을 것입니다. (나태해서 죄송합니다) :)

건배!

모든 값을 문자열 배열로 미리 생성할 수 있습니다.이 작업은 메모리가 거의 필요하지 않으며 첫 번째 호출 시 계산할 수 있습니다.

다음은 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;
}

하면 이생성니다가 생성됩니다.AZ,그리고나서AAZZ,그리고나서AAAZZZ.

PC에서 내PC, 출PC에 GetExcelStrings().ToArray()약 30ms가 소요됩니다.그 후에 필요한 경우 이 문자열 배열을 수천 번 참조할 수 있습니다.

함수가 실행되면 결과를 사전에 캐시합니다.따라서 다시 계산할 필요가 없습니다.

예: Convert(27)는 27이 사전에 매핑/저장되어 있는지 확인합니다.그렇지 않은 경우, 계산을 수행하고 사전에 27에 "AA"를 저장합니다.

절대적으로 가장 빠른 방법은 Excel 스프레드시트가 고정된 수의 열만 사용하도록 자본화하는 것입니다. 따라서 룩업 테이블을 수행할 수 있습니다.256개 항목의 일정한 문자열 배열을 선언하고 "A"에서 "IV"까지의 문자열로 미리 채웁니다.그런 다음 단순한 인덱스 조회를 수행합니다.

이 기능을 사용해 보십시오.

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

이제 모든 인덱스에 대해 각 열 이름을 미리 생성하고 하나의 거대한 배열에 저장하는 데 그렇게 많은 메모리가 필요하지 않으므로 열 이름을 두 번 조회할 필요가 없습니다.

추가 최적화를 생각할 수 있다면 나중에 추가하겠습니다. 하지만 이 기능은 상당히 빨라야 한다고 생각합니다. 그리고 사전 세대를 수행하는 경우에도 이러한 속도가 필요할 것이라고 생각합니다.

첫 번째 문제는 메소드에 6개의 변수를 선언한다는 것입니다.메소드가 수천 번 호출되는 경우 함수 범위 대신 클래스 범위로 이동하는 것만으로도 처리 시간이 즉시 절반 이상 단축될 수 있습니다.

이것은 자바로 쓰여 있지만, 기본적으로 같은 것입니다.

다음 코드는 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;
}

@Neil N -- 좋은 코드입니다. 제 생각에 세 번째 글자는 +65가 아니라 +64를 가져야 한다고 생각합니다. 제가 맞나요?

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

우리가 요인화를 시도해 보는 게 어때요?

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

캐슁은 실제로 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;
    }

최악의 경우(매 값 적중)의 캐싱은 250kb(가능한 값 17576개 *(size of(int)=4 + size of(char)*3 + string overhead=2)를 초과할 수 없습니다.

그것은 재귀적입니다.빠르고 정확하게:

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



}

내 솔루션:

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

바로크의 아이디어는 어떤 변환 함수보다 훨씬 편리하고 빠릅니다! 저는 그의 아이디어를 제가 사용하는 실제 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;
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) 에 따르면 UDF(사용자 정의 함수)나 다른 프로그램 대신 Excel 공식을 사용하면 됩니다.

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

(우리 조직에서는 UDF를 사용하는 것이 매우 고통스러울 것입니다.)

제가 제공하는 코드는 C#(대신 파이썬)이 아니라 어떤 언어든 논리를 사용할 수 있습니다.

대부분의 이전 답은 정답입니다.열 번호를 Excel 열로 변환하는 또 다른 방법이 있습니다. 이것을 기본 변환으로 생각하면 솔루션은 매우 간단합니다.26자만 있으므로 열 번호를 기본 26으로 변환합니다.이를 위한 방법은 다음과 같습니다.

단계:

  • 열을 지수로 설정합니다.

  • 97이 a인 ASCII 테이블에 도달해야 하기 때문에 (이전 단계에서) 계수 변수에서 1을 뺍니다.

  • 26으로 나누고 나머지를 구하세요.

  • 나머지에 +97을 추가하고 char로 변환(97은 ASCII 테이블에서 "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())

A1에서만 시작하는 문자가 아닌 문자를 생성해야 하는 경우

private static string GenerateCellReference(int n, int startIndex = 65)
            {
                string name = "";
                n += startIndex - 65;

                while (n > 0)
                {
                    n--;
                    name = (char)((n % 26) + 65) + name;
                    n /= 26;
                }

                return name + 1;
            }

언급URL : https://stackoverflow.com/questions/837155/fastest-function-to-generate-excel-column-letters-in-c-sharp

반응형