adding modules

git-svn-id: https://russianmorphology.googlecode.com/svn/trunk@49 d817d54c-26ab-11de-abc9-2f7d1455ff7a
This commit is contained in:
alexander.a.kuznetsov 2009-10-02 16:25:08 +00:00
parent 786ce92ae0
commit 710384987c
36 changed files with 221 additions and 695427 deletions

22
dictionary-reader/pom.xml Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<project>
<parent>
<artifactId>morpholgy</artifactId>
<groupId>org.apache.lucene.morpholgy</groupId>
<version>0.7-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.lucene.morpholgy</groupId>
<artifactId>dictionary-reader</artifactId>
<name>dictionary-reader</name>
<version>0.7-SNAPSHOT</version>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.apache.lucene.morpholgy</groupId>
<artifactId>morph</artifactId>
<version>0.7-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@ -14,9 +14,8 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.dictonary;
package org.apache.lucene.morpholgy.dictionary;
import org.apache.lucene.russian.morphology.RussianSuffixDecoderEncoder;
import java.io.BufferedReader;
import java.io.FileInputStream;
@ -72,7 +71,7 @@ public class DictonaryReader {
List<FlexiaModel> models = wordsFlexias.get(Integer.valueOf(wd[1]));
FlexiaModel flexiaModel = models.get(0);
if (models.size() > 0 && !ingnoredForm.contains(flexiaModel.getCode())) {
WordCard card = new WordCard(cleanString(flexiaModel.create(wordBase)), cleanString(wordBase), flexiaModel.getSuffix());
WordCard card = new WordCard(flexiaModel.create(wordBase), wordBase, flexiaModel.getSuffix());
for (FlexiaModel fm : models) {
card.addFlexia(fm);
}
@ -81,9 +80,6 @@ public class DictonaryReader {
}
}
private String cleanString(String s) {
return s.replace((char) (34 + RussianSuffixDecoderEncoder.RUSSIAN_SMALL_LETTER_OFFSET), (char) (6 + RussianSuffixDecoderEncoder.RUSSIAN_SMALL_LETTER_OFFSET));
}
private void sckipBlock(BufferedReader reader) throws IOException {
String s = reader.readLine();
@ -123,7 +119,7 @@ public class DictonaryReader {
System.out.println(line);
// flexiaModelArrayList.add(new FlexiaModel(fl[1], cleanString(fl[0].toLowerCase()), cleanString(fl[2].toLowerCase())));
}
if (fl.length == 2) flexiaModelArrayList.add(new FlexiaModel(fl[1], cleanString(fl[0].toLowerCase()), ""));
if (fl.length == 2) flexiaModelArrayList.add(new FlexiaModel(fl[1], fl[0].toLowerCase(), ""));
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.dictonary;
package org.apache.lucene.morpholgy.dictionary;
/**
* Represent inofrmation of how word form created form it imutible part.

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.dictonary;
package org.apache.lucene.morpholgy.dictionary;
import java.io.BufferedReader;
import java.io.FileInputStream;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.dictonary;
package org.apache.lucene.morpholgy.dictionary;
import java.io.BufferedReader;
import java.io.FileInputStream;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.dictonary;
package org.apache.lucene.morpholgy.dictionary;
import java.io.BufferedReader;
import java.io.FileInputStream;

View File

@ -14,15 +14,12 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology;
package org.apache.lucene.morpholgy.dictionary;
import org.apache.lucene.russian.morphology.dictonary.FlexiaModel;
import org.apache.lucene.russian.morphology.dictonary.GrammaReader;
import org.apache.lucene.russian.morphology.dictonary.WordCard;
import org.apache.lucene.russian.morphology.dictonary.WordProccessor;
import org.apache.lucene.russian.morphology.informations.Heuristic;
import org.apache.lucene.russian.morphology.informations.Morph;
import org.apache.lucene.morphology.Heuristic;
import org.apache.lucene.morphology.LetterDecoderEncoder;
import org.apache.lucene.morphology.Morph;
import java.io.IOException;
import java.util.*;
@ -33,6 +30,7 @@ public class StatiticsCollector implements WordProccessor {
private Map<Set<Heuristic>, Integer> ruleInverIndex = new HashMap<Set<Heuristic>, Integer>();
private List<Set<Heuristic>> rules = new ArrayList<Set<Heuristic>>();
private GrammaReader grammaReader;
private LetterDecoderEncoder decoderEncoder;
public StatiticsCollector(GrammaReader grammaReader) {
@ -40,6 +38,7 @@ public class StatiticsCollector implements WordProccessor {
}
public void proccess(WordCard wordCard) throws IOException {
wordCard = cleanWordCard(wordCard);
String normalStringMorph = wordCard.getWordsFroms().get(0).getCode();
String word = wordCard.getBase() + wordCard.getCanonicalSuffix();
if (word.contains("-")) return;
@ -56,6 +55,10 @@ public class StatiticsCollector implements WordProccessor {
}
}
private WordCard cleanWordCard(WordCard wordCard) {
return wordCard;
}
public void saveHeuristic() throws IOException {
@ -98,7 +101,7 @@ public class StatiticsCollector implements WordProccessor {
for (String key : inversIndex.keySet()) {
Set<Heuristic> currentSet = inversIndex.get(key);
if (!currentSet.equals(prevSet)) {
ints[count] = RussianSuffixDecoderEncoder.encodeToArray(key);
ints[count] = decoderEncoder.encodeToArray(key);
rulesId[count] = (short) ruleInverIndex.get(currentSet).intValue();
count++;
prevSet = currentSet;
@ -136,5 +139,9 @@ public class StatiticsCollector implements WordProccessor {
return length;
}
private String cleanString(String s) {
return decoderEncoder.cleanString(s);
//return s.replace((char) (34 + RussianSuffixDecoderEncoder.RUSSIAN_SMALL_LETTER_OFFSET), (char) (6 + RussianSuffixDecoderEncoder.RUSSIAN_SMALL_LETTER_OFFSET));
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.dictonary;
package org.apache.lucene.morpholgy.dictionary;
import java.util.ArrayList;
import java.util.List;
@ -53,4 +53,20 @@ public class WordCard {
public List<FlexiaModel> getWordsFroms() {
return wordsFroms;
}
public void setCanonicalFrom(String canonicalFrom) {
this.canonicalFrom = canonicalFrom;
}
public void setBase(String base) {
this.base = base;
}
public void setCanonicalSuffix(String canonicalSuffix) {
this.canonicalSuffix = canonicalSuffix;
}
public void setWordsFroms(List<FlexiaModel> wordsFroms) {
this.wordsFroms = wordsFroms;
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.dictonary;
package org.apache.lucene.morpholgy.dictionary;
import java.io.IOException;

23
morph/pom.xml Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<project>
<parent>
<artifactId>morpholgy</artifactId>
<groupId>org.apache.lucene.morpholgy</groupId>
<version>0.7-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.lucene.morpholgy</groupId>
<artifactId>morph</artifactId>
<name>morph</name>
<version>0.7-SNAPSHOT</version>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.informations;
package org.apache.lucene.morphology;
import java.io.Serializable;

View File

@ -0,0 +1,31 @@
/**
* Copyright 2009 Alexander Kuznetsov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.morphology;
public interface LetterDecoderEncoder {
public Integer encode(String string);
public int[] encodeToArray(String s);
public String decodeArray(int[] array);
public String decode(Integer suffixN);
public boolean checkCharacter(char c);
public String cleanString(String s);
}

View File

@ -1,14 +1,29 @@
package org.apache.lucene.russian.morphology.informations;
/**
* Copyright 2009 Alexander Kuznetsov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.morphology;
import org.apache.lucene.russian.morphology.RussianSuffixDecoderEncoder;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class LuceneMorph extends Morph{
public class LuceneMorph extends Morph {
LetterDecoderEncoder decoderEncoder;
public LuceneMorph(String fileName) throws IOException {
super(fileName);
@ -17,7 +32,7 @@ public class LuceneMorph extends Morph{
@Override
public List<String> getMorhInfo(String s) {
ArrayList<String> result = new ArrayList<String>();
int[] ints = RussianSuffixDecoderEncoder.encodeToArray(revertWord(s));
int[] ints = decoderEncoder.encodeToArray(revertWord(s));
int ruleId = findRuleId(ints);
for (Heuristic h : rules[rulesId[ruleId]]) {
result.add(h.transofrmWord(s));
@ -43,14 +58,14 @@ public class LuceneMorph extends Morph{
}
private Heuristic[] modeifyHeuristic(Heuristic[] heuristics){
private Heuristic[] modeifyHeuristic(Heuristic[] heuristics) {
ArrayList<Heuristic> result = new ArrayList<Heuristic>();
for(Heuristic heuristic:heuristics){
for (Heuristic heuristic : heuristics) {
boolean isAdded = true;
for(Heuristic ch:result){
for (Heuristic ch : result) {
isAdded = isAdded && !(ch.getActualNormalSuffix().equals(heuristic.getActualNormalSuffix()) && (ch.getActualSuffixLengh() == heuristic.getActualSuffixLengh()));
}
if(isAdded){
if (isAdded) {
result.add(heuristic);
}
}

View File

@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.informations;
package org.apache.lucene.morphology;
import org.apache.lucene.russian.morphology.RussianSuffixDecoderEncoder;
import java.io.BufferedReader;
import java.io.FileReader;
@ -30,6 +29,7 @@ public class Morph {
protected short[] rulesId;
protected Heuristic[][] rules;
protected String[] grammaInfo;
LetterDecoderEncoder decoderEncoder;
public Morph(String fileName) throws IOException {
@ -61,7 +61,7 @@ public class Morph {
public List<String> getMorhInfo(String s) {
ArrayList<String> result = new ArrayList<String>();
int[] ints = RussianSuffixDecoderEncoder.encodeToArray(revertWord(s));
int[] ints = decoderEncoder.encodeToArray(revertWord(s));
int ruleId = findRuleId(ints);
for (Heuristic h : rules[rulesId[ruleId]]) {
result.add(h.transofrmWord(s) + "|" + grammaInfo[h.getFormMorphInfo()]);

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology;
package org.apache.lucene.morphology;
public class SuffixToLongException extends RuntimeException {

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology;
package org.apache.lucene.morphology;
public class WrongCharaterException extends RuntimeException {

View File

@ -14,14 +14,14 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.analayzer;
package org.apache.lucene.morphology.analayzer;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.LowerCaseFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardFilter;
import org.apache.lucene.analysis.standard.StandardTokenizer;
import org.apache.lucene.russian.morphology.informations.LuceneMorph;
import org.apache.lucene.morphology.LuceneMorph;
import java.io.IOException;
import java.io.Reader;

View File

@ -14,16 +14,16 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.analayzer;
package org.apache.lucene.morphology.analayzer;
import org.apache.lucene.analysis.Token;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.russian.morphology.informations.LuceneMorph;
import org.apache.lucene.morphology.LuceneMorph;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.List;
public class RussianMorphlogyFilter extends TokenFilter {
@ -37,7 +37,7 @@ public class RussianMorphlogyFilter extends TokenFilter {
private List<String> stack = new ArrayList<String>();
private int index = 0;
private Token current = null;
private Token current = null;
/**
* Returns the next token in the stream, or null at EOS.

26
pom.xml
View File

@ -1,16 +1,19 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.lucene</groupId>
<artifactId>russian-morpholgy</artifactId>
<packaging>jar</packaging>
<groupId>org.apache.lucene.morpholgy</groupId>
<artifactId>morpholgy</artifactId>
<packaging>pom</packaging>
<version>0.7-SNAPSHOT</version>
<name>russian-morpholgy</name>
<name>morpholgy</name>
<url>http://maven.apache.org</url>
<scm>
<connection>scm:svn:http://russianmorphology.googlecode.com/svn/tags/russian-morpholgy-0.6</connection>
<developerConnection>scm:svn:https://russianmorphology.googlecode.com/svn/tags/russian-morpholgy-0.6</developerConnection>
<connection>scm:svn:http://russianmorphology.googlecode.com/svn/tags/russian-morpholgy-0.7</connection>
<developerConnection>scm:svn:https://russianmorphology.googlecode.com/svn/tags/russian-morpholgy-0.7
</developerConnection>
</scm>
@ -108,10 +111,10 @@
<header>etc/header.txt</header>
<excludes>
<exclude>**/*.txt</exclude>
<exclude>**/pom.xml</exclude>
</excludes>
<includes>
<include>**/src/**</include>
<include>**/pom.xml</include>
</includes>
</configuration>
@ -127,4 +130,9 @@
</plugins>
</build>
</project>
<modules>
<module>morph</module>
<module>dictionary-reader</module>
<module>russian</module>
</modules>
</project>

31
russian/pom.xml Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>morpholgy</artifactId>
<groupId>org.apache.lucene.morpholgy</groupId>
<version>0.7-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.lucene.morpholgy</groupId>
<artifactId>russian</artifactId>
<name>russian</name>
<version>0.7-SNAPSHOT</version>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.apache.lucene.morpholgy</groupId>
<artifactId>dictionary-reader</artifactId>
<version>0.7-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -14,12 +14,9 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology;
package org.apache.lucene.morphology.russian;
import org.apache.lucene.russian.morphology.dictonary.DictonaryReader;
import org.apache.lucene.russian.morphology.dictonary.FrequentyReader;
import org.apache.lucene.russian.morphology.dictonary.GrammaReader;
import org.apache.lucene.russian.morphology.dictonary.IgnoredFormReader;
import org.apache.lucene.morpholgy.dictionary.*;
import java.io.IOException;
import java.util.Set;

View File

@ -14,7 +14,11 @@
* limitations under the License.
*/
package org.apache.lucene.russian.morphology;
package org.apache.lucene.morphology.russian;
import org.apache.lucene.morphology.LetterDecoderEncoder;
import org.apache.lucene.morphology.SuffixToLongException;
import org.apache.lucene.morphology.WrongCharaterException;
import java.util.ArrayList;
@ -24,7 +28,7 @@ import java.util.ArrayList;
* Assumed that suffix contains only small russian letters and dash.
* Also assumed that letter <EFBFBD> and <EFBFBD> coinsed.
*/
public class RussianSuffixDecoderEncoder {
public class RussianSuffixDecoderEncoder implements LetterDecoderEncoder {
public static final int RUSSIAN_SMALL_LETTER_OFFSET = 1071;
static public int SUFFIX_LENGTH = 6;
public static final int EE_CHAR = 34;
@ -32,7 +36,7 @@ public class RussianSuffixDecoderEncoder {
public static final int DASH_CHAR = 45;
public static final int DASH_CODE = 33;
static public Integer encode(String string) {
public Integer encode(String string) {
if (string.length() > 6) throw new SuffixToLongException("Suffix length should not be greater then " + 12);
int result = 0;
for (int i = 0; i < string.length(); i++) {
@ -51,7 +55,7 @@ public class RussianSuffixDecoderEncoder {
return result;
}
static public int[] encodeToArray(String s) {
public int[] encodeToArray(String s) {
ArrayList<Integer> integers = new ArrayList<Integer>();
while (s.length() > 6) {
integers.add(encode(s.substring(0, 6)));
@ -67,7 +71,7 @@ public class RussianSuffixDecoderEncoder {
return ints;
}
static public String decodeArray(int[] array) {
public String decodeArray(int[] array) {
String result = "";
for (int i : array) {
result += decode(i);
@ -76,7 +80,7 @@ public class RussianSuffixDecoderEncoder {
}
static public String decode(Integer suffixN) {
public String decode(Integer suffixN) {
String result = "";
while (suffixN > 33) {
int c = suffixN % 34 + RUSSIAN_SMALL_LETTER_OFFSET;
@ -94,11 +98,15 @@ public class RussianSuffixDecoderEncoder {
return result;
}
static public boolean checkCharacter(char c) {
public boolean checkCharacter(char c) {
int code = 0 + c;
if (code == 45) return true;
code -= RUSSIAN_SMALL_LETTER_OFFSET;
if (code > 0 && code < 33) return true;
return false;
}
public String cleanString(String s) {
return s;
}
}

View File

@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.russian.morphology;
package org.apache.lucene.morphology.russian;
import org.apache.lucene.russian.morphology.informations.Heuristic;
import org.apache.lucene.russian.morphology.informations.Morph;
import org.apache.lucene.morphology.Heuristic;
import org.apache.lucene.morphology.Morph;
import java.io.IOException;
import java.util.Arrays;

View File

@ -1,87 +0,0 @@
/**
* Copyright 2009 Alexander Kuznetsov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.analayzer;
import org.apache.lucene.russian.morphology.RussianSuffixDecoderEncoder;
import java.io.*;
import java.util.Arrays;
public class SuffixHeuristic {
private long[] keys;
private long[] values;
public void readFromFile(String fileName) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(fileName));
readFromBufferedRreader(reader);
}
public SuffixHeuristic() throws IOException {
readFromResource();
}
public SuffixHeuristic(String fileName) throws IOException {
readFromFile(fileName);
}
public void readFromResource() throws IOException {
InputStream stream = this.getClass().getResourceAsStream("/org/apache/lucene/russian/morphology/russianSuffixesHeuristic.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream));
readFromBufferedRreader(bufferedReader);
}
private void readFromBufferedRreader(BufferedReader reader) throws IOException {
int size = Integer.valueOf(reader.readLine());
keys = new long[size];
values = new long[size];
for (int i = 0; i < size; i++) {
String[] s = reader.readLine().split(" ");
keys[i] = Long.valueOf(s[0]);
values[i] = Long.valueOf(s[1]);
}
}
public String getCanonicalForm(String form) {
int startSymbol = form.length() > RussianSuffixDecoderEncoder.SUFFIX_LENGTH ? form.length() - RussianSuffixDecoderEncoder.SUFFIX_LENGTH : 0;
String suffixS = form.substring(startSymbol);
if (!chechSuffix(suffixS)) return form;
Integer suffix = RussianSuffixDecoderEncoder.encode(suffixS);
int index = Arrays.binarySearch(keys, suffix);
if (index < -1) {
System.out.println(" " + form);
return form;
} else {
String nSuffix = RussianSuffixDecoderEncoder.decode((int) values[index]);
return startSymbol > 0 ? form.substring(0, startSymbol) + nSuffix : nSuffix;
}
}
private boolean chechSuffix(String suffix) {
for (int i = 0; i < suffix.length(); i++) {
if (!RussianSuffixDecoderEncoder.checkCharacter(suffix.charAt(i))) return false;
}
return true;
}
}

View File

@ -1,67 +0,0 @@
/**
* Copyright 2009 Alexander Kuznetsov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.russian.morphology;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class RussianSuffixDecoderEncoderTest {
@Test
public void testShouldCorretDecodeEncode() throws IOException {
InputStream stream = this.getClass().getResourceAsStream("/org/apache/lucene/russian/morphology/decoder-test-data.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
String s = bufferedReader.readLine();
while (s != null) {
String[] qa = s.trim().split(" ");
Integer ecodedSuffix = RussianSuffixDecoderEncoder.encode(qa[0]);
assertThat(RussianSuffixDecoderEncoder.decode(ecodedSuffix), equalTo(qa[1]));
s = bufferedReader.readLine();
}
}
@Test
public void testShouldCorretDecodeEncodeStringToArray() throws IOException {
InputStream stream = this.getClass().getResourceAsStream("/org/apache/lucene/russian/morphology/decoder-test-data-for-array.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
String s = bufferedReader.readLine();
while (s != null) {
String[] qa = s.trim().split(" ");
int[] ecodedSuffix = RussianSuffixDecoderEncoder.encodeToArray(qa[0]);
assertThat(RussianSuffixDecoderEncoder.decodeArray(ecodedSuffix), equalTo(qa[1]));
s = bufferedReader.readLine();
}
}
@Test(expected = SuffixToLongException.class)
public void shouldThrownExeptionIfSuffixToLong() {
RussianSuffixDecoderEncoder.encode("1234567890123");
}
@Test(expected = WrongCharaterException.class)
public void shouldThrownExeptionIfSuffixContainWrongCharater() {
RussianSuffixDecoderEncoder.encode("1");
}
}

View File

@ -1,68 +0,0 @@
/**
* Copyright 2009 Alexander Kuznetsov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.russian.morphology;
import org.apache.lucene.russian.morphology.informations.Morph;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class SpeedTest {
@Test
public void getTestOfSpeed() throws IOException {
Morph splitter = new Morph("sep.txt");
InputStream stream = Test.class.getResourceAsStream("/org/apache/lucene/russian/morphology/analayzer/russian-text.txt");
BufferedReader stream1 = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
String s = stream1.readLine().trim().toLowerCase();
for (String w : s.split(" ")) {
try {
System.out.println(w);
System.out.println(splitter.getMorhInfo(w));
} catch (WrongCharaterException e) {
}
}
// Long startTime = System.currentTimeMillis();
// RussianMorphlogyAnalayzer morphlogyAnalayzer = new RussianMorphlogyAnalayzer();
// System.out.println("To build analayzer take " + (System.currentTimeMillis() - startTime) + " ms.");
// InputStream stream = this.getClass().getResourceAsStream("/org/apache/lucene/russian/morphology/text.txt");
// BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
//
//
// final Token reusableToken = new Token();
//
// Token nextToken;
//
//
// startTime = System.currentTimeMillis();
// Integer count = 0;
// TokenStream in = morphlogyAnalayzer.tokenStream(null, reader);
// for (; ;) {
// nextToken = in.next(reusableToken);
// count++;
// if (nextToken == null) {
// break;
// }
//
// }
// System.out.println("It takes " + (System.currentTimeMillis() - startTime) + " ms. To proccess " + count + " words." );
}
}

View File

@ -1,61 +0,0 @@
/**
* Copyright 2009 Alexander Kuznetsov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.analayzer;
import org.junit.Test;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.Token;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.InputStream;
public class RussianMorphlogyAnalayzerTest {
@Test
public void shouldCorrectProccessText() throws IOException {
RussianMorphlogyAnalayzer morphlogyAnalayzer = new RussianMorphlogyAnalayzer();
InputStream stream = this.getClass().getResourceAsStream("/org/apache/lucene/russian/morphology/analayzer/russian-text.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
InputStream tokeStream = this.getClass().getResourceAsStream("/org/apache/lucene/russian/morphology/analayzer/token-of-russian-text.txt");
BufferedReader tokenReader = new BufferedReader(new InputStreamReader(tokeStream, "UTF-8"));
final Token reusableToken = new Token();
Token nextToken;
TokenStream in = morphlogyAnalayzer.tokenStream(null, reader);
for (; ;) {
nextToken = in.next(reusableToken);
if (nextToken == null) {
break;
}
System.out.println(nextToken.term());
// assertThat(nextToken.term(), equalTo(tokenReader.readLine().trim()));
}
}
}

View File

@ -1,38 +0,0 @@
/**
* Copyright 2009 Alexander Kuznetsov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.analayzer;
import org.junit.Test;
import java.io.IOException;
public class SuffixHeuristicTest {
@Test
public void testShouldDefineCorretCononicalWordForm() throws IOException {
// SuffixHeuristic suffixHeuristic = new SuffixHeuristic();
// InputStream stream = this.getClass().getResourceAsStream("/org/apache/lucene/russian/morphology/analayzer/suffix-heuristic-test-data.txt");
// BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
// String s = bufferedReader.readLine();
// while (s != null) {
// String[] qa = s.trim().split(" ");
// assertThat(suffixHeuristic.getCanonicalForm(qa[0]), equalTo(qa[1]));
// s = bufferedReader.readLine();
// }
}
}

View File

@ -1,46 +0,0 @@
/**
* Copyright 2009 Alexander Kuznetsov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.russian.morphology.utils;
import org.junit.Test;
public class UtilsTest {
@Test
public void testCompate() {
System.out.println((byte) 255);
//
// assertThat(Utils.compate((byte)3,(byte)2),equalTo(1));
// assertThat(Utils.compate((byte)2,(byte)3),equalTo(-1));
// assertThat(Utils.compate((byte)200,(byte)2),equalTo(1));
// assertThat(Utils.compate((byte)2,(byte)200),equalTo(-1));
// assertThat(Utils.compate((byte)255,(byte)254),equalTo(1));
// assertThat(Utils.compate((byte)254,(byte)255),equalTo(-1));
// assertThat(Utils.compate((byte)200,(byte)200),equalTo(0));
// assertThat(Utils.compate((byte)2,(byte)2),equalTo(0));
}
@Test
public void testStringTyByteArray() {
// Add your code here
}
@Test
public void testByteArrayToString() {
// Add your code here
}
}

View File

@ -1,13 +0,0 @@
тест тест
ёж еж
естера естера
что-то что-то
а а
яяяяяя яяяяяя
яяяя яяяя
аа аа
аааааа аааааа
аааааааааааа аааааааааааа
аааааааааааааааааа аааааааааааааааааа
ааааааааааааааааа ааааааааааааааааа
йфячыцувс йфячыцувс

View File

@ -1,8 +0,0 @@
тест тест
ёж еж
естера естера
что-то что-то
а а
яяяяяя яяяяяя
яяяя яяяя
аа аа

View File

@ -1,160 +0,0 @@
Морфологический анализатор для русского языка — это что-то заумное? Программа, которая приводит слово к начальной форме, определяет падеж, находит словоформы — непонятно, как и подступиться? А на самом деле все не так и сложно. В статье — как я писал аналог mystem, lemmatizer и phpmorphy на Python, и что из этого получилось.
Сразу скажу, получилась библиотека для морфологического анализа на Python, которую Вы можете использовать и дорабатывать согласно лицензии MIT.
Первый вопрос — зачем все это?
Потихоньку делаю один хобби-проект, назрело решение писать его на Python. А для проекта был жизненно необходим более-менее качественный морфологический анализатор. Вариант с mystem не подошел из-за лицензии и отсутствием возможности поковыряться, lemmatizer и phpmorphy меня слегка напрягали своими словарями не в юникоде, а приличного аналога на python я почему-то не нашел. Вообщем причин много, все, если честно, не очень серьезные, на самом деле просто захотелось. Решил я, в итоге, изобрести велосипед и написать свою реализацию.
За основу взял наработки с сайта aot.ru. Словари (LGPL) для русского и английского, а также идеи — оттуда. Там были описаны и конкретные алгоритмы реализации, но в терминах теории автоматов. Я знаком с теорией автоматов (даже диплом по формальным грамматикам защитил), но мне показалось, что тут можно прекрасно обойтись без нее. Поэтому реализация — независимая, что имеет как плюсы, так и минусы.
Для начала, библиотека должна, как минимум, уметь:
1. Приводить слово к нормальной форме (например, в ед.ч., И.п. для существительных) — для слова «ЛЮДЕЙ» она должна вернуть «ЧЕЛОВЕК»
2. Определять форму слова (или формы). Например, для слова «ЛЮДЕЙ» она должна как-то дать знать, что это существительное, во множественном числе, может оказаться в родительном или винительном падежах.
Разбираемся со словарями
Словари с сайта aot.ru содержат следующую информацию:
1. парадигмы слов и конкретные правила образования;
2. ударения;
3. пользовательские сессии;
4. набор префиксов (продуктивных приставок);
5. леммы (незменяемые части слова, основы);
и еще есть 6. грамматическая информация — в отдельном файле.
Из этого всего нам интересны правила образования слов, префиксы, леммы и грамматическая информация.
Все слова образуются по одному принципу:
[префикс]+[приставка]+[основа]+[окончание]
Префиксы — это всякие «мега», «супер» и т.д. Набор префиксов хранится просто списком.
Приставки — имеются в виду приставки, присущие грамматической форме, но не присущие неизменяемой части слова («по»,«наи»). Например, «наи» в слове «наикрасивейший», т.к. без превосходной степени будет «красивый».
Правила образования слов — это то, что надо приписать спереди и сзади основы, чтобы получить какую-то форму. В словаре хранятся пары «приставка — окончание», + «номер» записи о грамматической информации (которая хранится отдельно).
Правила образования слов объединены в парадигмы. Например, для какого-нибудь класса существительных может быть описано, как слово выглядит во всех падежах и родах. Зная, что существительное принадлежит к этому классу, мы сможем правильно получить любую его форму. Такой класс — это и есть парадигма. Первой в парадигме всегда идет нормальная форма слова (если честно, вывод эмпирический))
Леммы — это неизменяемые части слов. В словаре хранится информация о том, какой лемме соответствуют какие парадигмы (какой набор правил для образования грамматических форм слова). Одной лемме может соответствовать несколько парадигм.
Грамматическая информация — просто пары («номер» записи, грам. информация). «Номер» в кавычках, т.к. это 2 буквы, просто от балды, но все разные.
Файл со словарем — обычный текстовый, для каждого раздела сначала указано число строк в нем, а потом идут строки, формат их описан, так что написать парсер труда не составило.
Поняв структуру словаря, уже несложно написать первую версию морфологического анализатора.
Пишем морфологический анализатор
По сути, нам дано слово, и его надо найти среди всех разумных комбинаций вида
<префикс>+<приставка>+<лемма>+<окончание> и
<приставка>+<лемма>+<окончание>
Дело упрощает то, что оказалось (как показала пара строчек на питоне), что «приставок» у нас в языке (да и в английском вроде тоже) всего 2. А префиксов в словаре — порядка 20 для русского языка. Поэтому искать можно среди комбинаций
<префикс>+<лемма>+<окончание>, объединив в уме список приставок и префиксов, а затем выполнив небольшую проверочку.
С префиксом разберемся по-свойски: если слово начинается с одного из возможных префиксов, то мы его (префикс) отбрасываем и пытаемся морфологически анализировать остаток (рекурсивно), а потом просто припишем отброшенный префикс к полученным формам.
В итоге получается, что задача сводится к поиску среди комбинаций
<лемма>+<окончание>, что еще лучше. Ищем подходящие леммы, потом смотрим, есть ли для них подходящие окончания*.
Тут я решил сильно упростить себе жизнь, и задействовать стандартный питоновский ассоциативный массив, в который поместил все леммы. Получился словарь вида lemmas: {base -> [rule_id]}, т.е. ключ — это лемма, а значение — список номеров допустимых парадигм. А дальше поехали — сначала считаем, что лемма — это первая буква слова, потом, что это 2 первых буквы и т.д. По лемме пытаемся получить список парадигм. Если получили, то в каждой допустимой парадигме пробегаем по всем правилам и смотрим, получится ли наше слово, если правило применить. Получается — добавляем его в список найденных форм.
*Еще был вариант — составить сразу словарь всех возможных слов вида <лемма>+<окончание>, получалось в итоге где-то миллионов 5 слов, не так и много, но вариант, вообщем, мне не очень понравился.
Дописываем морфологический анализатор
По сути — все готово, мы написали морфологический анализатор, за исключением некоторых деталей, которые заняли у меня 90% времени)
Деталь первая
Если вспомнить пример, который был в начале, про «ЛЮДЕЙ» — «ЧЕЛОВЕК», то станет понятно, что есть слова, у которых неизменяемая часть отсутствует. И где тогда искать формы этих слов — непонятно. Пробовал искать среди всех окончаний (точно так же, как и среди лемм), ведь для таких слов и «ЛЮДЕЙ», и «ЧЕЛОВЕКУ» в словаре будут храниться как окончания. Для некоторых слов работало, для некоторых выдавало, кроме правильного результата, еще и кучу мусора. В итоге, после долгих экспериментов выяснилось, что есть в словаре такая хитрая магическая лемма "#", которая и соответствует всем пустым леммам. Ура, задача решена, ищем каждый раз еще и там.
Деталь вторая
Хорошо бы иметь «предсказатель», который смог бы работать и со словами, которых нет в словаре. Это не только неизвестные науке редкие слова, но и просто описки, например.
Тут есть 2 несложных, но вполне работающих подхода:
1. Если слова отличаются только тем, что к одному из них приписано что-то спереди, то, скорее всего, склоняться они будут однаково
2. Если 2 слова оканчиваются одинаково, то и склоняться они, скорее всего, будут одинаково.
Первый подход — это угадывание префикса. Реализуется очень просто: пробуем считать сначала одну первую букву слова префиксом, потом 2 первых буквы и т.д. А то, что осталось, передаем морфологическому анализатору. Ну и делаем это только для не очень длинных префиксов и не очень коротких остатков.
Второй (предсказание по концу слова) подход чуть сложнее в реализации (так-то сильно сложнее, если нужна хорошая реализация)) и «поумнее» в плане предсказаний.
Первая сложность связана с тем, что конец слова может состоять не только из окончания, но и из части леммы. Тут я тоже решил «срезать углы» и задействовал опять ассоциативный массив с предварительно подготовленными всеми возмоными окончаниями слов (до 5 букв). Не так и много их получилось, несколько сот тысяч. Ключ массива — конец слова, значение — список возможных правил. Дальше — все как при поиске подходящей леммы, только у слова берем не начало, а 1, 2, 3, 4, 5-буквенные концы, а вместо лемм у нас теперь новый монстромассив.
Вторая сложность — получается много заведомого мусора. Мусор этот отсекается, если учесть, что полученные слова могут быть только существительными, прилагательными, наречиями или глаголами.
Даже после этого у нас остается слишком много не-мусорных правил. Для определенности, для каждой части речи оставляем только самое распространенное правило.
По идее, если слово не было предсказано как существительное, хорошо бы добавить вариант с неизменяемым существительным в ед.ч. и.п., но это в TODO.
Идеальный текст для проверки работы предсказателя — конечно же, «Сяпала Калуша по напушке», про то, как она там увазила бутявку и что из этого вышло:
Сяпала Калуша по напушке и увазила бутявку. И волит:
— Калушата, калушаточки! Бутявка!
Калушата присяпали и бутявку стрямкали. И подудонились.
А Калуша волит:
Оее, оее! Бутявка-то некузявая!
Калушата бутявку вычучили.
Бутявка вздребезнулась, сопритюкнулась и усяпала с напушки.
А Калуша волит:
— Бутявок не трямкают. Бутявки дюбые и зюмо-зюмо некузявые. От бутявок дудонятся.
А бутявка волит за напушкой:
— Калушата подудонились! Калушата подудонились! Зюмо некузявые! Пуськи бятые!
Из текста предсказатель не справился с именем собственным Калуша, с «Калушата»(они стали мужиками «Калуш» и «Калушат»), с междометием «Оее», загадочным «зюмо-зюмо», и вместо «Пуська» опять выдал мужика «Пусек», остальное все, на мой взгляд, определил правильно. Вообщем есть куда стремиться, но уже неплохо.
О ужас, «хабрахабр» предсказывается тоже никак. А вот «хабрахабра» — уже понимает, что «хабрахабр».
Тут можно было бы, в принципе, подвести итог, если бы компьютеры работали мгновенно. Но это не так, поэтому есть
Деталь №3: ресурсы процессора и оперативной памяти
Скорость работы первого варианта меня вполне устроила. Получалось, по прикидкам, тыщ до 10 слов в секунду для простых русских слов, около тыщи для навороченных. Для английского — еще быстрее. Но было 2 очевидных (еще до начала разработки) «но», связанных с тем, что все данные грузились в оперативную память (через pickle/cPickle).
1. первоначальная загрузка занимала 3-4 секунды
2. кушалось порядка 150 мегабайт оперативной памяти с psyco и порядка 100 без ( +удалось немного сократить, когда привел всякие там питоновские set и list к tulpe, где возможно)
Не долго думая, провел небольшой рефакторинг и вынес все данные в отдельную сущность. А дальше мне на помощь пришла магия Python и duck typing. Вкратце — в алгоритмах использовались данные в виде ассоциативных и простых массивов. И все будет работать без переписывания алгоритмов, если вместо «настоящих» массивов подсунуть что-нибудь, что ведет себя как массив, а конкретнее, для нашей задачи, — поддерживает [] и in. Все) В стандартной поставке питона обнаружились такие «массивные» интерфейсы к нескольким нереляционным базам данных. Эти базы (bsddb, ndbm, gdbm), по сути, и есть ассоциативные массивы на диске. Как раз то, что нужно. Еще там обнаружилась пара высокоуровневых надстроек над этим хозяйством (anydbm и shelve). Остановился на том, что унаследовался от DbfilenameShelf и добавил поддержку ключей в юникоде и ключей-целых чисел. А, еще добавил кеширование результата, которое почему-то есть в Shelf, но убито в DbfilenameShelf.
Данные по скорости на тестовых текстах (995 слов, русский словарь, macbook):
get_pickle_morph('ru', predict_by_prefix = False): 0.214 CPU seconds
get_pickle_morph('ru'): 0.262 CPU seconds
get_shelve_morph('ru', predict_by_prefix = False): 0.726 CPU seconds
get_shelve_morph('ru'): 0.874 CPU seconds
Памяти вариант shelve, можно сказать, не кушал совсем.
Варианты shelve похоже, работали, когда словари уже сидели в дисковом кеше. Без этого время может быть и 20 секунд с включенным предсказателем. Еще замечал, что медленнее всего работает предсказание по префиксу: до того, как прикрутил кеширование к своим наследникам от DbfilenameShelf, тормозило это предсказание раз в 5 больше, чем все остальное вместе взятое. А в этих тестах вроде не сильно уже.
Кстати, пользуясь случаем, хочу спросить, вдруг кто-то знает, как в питоне можно узнать количество занятой текущим процессом памяти. По возможности кроссплатформенно как-нибудь. А то ставил в конец скрипта задержку и мерил по списку процессов.
Пример использования
import pymorphy
morph = pymorphy.get_shelve_morph('ru')
#слова должны быть в юникоде и ЗАГЛАВНЫМИ
info = morph.get_graminfo(unicode('Вася').upper())
Так все-таки, зачем?
В самом начале я уже объяснил, зачем стал писать морфологический анализатор.
Теперь время объяснить, почему я стал писать эту статью. Я ее написал в надежде на то, что такая библиотека интересна и полезна, и вместе мы эту библиотеку улучшим. Дело в том, что функционал, который уже реализован, вполне достаточен для моих нужд. Я буду ее поддерживать и исправлять, но не очень активно. Поэтому эта статья — не подробная инструкция по применению, а попытка описать, как все работает.
В поставке
pymorphy.py — сама библиотека
shelve_addons.py — наследники от shelf, может кому пригодится
encode_dicts.py — утилита, которая преобразовывает словари из формата AOT в форматы pymorphy. Без параметров, работает долго, ест метров 200 памяти, запускается 1 раз. Сконвертированные словари не распространяю из-за возможной бинарной несовместимости и большого объема.
test.py — юнит-тесты для pymorphy
example.py — небольшой пример и тексты с теми 995 словами
dicts/src — папка с исходными словарями, скачанными с aot.ru
dicts/converted — папка, куда encode_dicts.py будет складывать сконвертированные словари
Напоследок
ссылки:
www.assembla.com/wiki/show/pymorphy
hg.assembla.com/pymorphy
trac-hg.assembla.com/pymorphy
Лицензия — MIT.
проверял только под Python 2.5.
Замечания, предложения, вопросы по делу — приветствуются. Если интересно — подключайтесь к разработке.