繁体   English   中英

是否有更优雅的方式将Unicode更改为Ascii?

[英]Is there a more elegant way to change Unicode to Ascii?

我看到了很多问题,你有一些模糊的unicode字符,有点像某个ascii字符,需要在运行时因任何原因进行转换。

在这种情况下,我试图导出到csv。 已经对dash,emdash,endash和hbar使用了一个讨厌的修复程序,我刚收到了一个新的'''请求。 除了另一个讨厌的解决方案还有另一种更好的方法吗?

这是我现在所拥有的......

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

有任何想法吗?

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

不过,我们当然可以改善一切。 哪种方法最有效将取决于需要进行的更改的数量(以及要更改的字符串的大小,尽管通常最好假设这个或者可能非常大)。

在一个替换字符中,到目前为止使用的方法 - 使用.Replace是优越的,但我将char.ConvertFromUtf32(8211)替换为"\–" 对性能的影响可以忽略不计,但它更具可读性,因为在U + 2013中引用十六进制字符比在十进制表示法中更常见(当然char.ConvertFromUtf32(0x2013)在那里具有相同的优势,但没有优势只使用char表示法)。 (也可以将'–'直接放入代码中 - 在某些情况下更具可读性,但在这方面它看起来与 - , - 或 - 对读者来说差不多。

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

将此方法应用于您的代码将变为:

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

即使只有3的替换很少,这可能比在一次传递中完成所有这样的替换效率低一些(我不打算进行测试以找出为什么formattedString需要多长时间,超过一定数量它即使对于只有几个字符的字符串,使用单个传递也会变得更有效率。 一种方法是:

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

(另一种可能性是检查if (int)c >= 0x2013 && (int)c <= 0x2015但是分支数量的减少很小,如果你寻找的大多数字符在数值上彼此不相近则无关紧要)。

使用各种变体(例如,如果formatString将在某个时刻输出到流,则最好在获得每个最终字符时这样做,而不是再次缓冲)。

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

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;

在最后一次(如上所述)或每次转换中是否更好地测试不可动摇的角色之间的平衡取决于这种情况发生的可能性。 如果您可以确定(由于您的数据知识)它不会,并且可以删除该检查,显然会更好 - 但您必须非常确定。

这里的优点是,当我们使用查找方法时,我们只占用384个字符的内存来保存查找(还有一些用于数组开销),而不是109,000个字符。 其中块的最佳大小根据您的数据而变化(即,您想要进行哪些替换),但假设存在彼此相同的块则倾向于保持不变。

现在,最后,如果您关心“星体平面”中的一个字符,它在.NET内部使用的UTF-16中表示为代理对,或者您是否关心以特定方式替换某些多字符串?

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

您可以维护一个查找表,将问题字符映射到替换字符。 为了提高效率,您可以处理字符数组,以防止大量中间字符串流失,这是使用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());

不幸的是,鉴于您在数据中进行了大量特定的转换,您可能需要通过替换来完成这些转换。

话虽这么说,你可以做一些改进。

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

如果它们全部替换为相同的字符串:

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

要么

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM