2011-01-21 43 views
3

我看到这个问题很多,你有一些模糊的unicode字符,它有点像某个ASCII字符,无论什么原因需要在运行时转换。有没有更优雅的方法来改变Unicode到Ascii?

在这种情况下,我试图导出到CSV。已经对破折号,emdash,endash和hbar使用了一个令人讨厌的修复方法,我刚刚收到了一个'`'的新请求。除了另一个讨厌的问题,还有另一种更好的方法来做到这一点吗?

继承人什么我此刻...

 formattedString = formattedString.Replace(char.ConvertFromUtf32(8211), "-"); 
     formattedString = formattedString.Replace(char.ConvertFromUtf32(8212), "-"); 
     formattedString = formattedString.Replace(char.ConvertFromUtf32(8213), "-"); 

什么想法?

+0

听起来类似于http://stackoverflow.com/questions/2624646/convert-unicode-char-to -closest-most-similar-char-in-ascii-net – 2011-01-21 16:29:59

回答

7

这是一个相当不雅的问题,所以没有一种方法会真的很优雅。

不过,我们当然可以改进一些东西。哪种方法最适合取决于需要进行的更改的数量(以及要更改的字符串的大小,尽管通常最好假定这种更改是或可能很大)。

在一个替换字符中,目前使用的方法 - 使用.Replace比较好,但我会用"\u2013"替换char.ConvertFromUtf32(8211)。对性能的影响可以忽略不计,但它更具可读性,因为在U + 2013中以十六进制形式引用该字符比使用十进制符号更为常见(当然,char.ConvertFromUtf32(0x2013)在那里会有相同的优势,但仅使用char符号)。 (也可以直接将'–'直接放入代码中 - 在某些情况下更具可读性,但在这种情况下,读者看起来与“ - ”或“ - ”几乎相同)。

我还将字符串替换替换为稍微快一点的字符替换(在这种情况下,至少,您用一个字符替换单个字符)。

采取这种方法,你的代码就变成:

formattedString = formattedString.Replace('\u2013', '-'); 
formattedString = formattedString.Replace('\u2014', '-'); 
formattedString = formattedString.Replace('\u2015', '-'); 

即使有少更换为3,这很可能比做一个通所有这些替代品(我不打算效率较低做一个测试来找出需要多长时间,超过一定的数字,即使对于只有几个字符的字符串,使用单次传递也变得更有效率)。一种方法是:

StringBuilder sb = new StringBuilder(formattedString.length);//we know this is the capacity so we initialise with it: 
foreach(char c in formattedString) 
    switch(c) 
    { 
    case '\u2013': case '\u2014': case '\u2015': 
     sb.Append('-'); 
    default: 
     sb.Append(c) 
    } 
formattedString = sb.ToString(); 

(另一种可能性是,以检查是否(int)c >= 0x2013 && (int)c <= 0x2015但在分行数目减少是小的,不着边际的话你最期待的人物都没有数值接近对方)。使用各种变体(例如,如果formattedString要在某个时刻输出到流中,最好在获取每个最终字符时进行,而不是再次缓冲)。

请注意,此方法在您的搜索中不处理多字符串,但可以在输出中使用字符串,例如我们可能包括:

case 'ß': 
    sb.Append("ss"); 

现在,这是比以前更有效率,但一定数量的更换情况后,仍然变得很困难。它还涉及许多分支机构,这些分支机构都有自己的绩效问题

让我们考虑一下相反的问题。假设您想转换仅来自US-ASCII范围的来源的字符。你将只有128个可能的字符,所以你的方法可能是:

char[] replacements = {/*list of replacement characters*/} 
StringBuilder sb = new StringBuilder(formattedString.length); 
foreach(char c in formattedString) 
    sb.Append(replacements[(int)c]); 
formattedString = sb.ToString(); 

现在,这是不使用Unicode,其中有超过范围会从0到1114111.然而分配109,000个字符的实际,没准你关心的角色不仅比这个小得多(如果你真的关心很多情况,你会想要上面给出的方法),而且在相对受限的块中。

