API Яндекс Лингвистики под .NET

    После посещения Yet another Conference 2013 у меня возникла идея написать API для всех сервисов яндекс лингвистики под .NET. После недлительного гугления, таких библиотек к счастью не оказалось. Несмотря на то, что может она никому особо и не понадобится, я все же решил реализовать ее хотя бы для того, чтобы попрактиковаться с RestSharp, тестированием и различными функциями гитхаба (issuers, release, markdown и др.). Кроме того, в процессе реализации пришлось столкнуться с интересным алгоритмом сравнения строк, о котором я упомяну в топике.

    Сразу кидаю ссылки на исходники и бинарики на GitHub: Code, Binary

    Реализованные API


    • Яндекс.Предиктор. Данный сервис позволяет приложениям получать в виде подсказок наиболее вероятное продолжение слова или фразы. Предиктор также учитывает опечатки в исходном запросе. Это упрощает процесс ввода текста, особенно на мобильных устройствах.
    • Яндекс.Словарь. Данный сервис позволяет приложениям получать подробные словарные статьи из машинных словарей Яндекса. Статьи содержат сгруппированные переводы, информацию о частях речи, примеры, а также транскрипцию для английских слов.
    • Яндекс.Перевод. Перевод текста для более чем 30 языков.
    • Яндекс.Спеллер. Сервис проверки правописания, который помогает находить и исправлять орфографические ошибки. Работа сервиса основана на использовании орфографического словаря. В настоящее время Спеллер проверяет тексты на русском, украинском и английском языках.


    RestSharp позволяет очень легко писать код для синхронных и асинхронных HTTP GET и POST запросов, а также преобразовывать полученные ответы в формате XML или JSON в .NET объекты (в данном проекте использовался XML).

    Расширенная функция подсчета расстояния Дамерау—Левенштейна


    В процессе реализации спеллера мне захотелось, чтобы пользователю отображался не только исправленный вариант текста, но и ошибки в нем. В голову сразу пришла мысль о расстоянии Левейштейна. Однако:
    • Данный алгоритм не учитывает транспозиционные ошибки, которыми являются 80% при наборе текста (данные с википедии).
    • Данный алгоритм он возвращает расстояние, а не позиции ошибок в новом слове.

    Первый недостаток был нивелирован с помощью расстояния Дамерау—Левенштейна, а второй — с помощью анализа матрицы, полученной в процессе работы алгоритма (расстояние — это значение элемента последнего ряда в последнем столбце матрицы. Соответственно в моем случае расстоянием будет общее количество ошибок, возвращенной этой функцией).

    Таким образом были реализован алгоритм для поиска следующих ошибок в ошибочном (word) и корректном (correctedWord) словах:
    • Замена. Пример: синхрафазатрон -> синхрофазотрон
    • Вставка. Пример: синхрофазотр -> синхрофазотрон
    • Удаление. Пример: синнхрофаазотрон -> синхрофазотрон
    • Транспозиция. Пример: синхрофазортон -> синхрофазотрон

    Кроме того, веса различных ошибок могут настраиваться (по-умолчанию все имеют одинаковый вес, равный единице).

    Код расширенной функции подсчета расстояния Дамерау—Левенштейна
    public static List<Mistake> DamerauLevenshteinDistance(
    	string word, string correctedWord,
    	bool transposition = true,
    	int substitutionCost = 1,
    	int insertionCost = 1,
    	int deletionCost = 1,
    	int transpositionCost = 1)
    {
    	int w_length = word.Length;
    	int cw_length = correctedWord.Length;
    	var d = new KeyValuePair<int, CharMistakeType>[w_length + 1, cw_length + 1];
    	var result = new List<Mistake>(Math.Max(w_length, cw_length));
    
    	if (w_length == 0)
    	{
    		for (int i = 0; i < cw_length; i++)
    			result.Add(new Mistake(i, CharMistakeType.Insertion));
    		return result;
    	}
    
    	for (int i = 0; i <= w_length; i++)
    		d[i, 0] = new KeyValuePair<int, CharMistakeType>(i, CharMistakeType.None);
    
    	for (int j = 0; j <= cw_length; j++)
    		d[0, j] = new KeyValuePair<int, CharMistakeType>(j, CharMistakeType.None);
    
    	for (int i = 1; i <= w_length; i++)
    	{
    		for (int j = 1; j <= cw_length; j++)
    		{
    			bool equal = correctedWord[j - 1] == word[i - 1];
    			int delCost = d[i - 1, j].Key + deletionCost;
    			int insCost = d[i, j - 1].Key + insertionCost;
    			int subCost = d[i - 1, j - 1].Key;
    			if (!equal)
    				subCost += substitutionCost;
    			int transCost = int.MaxValue;
    			if (transposition && i > 1 && j > 1 && word[i - 1] == correctedWord[j - 2] && word[i - 2] == correctedWord[j - 1])
    			{
    				transCost = d[i - 2, j - 2].Key;
    				if (!equal)
    					transCost += transpositionCost;
    			}
    
    			int min = delCost;
    			CharMistakeType mistakeType = CharMistakeType.Deletion;
    			if (insCost < min)
    			{
    				min = insCost;
    				mistakeType = CharMistakeType.Insertion;
    			}
    			if (subCost < min)
    			{
    				min = subCost;
    				mistakeType = equal ? CharMistakeType.None : CharMistakeType.Substitution;
    			}
    			if (transCost < min)
    			{
    				min = transCost;
    				mistakeType = CharMistakeType.Transposition;
    			}
    
    			d[i, j] = new KeyValuePair<int, CharMistakeType>(min, mistakeType);
    		}
    	}
    
    	int w_ind = w_length;
    	int cw_ind = cw_length;
    	while (w_ind >= 0 && cw_ind >= 0)
    	{
    		switch (d[w_ind, cw_ind].Value)
    		{
    			case CharMistakeType.None:
    				w_ind--;
    				cw_ind--;
    				break;
    			case CharMistakeType.Substitution:
    				result.Add(new Mistake(cw_ind - 1, CharMistakeType.Substitution));
    				w_ind--;
    				cw_ind--;
    				break;
    			case CharMistakeType.Deletion:
    				result.Add(new Mistake(cw_ind, CharMistakeType.Deletion));
    				w_ind--;
    				break;
    			case CharMistakeType.Insertion:
    				result.Add(new Mistake(cw_ind - 1, CharMistakeType.Insertion));
    				cw_ind--;
    				break;
    			case CharMistakeType.Transposition:
    				result.Add(new Mistake(cw_ind - 2, CharMistakeType.Transposition));
    				w_ind -= 2;
    				cw_ind -= 2;
    				break;
    		}
    	}
    	if (d[w_length, cw_length].Key > result.Count)
    	{
    		int delMistakesCount = d[w_length, cw_length].Key - result.Count;
    		for (int i = 0; i < delMistakesCount; i++)
    			result.Add(new Mistake(0, CharMistakeType.Deletion));
    	}
    
    	result.Reverse();
    
    	return result;
    }
    



    Интерфейс


    Интерфейс был реализован на WinForms с надеждой, что приложение будет запускаться и на Mono. Однако на нем тестирование не проводилось.

    image

    Данную библиотеку можно использовать в любых проектах, но с указанием авторства (Apache 2.0).

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 15

      0
      Отличная вещь, пригодится! Спасибо!
        +1
        на сколько я вижу это не настоящее расстояние Домерау-Левенштейна, а Optimal String Alignment; настоящий DL алгоритм разрешает повторное редактирование подстрок, в то время как OSA не может этого делать, там же в педивикии есть пример

        приведенный алгоритм дла «CA» и «ABC» выдает 3, а нужно 2

        Данный алгоритм не учитывает транспозиционные ошибки, которыми являются 80% при наборе текста (данные с википедии).


        тут вы тоже не правы, Дамерау в статье выдвигает гипотезу, что 80% всех типов опечаток входит в множество из 4 типов {удаление, вставка, замена, перестановка соседних}
          0
          приведенный алгоритм дла «CA» и «ABC» выдает 3, а нужно 2

          Да, вы правы. Кроме того, мой алгоритм возвращает неправильные ошибки.

          тут вы тоже не правы, Дамерау в статье выдвигает гипотезу, что 80% всех типов опечаток входит в множество из 4 типов {удаление, вставка, замена, перестановка соседних}

          Значит информация в википедии неверна. Там написано: «Дамерау показал, что 80 % ошибок при наборе текста человеком являются транспозициями.»
            0
            русская педивикия такая русская
          +2
          Молодцы конечно.

          Пару мелочей от зануды.
          1- protected RestClient _client = new RestClient(«translate.yandex.net/api/v1.5/tr/»); — обычно считается плохим тоном, тк строка забита жестко.

          2- www.nuget.org/packages?q=yandex выложите сюда. Основная проблема на моей памяти со всеми библиотеками, что их найти тяжело. Nuget- это то место от куда все пакеты и качают и начинают их искать.

            0
            Спасибо за замечания. Я все исправлю.
              +2
              Ну до «наггета» еще далеко.

              Вообще выглядят странными конструкции вида:

              public class Dictionary
              {
              	protected RestClient _client = new RestClient("https://dictionary.yandex.net/api/v1/dicservice/");
              	protected string _key;
              ...
              }
              

              В классе у которого нет наследников.

              А если брать глобальнее, то:
              1. Отсутствие асинхронного API
              2. Куча повторяющихся кусков кода, как то:

              RestRequest request = new RestRequest("getLangs");
              request.AddParameter("key", _key);
              
              RestResponse response = (RestResponse)_client.Execute(request);
              XmlAttributeDeserializer deserializer = new XmlAttributeDeserializer();
              if (response.StatusCode == System.Net.HttpStatusCode.OK)
              {
              	var strs = deserializer.Deserialize<List<string>>(response);
              	var allLangs = (Lang[])Enum.GetValues(typeof(Lang));
              	Lang[] result = allLangs.Where(lang => strs.Contains(lang.ToString().ToLowerInvariant())).ToArray();
              	return result;
              }
              else
              {
              	var error = deserializer.Deserialize<YandexError>(response);
              	throw new YandexLinguisticsException(error);
              }
              


              Или вот такой паттерн, встречается многократно

              RestResponse response = (RestResponse)_client.Execute(request);
              XmlAttributeDeserializer deserializer = new XmlAttributeDeserializer();
              if (response.StatusCode == System.Net.HttpStatusCode.OK)
              {
              	...
              }
              else
              {
              	...
              }
              

              Это все, явные кандидаты на какое-то абстрагирование.

              3. Хоть гуевое приложение в «наггет» не пойдет, но в нем все сетевые вызовы синхронны.
                0
                Ну что касается асинхронности, то да, ее нет. И можно ее реализовать кстати тоже с помощью RestSharp. Ну а отсутствие абстрагирования никак не повлияет на работу :)
                +2
                Также(да, мне нечем заняться с утра :)), не контролируется длина полей ввода. Если вводить в них достаточно длинные строки, то выводятся ошибки.
                Например, вот тут:
                var error = deserializer.Deserialize<YandexError>(response);
                throw new YandexLinguisticsException(error);
                

                Метод Deserialize может вернуть null(при длинной введенной строке), и в строке результата вижу:

                System.NullReferenceException: Object reference not set to an instance of an object. at YandexLinguistics.NET.YandexLinguisticsException..ctor(YandexError error) in c:\Users\юленька\Documents\Code\Yandex-Linguistics.NET\YandexLinguistics.NET\YandexLinguisticsException.cs:line 12 at YandexLinguistics.NET.Dictionary.Lookup(LangPair lang, String text, String ui, LookupOptions flags) in c:\Users\юленька\Documents\Code\Yandex-Linguistics.NET\YandexLinguistics.NET\Dictionary\Dictionary.cs:line 69 at YandexLinguistics.NET.Gui.frmMain.<UpdateDictionaryResult>b__13() in c:\Users\юленька\Documents\Code\Yandex-Linguistics.NET\YandexLinguistics.NET.Gui\frmMain.cs:line 332
                  0
                  Спасибо за тестирование, посмотрю в чем проблема.
              +2
              Интересно. Может, есть смысл сделать реализацию без RestSharp, но с System.Net.HttpClient и клиентской частью ASP.NET Web API. Тогда не нужно будет сериализовать/десериализовать руками, и асинхронность там тоже есть
                0
                В RestSharp тоже есть асинхронность, но я ее не использовал просто. А в текущей реализации разве что-то десериализуется руками? Или что вы имеете ввиду?

                Насчет остального я подумаю, а может кто-нибудь другой реализует при острой необходимости :)
                  +1
                  Евгению конечно виднее, что он имел ввиду.

                  Просто в web.api эта асинхронность очень просто взять и использовать.
                  www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from-a-net-client

                  а конструкция типа
                   RestResponse response = (RestResponse)_client.Execute(request);
                                          XmlAttributeDeserializer deserializer = new XmlAttributeDeserializer();
                                          if (response.StatusCode == System.Net.HttpStatusCode.OK)
                                          {
                                                  var strs = deserializer.Deserialize<List<string>>(response);
                  
                                                  var allLangs = (Lang[])Enum.GetValues(typeof(Lang));
                  


                  выглядет по страшнее чем
                  HttpResponseMessage response = client.GetAsync("api/products").Result;  // Blocking call!
                  if (response.IsSuccessStatusCode)
                  {
                      // Parse the response body. Blocking!
                      var products = response.Content.ReadAsAsync<IEnumerable<Product>>().Result;
                  
                0
                Планируете ли в API вывести функционал склонения, ранее был XML сервис по склонению собственных имен?
                  0
                  Да, пожалуй можно, тем более он небольшой.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое