手记

初级字典树查找在 Emoji、关键字检索上的运用 Part-1

前言

通常用户自行修改资料是很常见的需求,我们规定昵称长度在2到10之间。假设用户试图使用表情符号 ‍‍‍‍作为用户名,请求是否合法?

打开浏览器控制台,输入 '‍‍‍'.length,打印结果是11。

公司项目涉及内容打印的,之前将 Emoji 显示成乱码、框框是家常便饭,而且手机和浏览器、打印物各种不一致也相当折磨人。硬头皮阅读 unicode.org/emoji ,使用哈希查找暂解决了问题。

年前项目遇到敏感词过滤的需求,各种参考,结合之前的 Emoji 方案,方才有桃花源 “复行数十步,豁然开朗”的感悟,解决方案得到了升级。

以下内容是关于字典树-TrieTree的初级使用,并运用到 Emoji 定位查找和敏感词过滤的实际过程。


Unicode

对于我们程序员,Emoji 带来了诸多问题

  • 长度是怎样的?

  • 如何在各种平台显示一致?

解决这些问题不可能脱离 Unicode 字符来谈。

当我们谈论 Unicode 时,我们在说什么?

由于 JavaScript 只能处理 UCS-2 编码,造成所有字符在这门语言中都是2个字节,如果是4个字节的字符,会当作两个双字节的字符处理。JavaScript 的字符函数都受到这一点的影响,无法返回正确结果。

在阅读完以上资料后,想必前面的两个问题有了初步概念。以下是 Unicode 字符"?"在部分编程语言及版本中的体现。

编程语言字符集编码字符"?"的字面量
C#UnicodeUTF-16"\ud834\udf06"
JavaUnicodeUTF-16"\ud834\udf06"
ECMAScript 5UnicodeUCS-2"\ud834\udf06"
ECMAScript 6UnicodeUCS-2, UTF-16"\ud834\udf06", "\u{1d306}"
Python ???u'\U0001d306'

概括来说,UTF-16使用一组规则扩充了字符集。

  • 如果字符编码U小于0x10000,也就是十进制的0到65535之内,则直接使用两字节表示;

  • 如果字符编码U大于0x10000,由于UNICODE编码范围最大为0x10FFFF,从0x10000到0x10FFFF之间 共有0xFFFFF个编码,也就是需要20个bit就可以标示这些编码。用U'表示从0-0xFFFFF之间的值,将其前 10 bit作为高位和16 bit的数值0xD800进行 逻辑or 操作,将后10 bit作为低位和0xDC00做 逻辑or 操作,这样组成的 4个byte就构成了U的编码。

部分编程语言对 4字节 Unicode 的支持

Java

String str = "\ud834\udf06";
System.out.printf("str: %s, length: %d", str, str.length());// str: ?, length: 2

C#

 mString str = "\ud834\udf06";
Console.WriteLine("str: {0}, length: {1}", str, str.Length);// str: ?, length: 2

JavaScript

> let str = "\ud834\udf06";
> str
< "?"> console.log("str: %s, length: %d", str, str.length);
  str: ?, length: 2

Python 3

>>> s = "\ud834\udf06">>> s'\ud834\udf06'>>> len(s)2

Python 2

>>> s = "\ud834\udf06">>> s'\\ud834\\udf06'>>> len(s)12>>> s = u'\ud834\udf06' >>> su'\U0001d306'>>> len(s)2

多数的编程语言的"字符串长度"表达的是"字符串占用字节的长度"。可视字符的长度计算和检索需要先将字节序列转化为 Unicode 字符序列。采用UTF-16 的编程语言有能力能够理解上述规则,但由于历史问题等基于 UCS-2 的 ECMAScript 5 及 Python2 就悲剧了。

C# Char.IsHighSurrogate 和 StringInfo

