请找一个例句:
(S1 (S (S (NP (NP (NP (NN Activation)) (PP (IN of) (NP (NN protein) (NN kinase) (NN C)))) (CC and) (NP (NP (NN elevation)) (PP (IN of) (NP (NN cAMP))))) (VP (VBP interact) (ADVP (RB synergistically)) (S (VP (TO to) (VP (VB raise) (NP (NP (NP (NP (NN c-Fos)) (CC and) (NP (NN AP-1))) (NN activity)) (PP (IN in) (NP (NN Jurkat) (NNS cells))))))))) (. .)))
目标是从这句话创建一棵树;叶子是单词,中间节点是词性标签,根是S1。括号表示句子中包含的短语的范围;它们不需要包含在树中。
实现上述目标的数据结构的良好组合是什么,您是否也能够共享支持您的建议的伪代码?
我想到了一个HashMap和ArrayList,但我很困惑如何实际开始实现。只是说这个逻辑在这一点上并不直观。您的建议将不胜感激。
感谢。
答案 0 :(得分:3)
这种事情的基本方法是将 lex 字符串转换为标记的序列,然后解析该字符串成为什么的称为抽象语法树。这是一个很大的主题,但非常简短:
Lexing 意味着将您的字符串分解为不同的逻辑令牌。在您的情况下,您可能只想将序列分为开括号和闭括号以及标签。所以你的令牌是“(”,“)”之一,或者不是那个非空白字符序列。
解析意味着读取该字符序列并从中构建树结构。
首先你需要一个树结构:在你的情况下,它可能是一个由Sentence
组成的数据结构,它由一个词性标签和一个可以是单词或子句的对象列表组成。 (我假设这里没有有趣的结构:如果你知道NN只能包含单词而NP只能包含子句或类似的东西,那么你可以在这里建立一个更丰富的树结构。)
接下来,您需要将令牌解析为此树。执行此操作的最简单方法非常简单:例如,它看起来像这里你可以编写一个函数`parse(List tokens),它希望第一个标记是一个开括号,第二个标记是一个标签,然后递归从序列中消耗标记,直到遇到一个近括号标记。
这些主题是大型书籍和许多图书馆等的主题,但希望这将使您初步了解如何解决问题。
答案 1 :(得分:1)
我最近在Python中做过类似的事情。我将仅讨论有关数据结构的部分:
每个词性都是词性列表(非终端),或者只是一个词(终端)。所以你可以创建类似的东西:
enum Type {
Sentence,
NounPhrase,
VerbPhrase,
Conjunction,
...
};
interface PartOfSpeech { };
class NonTerminal implements PartOfSpeech {
Type type;
List<PartOfSpeech> subparts;
};
class Terminal implements PartOfSpeech {
String word;
};
这是相对无类型的:你的程序只需要构造这些的有效结构(例如,没有使用由句子列表组成的VerbPhrase - 你可以做到但是它毫无意义!)。
替代路线是定义更明确的类型系统。因此,您可以为每种类型的词性定义类,例如
class VerbPhrase {
Verb verb;
AdverbPhrase adverb; /* optional */
...
};
由于有多种方法可以制作动词短语,因此您可以为每种类型添加类:
interface VerbPhrase { };
class IntransitiveVerbPhrase implements VerbPhrase {
Verb verb;
AdverbPhrase adverb; /* optional */
};
class TransitiveVerbPhrase implements VerbPhrase {
Verb verb;
AdverbPhrase adverb; /* optional */
NounPhrase obj;
};
等等。 Java级别的最佳类型显式性可能在一开始就不明显。首先写一些处理简单句子的东西,然后看看它的感受。
在我的情况下,我为每个词性类型创建了类,尽管每个类都继承自Terminal或Nonterminal。然后我有关于如何构建每种类型的规则。对于其中一些人来说,这有点混乱,例如。
add_rule(NounPhrase, [Noun])
add_rule(NounPhrase, [RelativeNounWord])
add_rule(NounPhrase, [Noun, AdjectiveClause])
add_rule(NounPhrase, [Article, Noun])
add_rule(NounPhrase, [Article, Adjectives, Noun])
add_rule(NounPhrase, [Article, Noun, AdjectiveClause])
add_rule(NounPhrase, [Article, Adjectives, Noun, AdjectiveClause])
add_rule(NounPhrase, [Adjectives, Noun])
add_rule(NounPhrase, [Adjectives, Noun, AdjectiveClause])
add_rule(NounPhrase, [Article, Adjectives, Noun])
...
这是代码,说“NounPhrase是一个名词。或者,它是一个RelativeNoun。或者,它是一个名词后跟一个AdjectiveClause。或者等等”。有一个通用的解析器,它试图将规则应用于单词列表,直到它得到一棵树。 (您可以在http://code.google.com/p/ejrh/source/browse/trunk/ircbot/nl.py看到凌乱且未记录的代码。)
这里有一定数量的组合爆炸。它可以通过引入新类型的词性来改进,或者只是将它的某些部分作为可选的:对于Article / Adjectives / AdjectiveClause / etc的每种可能组合都有规则。出现/缺席,您可以将它们作为可选项。
答案 2 :(得分:1)
这是一个非常天真的实现,可以帮助您开始使用一些想法。它没有基于词性的打字,也没有强制执行任何超出我从单个例子中推断的规则。
class Node {
String partOfSpeech;
Node(String partOfSpeech) {
this.partOfSpeech = partOfSpeech;
}
String toString() {
return "(" + partOfSpeech + " " + getInternalString() + ")";
}
abstract String getInternalString(); // could use a better name
abstract String toSentence();
}
class Word extends Node {
String word;
Word(String partOfSpeech, String word) {
super(partOfSpeech);
this.word = word;
}
String getInternalString() {
return word;
}
String toSentence() {
return word;
}
}
class Phrase extends Node {
List<Node> children;
Phrase(String partOfSpeech) {
super(partOfSpeech);
this.children = new ArrayList<Node>();
}
add(Node child) {
children.add(child);
}
String getInternalString() {
return combineChildren(false);
}
String toSentence() {
return combineChildren(true);
}
private String combineChildren(boolean sentence) {
StringBuilder result = new StringBuilder();
for (Node child : children) {
sentenct.append(sentence ? child.toSentence() : child.toString()).append(' ');
}
return result.substring(0, result.length() - 1);
}
}