”Excel列名与uint的相互转换“,这是一个挺有意思的命题,也是很多用程序与Excel打交道的程序员感兴趣的话题。
在话题展开之前,要先统一一个重要概念:单独的A-Z对应的uint为1-26。我的实现方法也是将Excel列名的进制认定为26进制。好了,我们言归正传(代码中用到了自己写的一个char的扩展方法)。
#region Join 字符串拼接扩展方法
///
/// 字符串拼接
///
/// 要操作的Char集合
/// 按照参数合并后的字符串
public static string Join(this IEnumerable iCharEnumerable)
{
StringBuilder tempReturnValue = new StringBuilder();
foreach (var tempChar in iCharEnumerable)
{
tempReturnValue.Append(tempChar);
}
return tempReturnValue.ToString();
}
#endregion
这个命题的难度就在于Excel的列名是从字母A到Z,然后AA、ZZ、AAA……,你说它是26进制也行,说它是27进制也未必没有道理。最关键的一点是:在这个进制中没有传统进制中”0“的概念。(单独的A代表1、Z代表26,AA代表27,那么”AA“中第一个A和第二个A分别代表什么呢?好吧,第一个A代表27,第二个A代表0^_^,读者可以考虑一下AAA中第二个A代表多少?)
推理的过程我就不讲了,我讲一下结论:如果列名的长度>=2,则处理第一个字母的时候要+1,处理最后一个字母的时候要-1(从左往右),中间的不特殊处理。
列名转换成uint
///
/// 将Excel列名转换为int
///
///
///
public static uint ExcelColumnToUInt32(this string iColumnName)
{
var tempColumn = iColumnName.Reverse().Join().ToUpper();
var tempReturnValue = 0D;
var tempStep = 26U;
for (int i = 0, tempMax = tempColumn.Length - 1; i <= tempMax; i++)
{
tempReturnValue += Math.Pow(tempStep, i) * ((uint)tempColumn[i] - 64);
if (i == tempMax && i > 0)
{
tempReturnValue++;
}
else if (i == 0 && i < tempMax)
{
tempReturnValue--;
}
}
if (tempReturnValue > uint.MaxValue || tempReturnValue < uint.MinValue)
{
throw new Exception("Excel列名溢出:" + iColumnName);
}
return (uint)tempReturnValue;
}
uint转换成Excel列名
///
/// 将uint转换成Excel列名
///
///
///
public static string ToExcelColumnName(this uint iColumnIndex)
{
var tempColumnIndex = iColumnIndex;
if (tempColumnIndex == 0)
{
throw new ArgumentOutOfRangeException("ColumnIndex必须大于0");
}
StringBuilder tempColumnName = new StringBuilder();
uint tempMode = 0;
uint tempRemainder = 0;
uint tempCurrentPow = 26;
for (int i = 0; ; i++)
{
if (i == 0)
{
tempRemainder = (uint)Math.Floor((double)(tempColumnIndex - 1u) / tempCurrentPow);
tempMode = (tempColumnIndex - 1u) % tempCurrentPow;
tempColumnName.Append((char)(64 + tempMode + 1));
if (tempRemainder == 0)
{
break;
}
tempColumnIndex = tempRemainder;
}
else
{
tempRemainder = (uint)Math.Floor((double)(tempColumnIndex) / tempCurrentPow);
tempMode = tempColumnIndex % tempCurrentPow;
if (tempMode == 0)
{
tempRemainder--;
tempMode = 26;
}
tempColumnName.Insert(0, (char)(64 + tempMode));
if (tempRemainder == 0)
{ break; }
tempColumnIndex = tempRemainder;
}
}
return tempColumnName.ToString();
}
为了验证上述方法,我做了如下测试,先上测试代码(一个命令控制台代码,很简单,就没写什么注释),后上论证过程
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace TestExcelColumnNameConverter
{
class Program
{
static void Main(string[] args)
{
var tempMax = 27;
List tempList = new List();
for (int i = 0; i < tempMax; i++)
{
Console.WriteLine(i);
for (int j = 0; j < tempMax; j++)
{
if (i > 0 && j == 0)
{ j++; }
for (int k = 0; k < tempMax; k++)
{
if (j > 0 && k == 0)
{ k++; }
for (int x = 0; x < tempMax; x++)
{
if (k > 0 && x == 0)
{ x++; }
for (int y = 1; y < tempMax; y++)
{
var tempItem = new ColumnNameAndUint();
tempList.Add(tempItem);
var tempColumnName = new StringBuilder();
tempColumnName.Append(i > 0 ? ((char)(64 + i)).ToString() : string.Empty)
.Append(j > 0 ? ((char)(64 + j)).ToString() : string.Empty)
.Append(k > 0 ? ((char)(64 + k)).ToString() : string.Empty)
.Append(x > 0 ? ((char)(64 + x)).ToString() : string.Empty)
.Append((char)(64 + y));
tempItem.OriginalStrName = tempColumnName.ToString();
tempItem.UintName = tempItem.OriginalStrName.ExcelColumnToUInt32();
tempItem.StrName = tempItem.UintName.ToExcelColumnName();
}
}
}
}
}
tempList.Where(s => s.OriginalStrName != s.StrName)
.ToList().ForEach(s =>
{
Console.WriteLine("{0}\t{1}\t{2}", s.OriginalStrName, s.StrName, s.UintName);
});
Console.WriteLine("正在写入文件");
using (var tempFs = new StreamWriter(String.Format("../../{0}.txt", DateTime.Now.ToString("yyyMMddHHmmss")), true, Encoding.UTF8))
{
tempList.ForEach(s =>
{
tempFs.WriteLine("{0}\t{1}\t{2}", s.OriginalStrName, s.StrName, s.UintName);
});
tempFs.Flush();
tempFs.Close();
}
Console.WriteLine("写入完毕,输入任意字符退出程序!");
Console.ReadKey();
}
}
///
/// 用于承载测试内容的载体,没有其他的意义
///
public class ColumnNameAndUint
{
public string OriginalStrName { get; set; }
public String StrName { get; set; }
public uint UintName { get; set; }
}
///
/// Excel列名和Uint转换功能的扩展类
///
public static class ExcelExtension
{
#region Join 字符串拼接扩展方法
///
/// 字符串拼接
///
/// 要操作的Char集合
/// 按照参数合并后的字符串
public static string Join(this IEnumerable iCharEnumerable)
{
StringBuilder tempReturnValue = new StringBuilder();
foreach (var tempChar in iCharEnumerable)
{
tempReturnValue.Append(tempChar);
}
return tempReturnValue.ToString();
}
#endregion
#region ExcelColumnToUInt32
///
/// 将Excel列名转换为int
///
///
///
public static uint ExcelColumnToUInt32(this string iColumnName)
{
var tempColumn = iColumnName.Reverse().Join().ToUpper();
var tempReturnValue = 0D;
var tempStep = 26U;
for (int i = 0, tempMax = tempColumn.Length - 1; i <= tempMax; i++)
{
tempReturnValue += Math.Pow(tempStep, i) * ((uint)tempColumn[i] - 64);
if (i == tempMax && i > 0)
{
tempReturnValue++;
}
else if (i == 0 && i < tempMax)
{
tempReturnValue--;
}
}
if (tempReturnValue > uint.MaxValue || tempReturnValue < uint.MinValue)
{
throw new Exception("Excel列名溢出:" + iColumnName);
}
return (uint)tempReturnValue;
}
#endregion
#region ToExcelColumnName
///
/// 将uint转换成Excel列名
///
///
///
public static string ToExcelColumnName(this uint iColumnIndex)
{
var tempColumnIndex = iColumnIndex;
if (tempColumnIndex == 0)
{
throw new ArgumentOutOfRangeException("ColumnIndex必须大于0");
}
StringBuilder tempColumnName = new StringBuilder();
uint tempMode = 0;
uint tempRemainder = 0;
uint tempCurrentPow = 26;
for (int i = 0; ; i++)
{
if (i == 0)
{
tempRemainder = (uint)Math.Floor((double)(tempColumnIndex - 1u) / tempCurrentPow);
tempMode = (tempColumnIndex - 1u) % tempCurrentPow;
tempColumnName.Append((char)(64 + tempMode + 1));
if (tempRemainder == 0)
{
break;
}
tempColumnIndex = tempRemainder;
}
else
{
tempRemainder = (uint)Math.Floor((double)(tempColumnIndex) / tempCurrentPow);
tempMode = tempColumnIndex % tempCurrentPow;
if (tempMode == 0)
{
tempRemainder--;
tempMode = 26;
}
tempColumnName.Insert(0, (char)(64 + tempMode));
if (tempRemainder == 0)
{ break; }
tempColumnIndex = tempRemainder;
}
}
return tempColumnName.ToString();
}
#endregion
}
}
上述代码会测试从A到ZZZZZ(5个Z)的区间内的所有列名转换,生成了一个247M的txt文件,我将文件内容导入到Sql server2008r2进行数据分析,以证明转换方法是正确的。
txt文件我就不上传了o(╯□╰)o,贴个截图吧
创建用于分析数据的物理表
CREATE TABLE ExcelColumnInfo
(
OriginalName NVARCHAR(20) ,
StrName NVARCHAR(20) ,
UintName BIGINT
)
--导入数据后创建三个蹩脚的索引
CREATE INDEX aa ON dbo.ExcelColumnInfo(OriginalName)
CREATE INDEX bb ON dbo.ExcelColumnInfo(StrName)
CREATE INDEX cc ON dbo.ExcelColumnInfo(UintName)
将文本文件内容导入到Sql中
--查看最小值
SELECT MIN(OriginalName) AS OriginalName ,
MIN(StrName) AS StrName ,
MIN(UintName) AS UintName
FROM dbo.ExcelColumnInfo
结果集
--查看最大值
SELECT MAX(OriginalName) AS OriginalName ,
MAX(StrName) AS StrName ,
MAX(UintName) AS UintName
FROM dbo.ExcelColumnInfo
结果集
--查看通过转换函数生成列名是否存在于原始列名不存在的值
SELECT *
FROM dbo.ExcelColumnInfo
WHERE OriginalName <> StrName ;
结果集
--查询UnintName是否存在数据孤岛
SELECT *
FROM dbo.ExcelColumnInfo T1
WHERE NOT EXISTS ( SELECT 1
FROM dbo.ExcelColumnInfo T2
WHERE t1.UintName + 1 = t2.UintName
OR t1.UintName - 1 = t2.UintName )
结果集(查询的时候有点慢)
通过上述验证,可以证明转换规则的正确性。如果发现bug可以联系我。