簡體   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