Часто возникает задача полностью склонить одно слово к другому. Такие задачи часто возникают в синомайзинге. Задача само по себе простая, если бы у слов было по одной морфологической интерпретации, а 90% случаев слово имеет по две или более интерпретаций. Расскажу как эту задачу можно решить без решение омонимии в большинстве случаев.
Пускай у нас есть слово замены WiZ (Word in Zamena) и слово текста WiT (Word in Text). Оба слова в общем случае имеют несколько интерпретаций. Код буду писать на псевдо Си. Исходник слишком много занимает.
WiZ.Inter[i] значит итую интерпетацию слова.
WiZ.Inter[i].Gram - грамемы (род число падеж одушевленность, превосходная степень и прочее)
WiZ.InterCount число интерпретаций
FM (Flexia Model)
WiZ.FM[i] - значит итое окончание у слова
WiZ.FM[i].Str - строка итого окончания слова
WiZ.FM[i].Gram - грамемы
WiZ.FM_Count - число все возможных окончаний
Compare (WiZ.FM[i].Str , WiZ.FM[j].Str) - тупо сравнивает строки и-того и жи-того окончания. Возращает труе если они равны, и фалсе в противоположном случае.
Compare(WiZ.FM[i].Gram , WiZ.FM[j].Gram) - возвращает число от нуля до 100. Ноль граммемы не соответствуют. 100 граммемы полностью соответствуют. от 1 до 99, если граммемы соответствую, но возможны варианты лучшего соответствия.
Например, в у одного из входящий значений падеж предложный первый, а у второго предложный второй. Или у одного род женский(жр), а у второго обобщенный (мр-жр). Есть несколько правил такой оценки. Некоторые из них зависят от порядка взодящих параметров (что вошло первым, а что вторым), некотрые нет. Итак пресупим…
Поскольку символы меньше и больше это одновременно с этим символы окончания и начало ХТМЛ тегов, Я буду писать МЕНЬШЕ и БОЛЬШЕ вместо них.
Итак мы приступим. Мы запишем в W_OUT результирующие интерпретации. Если при этих интерпретациях омонимия перейдет на строки мы вернем NULL - указатель на пустое место.
OUT =WiZ;
WiZ.Inter.DeleteALL();//удалим все интерпретации
OUT =WiZ;
WiZ.Inter.DeleteALL();//удалим все интерпретации
for (int i=0;i МЕНЬШЕ WiT.InterCount;i++)//перебираем все интерпретации слова из текста
{
int BestRes=-1;
int BestJ=-1;
for (int j=0;j МЕНЬШЕ WiZ.FM_Count;j++) //перебираем все интерпретации слова замены
{
int res = 0;
res=Comp(WiT.Inter[i].Gramm,WiZ.FM[j].Gramm);
if (res==100)
{
BestJ=J;
break;
}
else if (res!=0 && BestRes МЕНЬШЕ res)
{
BestRes=res;
BestJ=J;
}
}
if (BestJ==-1)
return NULL;//ничего не найдено
if (i!=0&&!Comp(WiZ.FM[BestJ].Str,WiZ.Inter[i-1].Str))
return NULL;//если строки не совпадают омонимия перешла на строки
OUT.Inter[i]=BestJ;
}
return OUT;
Но в большинстве случаев там где можно было бы преобразовать, этот код возвращает ноль. Это происходит из-за того, что на 2ом, 3ом или … шаге. Выбирает лучшую интерпритацию с другой строкой. Пусть даже есть правильное окончание и его оценка такая-же, но если она позже встречается, то он ее не вернет. Поэтому преобразуем код.
OUT =WiZ;
WiZ.Inter.DeleteALL();//удалим все интерпретации
for (int i=0;i МЕНЬШЕ WiT.InterCount;i++)//перебираем все интерпретации слова из текста
{
int BestRes=-1;
int BestJ=-1;
for (int j=0;j МЕНЬШЕ WiZ.FM_Count;j++) //перебираем все интерпретации слова замены
{
int res = 0;
if (i==0||Comp(WiZ.FM[BestJ].Str,WiZ.Inter[i-1].Str)
res=Comp(WiT.Inter[i].Gramm,WiZ.FM[j].Gramm);
if (res==100)
{
BestJ=J;
break;
}
else if (res!=0 && BestRes МЕНЬШЕ res)
{
BestRes=res;
BestJ=J;
}
}
if (BestJ==-1)
return NULL;//ничего не найдено
OUT.Inter[i]=BestJ;
}
Но и этот код не идеальный. Если на первом шаге (i=0). Метод “зацепиться” за неправильную строку, то все-равно вернет НУЛЛ.
Поэтому нужно рестартить.
OUT =WiZ;
WiZ.Inter.DeleteALL();//удалим все интерпиритации
long BestJ_Lev0;
for (int i=0;i МЕНЬШЕ WiT.InterCount;i++)//перебираем все интерпритации слова из текста
{
STARTJ=0;
RESTART:
int BestRes=-1;
int BestJ=-1;
for (int j=STARTJ;j МЕНЬШЕ WiZ.FM_Count;j++) //перебираем все интерпритации слова замены
{
int res = 0;
if (i==0||Comp(WiZ.FM[j].Str,WiZ.Inter[i-1].Str)
res=Comp(WiT.Inter[i].Gramm,WiZ.FM[j].Gramm);
if (res==100)
{
BestJ=J;
break;
}
else if (res!=0 && BestRes МЕНЬШЕ res)
{
BestRes=res;
BestJ=J;
}
}
if (BestJ==-1)
{
if (i==0)
return NULL;//ничего не найдено
else
{
i=0;//переходим на первый уровень
STARTJ=BestJ_Lev0+1;//и начинаем считать с прошлого найденного элемента+1
goto RESTART;
}
}
OUT.Inter[i]=BestJ;
if (i==0)
BestJ_Lev0=BestJ;
}
Но и этот код может вернуть НУЛЛ на гладком месте. Если правильное окончание нулевого уровня находиться до окончания выбранного и при этом имеет худшую оценку.
Поэтому поступим так.
OUT =WiZ;
WiZ.Inter.DeleteALL();//удалим все интерпретации
long BestJ_Lev0;
int RestartCount=0;
int Iskl1=-1;
int Iskl2=-1;
int Iskl3=-1;
for (int i=0;i МЕНЬШЕ WiT.InterCount;i++)//перебираем все интерпритации слова из текста
{
STARTJ=0;
RESTART:
int BestRes=-1;
int BestJ=-1;
for (int j=0;j МЕНЬШЕ WiZ.FM_Count;j++) //перебираем все интерпритации слова замены
{
int res = 0;
if ((i==0||Comp(WiZ.FM[j].Str,WiZ.Inter[i-1].Str)
&&(i!=0||Iskl1==-1||!Comp(WiZ.FM[j].Str,WiZ.FM[Iskl1].Str)
&&(i!=0||Iskl2==-1||!Comp(WiZ.FM[j].Str,WiZ.FM[Iskl2].Str)
&&(i!=0||Iskl3==-1||!Comp(WiZ.FM[j].Str,WiZ.FM[Iskl3].Str))
res=Comp(WiT.Inter[i].Gramm,WiZ.FM[j].Gramm);
//Нулевой уровень проверяем, если строки не попали в исключения
if (res==100)
{
BestJ=J;
break;
}
else if (res!=0 && BestRes МЕНЬШЕ res)
{
BestRes=res;
BestJ=J;
}
}
if (BestJ==-1)
{
if (i==0||RestartCount==3)
return NULL;//ничего не найдено
else
{
i=0;//переходим на первый уровень
RestartCount++;
if (RestartCount==1)
Iskl1=BestJ_Lev0;
if (RestartCount==2)
Iskl2=BestJ_Lev0;
if (RestartCount==3)
Iskl3=BestJ_Lev0;
goto RESTART;
}
}
OUT.Inter[i]=BestJ;
if (i==0)
BestJ_Lev0=BestJ;
}
Вот собственно весь алгоритм только необходимо отметить, что это псевдо си и для того, чтобы сверить строки двух окончаний необходимо сверить еще и окончание и окончательно-приставочную строку. Например лучший в превосходной степени преобразуется в НАИлучший. НАИ и есть окончательно-приставочная строка или лемма-зависимая приставка.