在C#中生成Excel列字母的最快功能

问题描述 投票:34回答:21

什么是最快的c#函数,它接受并返回包含一个或多个字母的字符串,以便在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();
}
c# excel performance
21个回答
49
投票

我目前使用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
投票

这是用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;
}

1
投票

@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
投票

这是使用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

在我的电脑上,调用GetExcelStrings().ToArray()大约需要30毫秒。此后,如果需要数千次,可以引用此字符串数组。


0
投票

缓存确实会将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个可能的值*(sizeof(int)= 4 + sizeof(char)* 3 + string overhead = 2)


0
投票

它是递归的。快,右:

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
投票

抱歉有转变。纠正。

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
投票

我的解决方案

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
投票

barrowc的想法比任何转换功能都更方便,更快捷!我已将他的想法转换为我使用的实际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
投票

我们为什么不尝试阶乘?

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
投票
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) ;        
}

19
投票

我可以告诉你,最快的功能不是最漂亮的功能。这里是:

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

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

0
投票

根据Allen Wyatt(https://excel.tips.net/T003254_Alphabetic_Column_Designation.html),只使用Excel公式而不是用户定义函数(UDF)或其他程序:

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

(在我的组织中,使用UDF会非常痛苦。)


0
投票

我提供的代码不是C#(而是python),但逻辑可以用于任何语言。

以前的大部分答案都是正确的。这是将列号转换为excel列的另一种方法。如果我们将此视为基本转换,那么解决方案就相当简单了。简单地说,将列号转换为基数26,因为只有26个字母。以下是如何执行此操作的方法:

脚步:

  • 将列设置为商
  • 从商变量中减去一个(来自上一步)因为我们需要以97为a来结束ascii table
  • 除以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())

14
投票

根本不要转换它。 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)


5
投票

这是我的版本:这没有任何限制,如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;
}

我没有测试这个性能,如果有人能做到这一点对其他人来说会很棒。 (抱歉懒惰):)

干杯!


4
投票

您可以将所有值预生成为字符串数组。这将占用非常少的内存并且可以在第一次调用时计算。


2
投票

函数运行后,让它将结果缓存到字典中。因此,它不必再次进行计算。

例如Convert(27)将检查27是否被映射/存储在字典中。如果没有,请进行计算并在字典中将“AA”存储为27。


2
投票

绝对的FASTEST,只是将Excel电子表格大写为固定数量的列,因此您将执行查找表。声明一个包含256个条目的常量字符串数组,并使用从“A”到“IV”的字符串预填充它。然后,您只需进行直接索引查找。


2
投票

试试这个功能。

// 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
投票

您的第一个问题是您在方法中声明了6个变量。如果一个methd将被调用数千次,那么将它们移动到类范围而不是函数范围可能会使你的处理时间减少一半以上。

© www.soinside.com 2019 - 2024. All rights reserved.