我正在寻找一种有效的数据结构/算法来存储和搜索基于音译的单词查找(比如google do:http://www.google.com/transliterate/但我并不是想尝试使用google音译API)。不幸的是,我正在尝试的自然语言没有实现任何soundex,所以我自己。
对于一个开源项目,我现在使用普通数组存储单词列表并动态生成正则表达式(基于用户输入)以匹配它们。它工作正常,但正则表达式太强大或资源密集,超出了我的需要。例如,如果我尝试将其移植到手持设备上,我担心这种解决方案会耗尽太多电池,因为使用正则表达式搜索数千个单词的成本太高。
对于复杂语言,必须有更好的方法来实现这一点,拼音输入法如何工作?关于从哪里开始的任何建议?
提前致谢。
编辑:如果我理解正确,这是@Dialecticus建议的 -
我想从语言1 音译,其中包含3个字符a,b,c
到语言2 ,其中包含6个字符p,q,r,x,y,z
。由于每种语言拥有的字符数量和手机数量不同,通常无法定义一对一的映射。
让我们假设这里是我们的关联数组/音译表:
a -> p, q
b -> r
c -> x, y, z
对于 Language2 :
,我们在普通数组中也有一个有效的单词列表...
px
qy
...
如果用户输入ac
,则在音译步骤1之后,可能的组合将变为px, py, pz, qx, qy, qz
。在步骤2中,我们必须在有效的单词列表中进行另一次搜索,并且除了{之外必须消除其中的所有人{1}}和px
。
我目前所做的与上述方法没有什么不同。我没有使用音译表进行可能的组合,而是构建正则表达式qy
并将其与我的有效单词列表进行匹配,后者提供输出[pq][xyz]
和px
。
我很想知道是否有更好的方法。
答案 0 :(得分:4)
根据我的理解,你有一个字母表中的输入字符串S(我们称之为A1),你想将它转换为字符串S',它是另一个字母表A2中的等价物。实际上,如果我理解正确,你想要生成一个输出字符串的列表[S'1,S'2,...,S'n],它可能等同于S.
我想到的一种方法是,A2中有效单词列表中的每个单词都会生成A1中与之匹配的字符串列表。使用编辑中的示例,我们有
px->ac
qy->ac
pr->ab
(为了清楚起见,我添加了一个额外的有效字pr
)
现在我们知道可能的一系列输入符号将始终映射到有效的单词,我们可以使用我们的表来构建Trie。
每个节点将保存一个指向A2中有效字列表的指针,该列表映射到A1中的符号序列,这些符号构成从Trie根到当前节点的路径。
因此,对于我们的例子,Trie看起来像这样
Root (empty)
| a
|
V
+---Node (empty)---+
| b | c
| |
V V
Node (px,qy) Node (pr)
从根节点开始,当符号被消耗时,转换从当前节点到其子节点标记有消耗的符号,直到我们读取整个字符串。如果在任何时候没有为该符号定义转换,则输入的字符串在我们的trie中不存在,因此不会映射到目标语言中的有效单词。否则,在过程结束时,与当前节点关联的单词列表是输入字符串映射到的有效单词列表。
除了构建trie的初始成本(如果我们从不希望更改有效单词列表,可以预先构建trie),这需要输入长度为O(n)来查找列表映射有效单词。
使用Trie还具有以下优点:您还可以使用它来查找可以通过在输入的末尾添加更多符号(即前缀匹配)生成的所有有效单词的列表。例如,如果输入符号'a',我们可以使用trie找到所有可以以'a'开头的有效单词('px','qr','py')。但这样做并不像找到完全匹配那么快。
这是对解决方案的快速破解(使用Java):
import java.util.*;
class TrieNode{
// child nodes - size of array depends on your alphabet size,
// her we are only using the lowercase English characters 'a'-'z'
TrieNode[] next=new TrieNode[26];
List<String> words;
public TrieNode(){
words=new ArrayList<String>();
}
}
class Trie{
private TrieNode root=null;
public void addWord(String sourceLanguage, String targetLanguage){
root=add(root,sourceLanguage.toCharArray(),0,targetLanguage);
}
private static int convertToIndex(char c){ // you need to change this for your alphabet
return (c-'a');
}
private TrieNode add(TrieNode cur, char[] s, int pos, String targ){
if (cur==null){
cur=new TrieNode();
}
if (s.length==pos){
cur.words.add(targ);
}
else{
cur.next[convertToIndex(s[pos])]=add(cur.next[convertToIndex(s[pos])],s,pos+1,targ);
}
return cur;
}
public List<String> findMatches(String text){
return find(root,text.toCharArray(),0);
}
private List<String> find(TrieNode cur, char[] s, int pos){
if (cur==null) return new ArrayList<String>();
else if (pos==s.length){
return cur.words;
}
else{
return find(cur.next[convertToIndex(s[pos])],s,pos+1);
}
}
}
class MyMiniTransliiterator{
public static void main(String args[]){
Trie t=new Trie();
t.addWord("ac","px");
t.addWord("ac","qy");
t.addWord("ab","pr");
System.out.println(t.findMatches("ac")); // prints [px,qy]
System.out.println(t.findMatches("ab")); // prints [pr]
System.out.println(t.findMatches("ba")); // prints empty list since this does not match anything
}
}
这是一个非常简单的trie,没有压缩或加速,只适用于输入语言的小写英文字符。但它可以很容易地修改为其他字符集。
答案 1 :(得分:2)
我会在当时建立一个符号的音译,而不是当时的一个词。对于大多数语言,可以独立于单词中的其他符号来音译每个符号。您仍然可以将整个单词作为完整单词进行音译,但符号和异常的音译表肯定会小于所有现有单词的音译表。
音译表的最佳结构是某种associative array,可能使用hash tables。在C ++中有std::unordered_map
,在C#中你会使用Dictionary
。