Showing posts with label определить схожесть слов. Show all posts
Showing posts with label определить схожесть слов. Show all posts

Thursday, January 21, 2016

Алгоритм Стеммер Портера для русского языка на C++ (CPP)

Для реализации одной из задач по обработке текста мне было необходимо подсчитать количество различных слов в тексте.. но не просто различных, а целиком и полностью! :) Например, слова "себя" и "себе" - должны считаться одинаковыми. Поискав в сети немного информации, наткнулся на алгоритм Стеммер Портера, который способен выделить в слове его основу, отбросив всё ненужное. Нашёл несколько его реализаций, в том числе на PHP и JAVA. Но нужной мне реализации на языке С++ я найти так и не смог. Пришлось переводить код с Java  на С++. В C++ довольно скудная поддержка регулярных выражений, с несколькими ограничениями, поэтому пришлось упрощать некоторые выражения.
Кстати, во время разработки нашёл баг в классе std::regex. Так вот, если в регулярном выражении есть маленькая буква "я", то всё крашится и падает :-/ Все остальные русские символы, в том числе и большая буква "Я" не вызывают подобного эффекта. Именно поэтому пришлось все регулярные выражения записать в КАПСЕ.

Ниже приведу листинг класса на C++:
class StemmerPorter{
private:
static const string EMPTY;
static const string S1;
static const string S13;
static const string SN;
static const string const1;
static const regex PERFECTIVEGROUND;
static const regex REFLEXIVE;
static const regex ADJECTIVE;
static const regex PARTICIPLE;
static const regex VERB;
static const regex NOUN;
static const regex I;
static const regex P;
static const regex NN;
static const regex DERIVATIONAL;
static const regex DER;
static const regex SUPERLATIVE;

public:
StemmerPorter();
string get(string s);
};

const string StemmerPorter::const1 = "АЕИОУЫЭИЮЯ";
const string StemmerPorter::EMPTY = "";
const string StemmerPorter::S1 = "$1";
const string StemmerPorter::S13 = "$1$3";
const string StemmerPorter::SN = "Н";
const regex StemmerPorter::PERFECTIVEGROUND = regex("(ИВ|ИВШИ|ИВШИСЬ|ЫВ|ЫВШИ|ЫВШИСЬ|ВШИ|ВШИСЬ)$");
const regex StemmerPorter::REFLEXIVE = regex("(СЯ|СЬ)$");
const regex StemmerPorter::ADJECTIVE = regex("(ЕЕ|ИЕ|ЫЕ|ОЕ|ИМИ|ЫМИ|ЕЙ|ИЙ|ЫЙ|ОЙ|ЕМ|ИМ|ЫМ|ОМ|ЕГО|ОГО|ЕМУ|ОМУ|ИХ|ЫХ|УЮ|ЮЮ|АЯ|ЯЯ|ОЮ|ЕЮ)$");
const regex StemmerPorter::PARTICIPLE = regex("(.*)(ИВШ|ЫВШ|УЮЩ)$|([АЯ])(ЕМ|НН|ВШ|ЮЩ|Щ)$");
const regex StemmerPorter::VERB = regex("(.*)(ИЛА|ЫЛА|ЕНА|ЕЙТЕ|УЙТЕ|ИТЕ|ИЛИ|ЫЛИ|ЕЙ|УЙ|ИЛ|ЫЛ|ИМ|ЫМ|ЕН|ИЛО|ЫЛО|ЕНО|ЯТ|УЕТ|УЮТ|ИТ|ЫТ|ЕНЫ|ИТЬ|ЫТЬ|ИШЬ|УЮ|Ю)$|([АЯ])(ЛА|НА|ЕТЕ|ЙТЕ|ЛИ|Й|Л|ЕМ|Н|ЛО|НО|ЕТ|ЮТ|НЫ|ТЬ|ЕШЬ|ННО)$");
const regex StemmerPorter::NOUN = regex("(А|ЕВ|ОВ|ИЕ|ЬЕ|Е|ИЯМИ|ЯМИ|АМИ|ЕИ|ИИ|И|ИЕЙ|ЕЙ|ОЙ|ИЙ|Й|ИЯМ|ЯМ|ИЕМ|ЕМ|АМ|ОМ|О|У|АХ|ИЯХ|ЯХ|Ы|Ь|ИЮ|ЬЮ|Ю|ИЯ|ЬЯ|Я)$");
const regex StemmerPorter::I = regex("И$");
const regex StemmerPorter::P = regex("Ь$");
const regex StemmerPorter::NN = regex("НН$");
const regex StemmerPorter::DERIVATIONAL = regex(".*[^АЕИОУЫЭЮЯ]+[АЕИОУЫЭЮЯ].*ОСТЬ?$");
const regex StemmerPorter::DER = regex("ОСТЬ?$");
const regex StemmerPorter::SUPERLATIVE = regex("(ЕЙШЕ|ЕЙШ)$");

StemmerPorter::StemmerPorter(){
setlocale(0,"");
}
string StemmerPorter::get(string s){
transform(s.begin(), s.end(), s.begin(), ::toupper);
replace( s.begin(), s.end(), 'Ё', 'Е');
int k=0;
size_t pos = s.find_first_of(const1, 0);
if(pos != string::npos){
string pre = s.substr(0,pos+1);
string rv = s.substr(pos+1);
string temp = regex_replace(rv, PERFECTIVEGROUND, EMPTY);
if(rv.size()!=temp.size()){
rv=temp;
}
else{
rv = regex_replace(rv, REFLEXIVE, EMPTY);
            temp = regex_replace(rv, ADJECTIVE, EMPTY);
if(rv.size()!=temp.size()){
rv = temp;
rv = regex_replace(rv, PARTICIPLE, S13);
}
else{
temp = regex_replace(rv, VERB, S13);
if(rv.size()!=temp.size()){
rv=temp;
}
else{
rv=regex_replace(temp, NOUN, EMPTY);
}
}
}
rv=regex_replace(rv, I, EMPTY);
if(regex_match(rv, DERIVATIONAL)){
rv = regex_replace(rv, DER, EMPTY);
}
temp = regex_replace(rv, P, EMPTY);
if(temp.length()!=rv.length()){
rv=temp;
}
else{
rv=regex_replace(rv, SUPERLATIVE, EMPTY);
rv=regex_replace(rv, NN, SN);
}
s=pre+rv;
}
transform(s.begin(), s.end(), s.begin(), tolower);
return s;
}

Использовать данный класс очень просто:
StemmerPorter sp;
std::cout<<sp.get("Алгоритмы")<<std::endl;