如何从头开始编一个拼音输入法?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何从头开始编一个拼音输入法?相关的知识,希望对你有一定的参考价值。
需要学习输入法框架的 API;不同系统的输入法框架有较大差异,可能会涉及一些系统底层的细节;调试困难;Windows 的输入法以 DLL 注入方式加载,需要事先注册到系统,且不可运行时替换 (hot-swap);Mac OS 对输入法做超时控制,难以利用断点单步调试我的建议是,在理解输入法工作原理的基础上,先写一个逻辑纯粹的、在命令行上以 REPL (Read-eval-print loop) 方式工作的输入法原型。比方说,首先实现输入一串编码、查词典、输出查询结果。如果是做拼音输入法,可进一步在词典查询基础上完成词组、语句的转换。然後,再增加选字、编辑等功能键的处理逻辑。不可显示为字符的按键,可通过转义序列模拟,例如 `rime_api_console` 采用花括弧+IBus键名表示任意的按键:`shuruufaLeftLeftBackSpaceEnd`https://github.com/lotem/librime/blob/master/tools/rime_api_console.cc#L144这样做的好处是可以将测试用的按键序列保存为文本,通过输入重定向重放测试场景:```$ rime_api_console< test_key_sequence.txt```原型将输入法所需逻辑完全实现後,即可集成到真实的输入法框架,完善用户介面等等。最後,藉机宣传一下 RIME:RIME/中州韵输入法引擎,是一套跨平台的输入法算法框架,即在输入法与操作系统接口之上封装了一套输入法常用的算法和逻辑组件,允许用户以 YAML 文档格式自定义输入方案和码表,快速建立拼音、注音、五笔及类似形态的输入法。对於需要特殊逻辑的输入法,可通过编程扩展该框架,使开发者只须专注於相关逻辑。目前这一框架的不足是接口尚未完全定型、缺少开发文档。
参考技术A我觉得引擎是重中之重的地方。引擎出词分为两个部门:1 unigram 2 bigram 这两个部分。unigram不用解释,直接大量的文本统计,bigram是干什么用的呢?比如说:不用解释 0.89 这个就是一个bigram,在统计bigram的时候需要做平滑,这个地方需要注意,训练这个模型是关键,因为bigram统计完大约有上亿条,你需要筛选出400万的bigram对(这个地方是技术之所在)。模型统计好了,用户输入拼音串是简拼和全拼混合的,这个时候有两种做法,第一是将拼音转换为全拼(搜狗输入法,百度输入法,QQ输入法是这种设计),第二是直接简拼和全拼混合(这个时候词典得采用声母+韵母共同命中的方法查询)(WI输入法,阿里拼音都是这样的设计)然后就是所谓的拼音切分等流程。
参考技术B1. 首先是要做拼音切分,切分方案n种(前缀,声母韵母),比如nihao,切分成ni\'hao2.根据切分出来的出来的[ni\'hao],查出来候选词。这个怎么查,假设你已经有了一个词典,然后用查字典的方法,查到[你好][利好][拟好]等候选词。然后要排序是吧,哪个要放在第一位,哪个要放第二位什么的。3.the end
针对拼音切分,你需要存储所有的拼音吧。怎么存储这个拼音啊,一般习惯上用trie树去存储,顺便可以做拼音切分。当然假设你有一个词典,怎么通过切分出来的拼音去找词,依然可以用trie树的方案,去存储。至于排序,可以给每个词,比如[你好]给个权值10,[利好]给个8什么的。
如何从头开始创建/编写一个简单的 XML 解析器?
【中文标题】如何从头开始创建/编写一个简单的 XML 解析器?【英文标题】:How to create/write a simple XML parser from scratch? 【发布时间】:2011-09-08 13:06:03 【问题描述】:除了代码示例,我想知道英文中简化的基本步骤是什么。
一个好的解析器是如何设计的?我知道正则表达式不应该在解析器中使用,但是正则表达式在解析 XML 中的作用有多大?
推荐使用什么数据结构?我应该使用链表来存储和检索节点、属性和值吗?
我想学习如何创建一个 XML 解析器,以便我可以用 D 编程语言编写一个。
【问题讨论】:
事件驱动解析器或文档构建解析器? 不幸的是,谷歌搜索“文档构建解析器”只会导致这个问题。如果您创建一个答案,也许您可以解决事件驱动解析器和文档构建解析器之间的区别。 我会注意到没有像“简单 XML”这样的语言。如果您打算解析 XML,那么您的解析器应该解析所有 XML,而不仅仅是其中的一部分。原因很简单:今天您可能只需要“简单”的 XML,但明天,您的代码可能会被要求解析“真正的 XML”。 @JohnSaunders 我认为他的意思是一个简单的解析器,而不是简单的 XML。 【参考方案1】:解析器和节点列表之间是有区别的。解析器是接收一堆纯文本 XML 并尝试确定其中存在哪些节点的部分。然后是保存节点的内部结构。在该结构之上的层中,您可以找到 DOM,即文档对象模型。这是构成 XML 文档的嵌套节点结构。解析器只需要知道通用 DOM 接口即可创建节点。
我不会为此使用正则表达式作为解析器。我认为最好的办法就是逐个字符地遍历字符串并检查你得到的是否与你应该得到的匹配。
但是为什么不使用任何现有的 XML 解析器呢?编码数据有很多可能性。很多例外。而且,如果您的解析器不能全部管理它们,那么 XML 解析器的称号就几乎不值得了。
【讨论】:
【参考方案2】:解析器必须满足您输入语言的需求。在您的情况下,简单的 XML。关于 XML,首先要了解的是它是上下文无关的并且绝对没有歧义,所有内容都包裹在两个标记之间,这就是 XML 出名的原因:它易于解析。最后,XML 总是简单地用树形结构表示。如前所述,您可以简单地解析您的 XML 并同时执行代码,或者解析 XML,生成树,然后根据该树执行代码。
D 提供了一种非常有趣的方式来非常轻松地编写 XML 解析器,例如:
doc.onStartTag["pointlight"] = (ElementParser xml)
debug writefln("Parsing pointlight element");
auto l = new DistantLight(to!int(xml.tag.attr["x"]),
to!int(xml.tag.attr["y"]),
to!int(xml.tag.attr["z"]),
to!ubyte(xml.tag.attr["red"]),
to!ubyte(xml.tag.attr["green"]),
to!ubyte(xml.tag.attr["blue"]));
lights ~= l;
xml.parse();
;
【讨论】:
我从未听说过一种名为“简单 XML”的语言。你能提供一个链接吗?是国际标准吗? 简单的 XML 是指对于基于事件的解析器,用户需要向其传递一些函数(startNode(name,attrs)
、endNode(name)
和 someText(txt)
可能通过接口)并在传递文件时在需要时调用它们
解析器将有一个 while 循环,该循环将在读取到 <
和直到 >
之间交替,并正确转换为参数类型
void parse(EventParser p, File file)
string str;
while((str = file.readln('<')).length !=0)
//not using a rewritable buffer to take advantage of slicing
//but it's a quick conversion to a implementation with a rewritable buffer though
if(str.length>1)p.someText(str.chomp('<'));
str = file.readln('>');
str = str.chomp('>');
//split str in name and attrs
auto parts = str.split();
string name = parts[0];
string[string] attrs;
foreach(attribute;parts[1..$])
auto splitAtrr = attribute.split("=");
attrs[splitAtrr[0]] = splitAtrr[1];
if(str[0] == '/')p.endNode(name);
else
p.startNode(name,attrs);
if(str[str.length-1]=='/')p.endNode(name);//self closing tag
您可以在基于事件的解析器之上构建 DOM 解析器,每个节点所需的基本功能是 getChildren 和 getParent getName 和 getAttributes(构建时使用 setter ;))
具有上述方法的 dom 解析器的对象:
class DOMEventParser : EventParser
DOMNode current = new RootNode();
overrides void startNode(string name,string[string] attrs)
DOMNode tmp = new ElementNode(current,name,attrs);
current.appendChild(tmp);
current = tmp;
overrides void endNode(string name)
asser(name == current.name);
current = current.parent;
overrides void someText(string txt)
current.appendChild(new TextNode(txt));
当解析结束时,rootnode 将拥有 DOM 树的根
注意:我没有放任何验证码以确保xml的正确性
编辑:属性的解析有一个错误,而不是在空格上拆分,正则表达式更好
【讨论】:
【参考方案4】:如果您不知道如何编写解析器,那么您需要做一些阅读。掌握任何有关编译器编写的书(许多最好的书都是 30 或 40 年前写的,例如 Aho 和 Ullmann),并学习有关词法分析和语法分析的章节。 XML 本质上没有什么不同,只是词汇和语法阶段不像在某些语言中那样清楚地相互隔离。
提醒一句,如果您想编写一个完全符合标准的 XML 解析器,那么您将花费 90% 的精力在规范的晦涩角落处理边缘情况,处理诸如大多数 XML 用户所使用的参数实体之类的事情甚至不知道。
【讨论】:
很好奇,什么数据结构最适合这项任务?我的直觉说是一棵通用树,不知道 OP 是否也想从头开始构建它,他/她可能会参与一个漫长的项目。【参考方案5】:由于 D 与 Java 密切相关,可能会生成一个带有 ANTLR 的 XML 解析器(因为很可能已经有用于 ANTLR 的 XML EBNF 语法,然后您可以使用这些),然后转换生成的 Java 解析器代码为 D,可以选择吗?至少这会给你一个起点,然后你可以努力尝试专门为 D 优化代码......
至少 ANTLR 并不像许多人想象的那么难。在对它一无所知后,我通过观看this great set of screencasts on ANTLR 的 3-4 开始。
顺便说一句,我发现 ANTLRWorks 可以轻松使用(与截屏视频中使用的 Eclipse 插件相反......但截屏视频内容仍然适用)。
只是我的 0.02c。
【讨论】:
【参考方案6】:文档中的第一个元素应该是序言。这说明了 xml 版本、编码、文件是否是独立的,也许还有其他一些东西。序言以<?
开头。
序言之后是带有元数据的标签。特殊标签,如 cmets、doctypes 和元素定义应以 <!
开头。处理指令以<?
开头。这里可以有嵌套标签,因为<!DOCTYPE
标签可以在 dtd 样式的 xml 文档中包含 <!ELEMENT
和 <!ATTLIST
标签——请参阅 Wikipedia 以获得完整的示例。
应该只有一个***元素。它是唯一一个前面没有<!
或<?
的。***元素之后可能有更多元数据标签;先处理这些。
对于显式解析:首先识别标签——它们都以<
开头——然后确定它是什么类型的标签以及它的闭包是什么样的。 <!--
是一个注释标签,除了它的结尾之外,不能有--
。 <?
以 ?>
结尾。 <!
以 >
结尾。重复一遍:<!DOCTYPE
可以在其关闭之前嵌套标签,并且可能还有其他我不知道的嵌套标签。
一旦你找到一个标签,你就会想找到它的结束标签。先检查标签是否自动关闭;否则,找到它的闭包。
对于数据结构:我建议使用树结构,其中每个元素都是一个节点,每个节点都有一个索引/映射的子元素列表。
显然,一个完整的解析器需要更多的研究;我希望这足以让您入门。
【讨论】:
以上是关于如何从头开始编一个拼音输入法?的主要内容,如果未能解决你的问题,请参考以下文章