在C#中生成Excel列字母的最快函数是什么?

38
什么是最快的 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();
}

1
你能发一下你当前的函数吗?这么简单的东西不应该占用你25%的处理能力。 - Neil N
请查看此问题:https://dev59.com/gnVC5IYBdhLWcg3wbghT。 - Joel Coehoorn
我不确定我的代码有多快,但它应该比你发布的原始代码快得多。我的代码发布在http://www.blackbeltcoder.com/Articles/strings/converting-between-integers-and-spreadsheet-column-labels。 - Jonathan Wood
请参考以下问题:https://dev59.com/QHVC5IYBdhLWcg3wykej - Erwin Mayer
如果您正在使用微软的Excel自动化库,那么您的速度问题可能与列字母函数无关。我有一些代码在循环中运行并将公式应用于单元格。没有复杂的逻辑,但它运行得非常慢。 - Walter Stabosz
22个回答

60

我目前在使用这个,与 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
+1:可能不是最快的,但对我需要做的事情很有用 :) - Ian
1
不错的进制转换变体! :) - Chris Pfohl
点赞,不要在字符串或字符数组中搜索 - Stephan

21

我能告诉你,最快的函数不会是最漂亮的函数。下面是它:

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

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

4
引起关注数组方法很好,但手动定义并不是一个好主意。预生成是正确的方法。 - Noldorin
4
嗨,他要求最快的!任何自动预填代码都会变慢 ;) - womp
1
@womp:这是真的...虽然它只是一次性操作,所以它可以有效地被折扣。除了混乱之外,在代码生成巨大数组和在初始化期间生成它有什么区别呢?我知道,你只是为了好玩而孜孜不倦地对其进行字面上的解释(除非我错了)。 - Noldorin
没错,你说得对 :) 显然,用一些代码预先生成数组会更实用。但这在技术上是绝对最快的。只是为了记录,我投了一些其他答案的赞 ;) - womp
6
顺便提一下,这张地图应该是静态的,这样就不会为每个实例复制(并可能重新生成)它。 - Thomas Levesque

15

不需要进行转换。Excel可以像使用A1标记法一样方便地使用R1C1标记法。

所以(抱歉使用的是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)) (如果不使用活动工作表,则需要某种对象限定符),而不是 Range("A1:F4")

Columns 属性可以采用字母(例如 F)或数字(例如 6)。


7
这是我的版本:它没有任何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

这里是使用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上,调用GetExcelStrings().ToArray()大约需要30毫秒。此后,如果需要重复引用该字符串数组,则无需再次调用该方法。


感谢约束编程。它非常适合某些任务!这是一个懒惰的字母表,可以更快地进行初始生成:var a = new string[] { "" }.Union(from c in Enumerable.Range((int)'A', 26) select Convert.ToString((char)c)); - nurettin
虽然拥有一个懒惰的数据源通常可以减少延迟,但在这种情况下,alphabet的创建几乎是瞬间完成的,因此我认为它不会有所帮助。通过您的修改进行快速测试表明,它会减慢总运行时间(1000次重复从14秒增加到20秒以上)。 - Matthew Strawbridge

4
您可以预先将所有值生成为字符串数组。这需要很少的内存,并且可以在第一次调用时计算。

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。 - BrainStorm.exe
更正,当从 findColChars 传递值时,它会发生。 - BrainStorm.exe

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

现在,为每个索引预生成每个列名并将它们存储在单个大数组中不应该占用太多内存,因此您不需要查找任何列两次的名称。如果我能想到任何进一步的优化,我会在以后添加它们,但我相信这个函数应该非常快,如果您进行预生成,我怀疑您甚至不需要这种速度。

@esac:你说得很对。 (甚至for循环还有另一个问题。:P)老实说,我现在不应该编写代码...所以,在公平的情况下,那确实应该被downvote。谢谢你有礼貌地把它删除了。 :) 对于你的更正+1。 - Noldorin
返回新字符串(name, 3); 没有 string(char[], int) 的重载。也许你的意思是 "new string(name)"。此外,对于行 name[i] = alphabet[rem / div];(是的,我之前定义了字母表),当索引为 (26 % 100) 时,你会得到一个数组越界异常。 - esac

2

最快的方法是将Excel电子表格限制在固定列数,然后使用查找表。声明一个包含256个条目的常量字符串数组,并使用从“A”到“IV”的字符串预填充它。然后只需进行直接索引查找即可。


我不知道你从哪里得出Excel只有固定的256列。我开始滚动和添加文本,现在已经到AEW了,但还是没填完。 - esac
3
在 Excel 2007 之前,最多只能使用 16384 列(XFD)。但是,不怪你放弃了。 ;) - Matthew Flaschen
我正在使用 Office 2005。显然,在新版本中已经扩展了(或完全解除了)256列限制。这当然使编程种子查找表的重要性更加突出。 - Doug

2

一旦你的函数运行完毕,就让它将结果缓存到一个字典中。这样,它就不用再次计算了。

例如,Convert(27) 将会检查 27 是否已经被映射/储存在字典中。如果没有,那么进行计算并在字典中存储 "AA" 和 27 的对应关系。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接