adding modules
git-svn-id: https://russianmorphology.googlecode.com/svn/trunk@49 d817d54c-26ab-11de-abc9-2f7d1455ff7a
This commit is contained in:
parent
786ce92ae0
commit
710384987c
22
dictionary-reader/pom.xml
Normal file
22
dictionary-reader/pom.xml
Normal 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>
|
@ -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(), ""));
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
23
morph/pom.xml
Normal 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>
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
@ -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 {
|
||||
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));
|
@ -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()]);
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.russian.morphology;
|
||||
package org.apache.lucene.morphology;
|
||||
|
||||
|
||||
public class SuffixToLongException extends RuntimeException {
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.russian.morphology;
|
||||
package org.apache.lucene.morphology;
|
||||
|
||||
|
||||
public class WrongCharaterException extends RuntimeException {
|
@ -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;
|
@ -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 {
|
24
pom.xml
24
pom.xml
@ -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>
|
||||
|
||||
<modules>
|
||||
<module>morph</module>
|
||||
<module>dictionary-reader</module>
|
||||
<module>russian</module>
|
||||
</modules>
|
||||
</project>
|
31
russian/pom.xml
Normal file
31
russian/pom.xml
Normal 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>
|
@ -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;
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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");
|
||||
}
|
||||
|
||||
}
|
@ -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." );
|
||||
}
|
||||
}
|
@ -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()));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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();
|
||||
// }
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
тест тест
|
||||
ёж еж
|
||||
естера естера
|
||||
что-то что-то
|
||||
а а
|
||||
яяяяяя яяяяяя
|
||||
яяяя яяяя
|
||||
аа аа
|
||||
аааааа аааааа
|
||||
аааааааааааа аааааааааааа
|
||||
аааааааааааааааааа аааааааааааааааааа
|
||||
ааааааааааааааааа ааааааааааааааааа
|
||||
йфячыцувс йфячыцувс
|
@ -1,8 +0,0 @@
|
||||
тест тест
|
||||
ёж еж
|
||||
естера естера
|
||||
что-то что-то
|
||||
а а
|
||||
яяяяяя яяяяяя
|
||||
яяяя яяяя
|
||||
аа аа
|
@ -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.
|
||||
|
||||
Замечания, предложения, вопросы по делу — приветствуются. Если интересно — подключайтесь к разработке.
|
Loading…
x
Reference in New Issue
Block a user