Unicode 字符串逆序

字符串的逆序是个非常简单的算法,可以直接使用一层循环搞定,或者下面一句代码。

str = new string(str.Reverse().ToArray());

但是对于 Unicode 字符串来说,这种方法并不完全正确,因为 Unicode 里有复合字符和代理项对这两种特殊的东西。

复合字符是后跟一个或多个组合字符的基本字符,也就是说,有些符号并不是由单一的一个 char 来表示的,而是由一个基本字符后跟多个组合字符组成的。例如字符 'ë',它的 Unicode 编码是 \u00EB,但是它同样也可以使用 \u0065\u0308 来表示,其中 \u0065 对应字符 'e',\u0308 则是组合字符(表示 e 上的两点),如图 1 所示。这时候就需要用两个 char 来表示一个字符,而且它们的相对顺序不能被改变。组合字符在表示重音和数学符号时还是非常有用的。

图1 复合字符示例

代理项对是为了用 utf-16 表示 Unicode 基本多语言平面 (BMP) 以外的字符。例如符号 \U0001D160,在 C# 中是用两个 char \uD834\uDD60 表示的,其中 \uD834 是高代理项,\uDD60 是低代理项。

因此,要想更加完美的对 Unicode 字符串进行逆序,需要保证复合字符和代理项对的顺序。还好 C# 提供了 TextElementEnumerator 类来枚举字符串的文本元素,这样就不需要自己去考虑 Unicode 的具体编码方式了。

具体的实现还是一次循环,对于普通字符还是直接对 char 进行逆序,仅当遇到复合字符或代理项对时,才使用 TextElementEnumerator 进行枚举,并以文本元素为单位进行逆序。我了解有 TextElementEnumerator 这个类,也是当初在看 Microsoft.VisualBasic.Strings.StrReverse 方法的源代码才发现的,微软自己的类库考虑的的确比较全面。

using System.Globalization;

namespace Cyjb {
	/// <summary>
	/// 提供 <see cref="System.String"/> 类的扩展方法。
	/// </summary>
	public static class StringExt {
		/// <summary>
		/// 返回指定字符串的字符顺序是相反的字符串。
		/// </summary>
		/// <param name="str">字符反转的字符串。</param>
		/// <returns>字符反转后的字符串。</returns>
		/// <remarks>参考了 Microsoft.VisualBasic.Strings.StrReverse 方法的实现。</remarks>
		public static string Reverse(this string str) {
			if (string.IsNullOrEmpty(str)) {
				return string.Empty;
			}
			int len = str.Length;
			int end = len - 1;
			int i = 0;
			char[] strArr = new char[len];
			while (end >= 0) {
				switch (char.GetUnicodeCategory(str[i])) {
					case UnicodeCategory.Surrogate:
					case UnicodeCategory.NonSpacingMark:
					case UnicodeCategory.SpacingCombiningMark:
					case UnicodeCategory.EnclosingMark:
						// 字符串中包含组合字符,翻转时需要保证组合字符的顺序。
						// 为了能够包含基字符,回退一位。
						if (i > 0) {
							i--;
							end++;
						}
						TextElementEnumerator textElementEnumerator = StringInfo.GetTextElementEnumerator(str, i);
						textElementEnumerator.MoveNext();
						int idx = textElementEnumerator.ElementIndex;
						while (end >= 0) {
							i = idx;
							if (textElementEnumerator.MoveNext()) {
								idx = textElementEnumerator.ElementIndex;
							} else {
								idx = len;
							}
							for (int j = idx - 1; j >= i; strArr[end--] = str[j--]) ;
						}
						goto EndReverse;
				}
				// 直接复制。
				strArr[end--] = str[i++];
			}
		EndReverse:
			return new string(strArr);
		}
	}
}

关于 Unicode 的更多资料,可以参考《循序渐进全球化:支持 Unicode》。以上的字符串逆序也并不一定是完美的解决方案,不过条件所限,只能这样了。

代码可见 Cyjb.StringExt 类中的 Reverse 方法。

posted @ 2012-11-11 15:35  CYJB  阅读(2990)  评论(0编辑  收藏  举报
Fork me on GitHub