//获取 unicode 码点public static IEnumerable<Int32> CodePoints(this String s) {  for (int i = 0; i < s.Length; ++i) {    yield return Char.ConvertToUtf32(s, i);    if (Char.IsHighSurrogate(s, i))
      i++;
  }
}   

public static IEnumerable<String> TextElements(String s) {  var enumerator = StringInfo.GetTextElementEnumerator(s);  while (enumerator.MoveNext()) {    yield return enumerator.GetTextElement();
  }
}

ECMAScript 6 String.prototype.codePointAt(index: number)

需要注意的是,对于4字节码点字符,如果参数大于Unicode字符数时,String.prototype.codePointAt 函数仍然生效但退化成了 String.prototype.charCodeAt 的实现。

故不能简单实现成 let codePoints = s => Array.from([...s].keys()).map(i => s.codePointAt(i));

let s = '‍‍';let codePoints = s => Array.from([...s].keys()).map(i => s.codePointAt(i));
codePoints(s)//[128104, 56424, 8205, 128105, 56425] ERROR!!!

正确的做法

let s = '‍‍';let codePoints = s => [...s].length === 1 
  ? Array.from([...s].keys()).map(i => s.codePointAt(i)) 
  : Array.prototype.concat.call(...[...s].map(codePoints));
codePoints(s)//(5) [128104, 8205, 128105, 8205, 128102]

Emoji

Emoji 最早在日本兴起,然后由 Apple 引入,目前是国际标准,见于Unicode® Emoji 。这个过程带来了各种历史遗留问题(后边会提提及),而Emoji 本身也在持续发展中,今天的资料可能变成明日黄花。

有了对 Unicode 的科普在前,我们现在知道 Emoji 只是 Unicode 字符或序列,文本渲染引擎遇到它们时进行解析和替换成自有实现。

  • 部分 Emoji 可以用2字节字符表示

  • 部分 Emoji 可以用4字节字符表示

  • 部分 Emoji 可以是一套 Unicode 字符组合

  • 部分 Emoji 是其他 Emoji 的组合,可能存在退化方案

略微提及,macOS 和 Android 分别使用的解决方案关键字是 AppleColorEmoji 和 NotoColorEmoji`,涉及TTF字休编程等,如有需要请自行搜索。

由此可见,Emoji 长度虽然确定但不能目测;如何显示是文本渲染引擎的工作,但不同的平台、 浏览器、 厂商甚至各个版本之间都有巨大的差异。

长度是怎样的?

探究 emoji 字符长度 有一段代码演示了 Emoji 字符长度的表现。

// neutral family// U+1F46A// length: 2> // ZWJ sequence: family (man, woman, boy)// U+1F468 + U+200D + U+1F469 + U+200D + U+1F466// ‍ + U+200D + ‍ + U+200D + // length: 8> ‍‍‍// ZWJ sequence: family (woman, woman, girl)// U+1F469 + U+200D + U+1F469 + U+200D + U+1F467// ‍ + U+200D + ‍ U+200D + // length: 8> ‍‍‍// ZWJ sequence: family (woman, woman, girl, girl)// U+1F469 + U+200D + U+1F469 + U+200D + U+1F467 + U+200D + U+1F467// ‍ + U+200D + ‍ + U+200D + ‍ + U+200D + // length: 11> ‍‍‍‍

这段文本可能因为浏览器版本等原因看到表情序列而不是组合,故我在 Chrome 下对显示效果作了截图

如何在各种平台显示一致?

Twitter 对 Emoji 跨平台一致显示的解决方案在 twitter/twemoji。它有以下问题:

  • 按年度月份更新, 像框中间有数字的 emoji 字符 '\u0031\ufe0f\u20e3' 还不支持

  • 以其 CDN 资源作为结果输果.

我们想知道一段文本里边有什么 Emoji,在哪里,怎样替换,怎样自定义显示,需要更多的掌控。

原文出处:https://www.cnblogs.com/Jusfr/p/9559635.html

0人推荐
随时随地看视频
慕课网APP