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

Com*_*ain 3 .net c# unicode 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), "-");
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

Jon*_*nna 7

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

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

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

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

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

formattedString = formattedString.Replace('\u2013', '-');
formattedString = formattedString.Replace('\u2014', '-');
formattedString = formattedString.Replace('\u2015', '-');
Run Code Online (Sandbox Code Playgroud)

即使是用尽可能少的替代品为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();
Run Code Online (Sandbox Code Playgroud)

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

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

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

case 'ß':
  sb.Append("ss");
Run Code Online (Sandbox Code Playgroud)

现在,这比以前更有效,但在一定数量的替换案例后仍然变得难以处理.它还涉及许多分支机构,它们都有自己的性能问题.

让我们考虑一下相反的问题.假设您想要转换仅在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();
Run Code Online (Sandbox Code Playgroud)

现在,这对于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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


Tim*_*oyd 6

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

或者,如果您想要对非ASCII范围字符进行全面处理:

string output = new string(input.Select(c => c <= 127 ? c : '-').ToArray());
Run Code Online (Sandbox Code Playgroud)