还可以考虑如果你不特别关心任何代理人(我们会在后面介绍)。那么,大多数人物你根本不关心,那么,让我们来看看这个:

char[] unchanged = new char[128]; 
for(int i = 0; i != 128; ++i) 
    unchanged[i] = (char)i; 
char[] error = new string('\uFFFD', 128).ToCharArray(); 
char[] block0 = (new string('\uFFFD', 13) + "---" + new string('\uFFFD', 112)).ToCharArray(); 

char[][] blocks = new char[8704][]; 
for(int i = 1; i != 8704; ++i) 
    blocks[i] = error; 
blocks[0] = unchanged; 
blocks[64] = block0; 

/* the above need only happen once, so it could be done with static members of a helper class that are initialised in a static constructor*/ 

StringBuilder sb = new StringBuilder(formattedString.Length); 
foreach(char c in formattedString) 
{ 
    int cAsI = (int)c; 
    sb.Append(blocks[i/128][i % 128]); 
} 
string ret = sb.ToString(); 
if(ret.IndexOf('\uFFFD') != -1) 
    throw new ArgumentException("Unconvertable character"); 
formattedString = ret; 

之间是否是更好地测试在最后一气呵成的uncovertable字符(如上)的余额或每个根据这种情况发生的可能性,转换会有所不同。如果你能确定(由于你的数据的知识),它肯定会更好,并且可以删除该检查 - 但你必须确实确实是

这里的好处是,虽然我们使用查找方法,但我们只占用384个字符的内存来保存查找(还有一些更多用于数组开销),而不是109,000个字符'价值。其中的块的最佳尺寸根据您的数据而不同(也就是您想要制作的替代品),但假设存在彼此相同的块往往会成立。

现在,最后,如果您关心在.NET中内部使用的UTF-16中表示为代理对的“星座飞机”中的某个角色,或者如果您关心将某些多字符字符串替换为一种特殊的方式?

在这种情况下,您可能必须至少在开关中读取一个或更多字符(如果在大多数情况下使用块方法,则可以使用不可转换的情况来指示此类工作是需要)。在这种情况下,可能需要转换为US-ASCII,然后从System.Text.Encoding以及EncoderFallbackEncoderFallbackBuffer的自定义实现转换回US-ASCII,并在那里处理它。这意味着大多数转换(明显的情况)将为您完成,而您的实现只能处理特殊情况。

4

您可以维护一个查找表,将问题字符映射到替换字符。为了提高效率,您可以使用字符数组来防止大量使用string.Replace的中间字符串流失。

例如:

var lookup = new Dictionary<char, char> 
{ 
    { '`', '-' }, 
    { 'இ', '-' }, 
    //next pair, etc, etc 
}; 

var input = "blah இ blah ` blah"; 

var r; 

var result = input.Select(c => lookup.TryGetValue(c, out r) ? r : c); 

string output = new string(result.ToArray()); 

或者,如果你想要的非ASCII字符范围毯治疗:

string output = new string(input.Select(c => c <= 127 ? c : '-').ToArray()); 
3

不幸的是,因为你做你的数据中一堆特定变换,你可能需要通过替换来做到这些。

这就是说,你可以做一些改进。

  1. 如果这很常见,并且字符串很长,那么将它们存储在一个StringBuilder而不是一个字符串中将允许就地替换这些值,这可能会改进这些内容。
  2. 您可以将来自和去往的转换字符存储在字典或其他结构中,并以简单的循环执行这些操作。
  3. 您可以在运行时从配置文件加载“from”和“to”字符,而不必对每个转换操作进行硬编码。后来,当更多这样的请求时,你不需要改变你的代码 - 它可以通过配置来完成。
0

如果他们全部换成相同的字符串:

formattedString = string.Join("-", formattedString.Split('\u2013', '\u2014', '\u2015')); 

foreach (char c in "\u2013\u2014\u2015") 
    formattedString = formattedString.Replace(c, '-'); 
相关问题