Анализ английского текста с чашкой кофе «JavaSE8»

от автора

От автора

«Куда только не заведёт любопытство» — именно с этих слов и началась эта история.

Дело обстояло так.

Вернулся я из командировки из США, где провел целый месяц своей жизни. Готовился я Вам скажу я к ней основательно и прилично так налегал на английский, но вот не задача, приехав к заморским друзьям я понял что совершенно их не понимаю. Моему огорчению не было придела. Первым делом по приезду я встретился с другом, который свободно говорит по английски, излил ему душу и услышал в ответ: «… ты просто не те слова учил, нужно учить самые популярные… запас слов, который используется в повседневных разговорах не более 1000 слов…»

Хм, так ли это?, возник вопрос в моей голове… И пришла мне в голову идея проанализировать разговорный текст, так сказать, определить те самые употребляемые слова.

Исходные данные

В качестве разговорного текста я решил взять сценарий одной из серий сериала друзья, заодно и проверим гипотезу — «… если смотреть сериалы на английском, то хорошо подтянешь язык …» (сценарий без особого труда можно найти в интернете)

Используемые технологии

  • Java SE 8
  • Eclipse Mars 2

Ожидаемый результат

Результатом нашего творчества станет jar библиотека, которая будет составлять лексический минимум для текста с заданным процентом понимания. То есть мы например хотим понять 80% всего текста и библиотека, проанализировав текст выдаёт нам набор слов, которые необходимо для этого выучить.

И так, поехали.

Объекты DTO (боевые единицы)

ReceivedText.java

package ru.lexmin.lexm_core.dto;  /**  * Класс для получения от пользователя введённой информации а виде текста (text)  * и процента понимания (percent)  *  */ public class ReceivedText {  	/** 	 * Версия 	 */ 	private static final long serialVersionUID = 5716001583591230233L;  	// текст, который ввёл пользователь 	private String text;  	// желаемый процент понимания текста пользователем 	private int percent;  	/** 	 * Пустой конструктор 	 */ 	public ReceivedText() { 		super(); 	}  	/** 	 * Конструктор с параметрами 	 *  	 * @param text 	 *            {@link String} 	 * @param percent 	 *            int 	 */ 	public ReceivedText(String text, int percent) { 		super(); 		this.text = text; 		this.percent = percent; 	}  	/** 	 * @return text {@link String} 	 */ 	public String getText() { 		return text; 	}  	/** 	 * Устанавливает параметр 	 *  	 * @param text 	 *            text {@link String} 	 */ 	public void setText(String text) { 		this.text = text; 	}  	/** 	 * @return percent {@link int} 	 */ 	public int getPercent() { 		return percent; 	}  	/** 	 * Устанавливает параметр 	 *  	 * @param percent 	 *            percent {@link int} 	 */ 	public void setPercent(int percent) { 		this.percent = percent; 	}  } 

WordStat.java

package ru.lexmin.lexm_core.dto;  import java.util.HashMap; import java.util.Map;  /**  * Класс для передачи рзультов обработки текста в виде: - количество слов в  * тексте - честота употребления каждого слова.  *   * Количество слов хранится в поле countOfWords (int) Частота употребления  * хранится в поле frequencyWords (Map<String, Integer>): - ключом является  * слово - значением частора употребления в тексте  *   * Поле receivedText - содержет ссылку на dto с текстом и процентом понимания.  *  */ public class WordStat {  	/** 	 * Версия 	 */ 	private static final long serialVersionUID = -1211530860332682161L;  	// ссылка на dto с исходным текстом и параметрами 	private ReceivedText receivedText;  	// кол-во слов в тексте, на который ссылка receivedText 	private int countOfWords;  	// статистика по часторе слов текста, на который ссылка receivedText, 	// отфильтрованная с учётом процента понимания 	private Map<String, Integer> frequencyWords;  	/** 	 * Констркутор по умолчанию 	 */ 	public WordStat() { 		super(); 	}  	/** 	 * Конструктор с параметрами 	 *  	 * @param receivedText 	 * @param countOfWords 	 * @param frequencyWords 	 */ 	public WordStat(ReceivedText receivedText, int countOfWords, Map<String, Integer> frequencyWords) { 		this.receivedText = receivedText; 		this.countOfWords = countOfWords; 		this.frequencyWords = frequencyWords; 	}  	/** 	 * Конструктор задаёт значение поля receivedText из передоваемого объекта. 	 * остальнве поля интциализируются значениями по умолчанию 	 *  	 * @param receivedText 	 */ 	public WordStat(ReceivedText receivedText) { 		this.receivedText = receivedText; 		// инициализация остальных полей значениями по умолчинию 		this.countOfWords = 0; 		this.frequencyWords = new HashMap<String, Integer>(); 	}  	/** 	 * @return receivedText {@link ReceivedText} 	 */ 	public ReceivedText getReceivedText() { 		return receivedText; 	}  	/** 	 * Устанавливает параметр 	 *  	 * @param receivedText 	 *            receivedText {@link ReceivedText} 	 */ 	public void setReceivedText(ReceivedText receivedText) { 		this.receivedText = receivedText; 	}  	/** 	 * @return countOfWords {@link int} 	 */ 	public int getCountOfWords() { 		return countOfWords; 	}  	/** 	 * Устанавливает параметр 	 *  	 * @param countOfWords 	 *            countOfWords {@link int} 	 */ 	public void setCountOfWords(int countOfWords) { 		this.countOfWords = countOfWords; 	}  	/** 	 * @return frequencyWords {@link Map<String,Integer>} 	 */ 	public Map<String, Integer> getFrequencyWords() { 		return frequencyWords; 	}  	/** 	 * Устанавливает параметр 	 *  	 * @param frequencyWords 	 *            frequencyWords {@link Map<String,Integer>} 	 */ 	public void setFrequencyWords(Map<String, Integer> frequencyWords) { 		this.frequencyWords = frequencyWords; 	}  } 

Ну тут всё просто и понятно, думаю комментариев в коде достаточно

Интерфейс анализатора текстов (определяем функциональность)

TextAnalyzer.java

package ru.lexmin.lexm_core;  import ru.lexmin.lexm_core.dto.ReceivedText; import ru.lexmin.lexm_core.dto.WordStat;  /**  * Данный интерфейс описывает основной функционал анализа получаемого от  * пользователя текста  *  */ public interface TextAnalyzer {  	/** 	 * Мемод получает объект класса {@link WordStat}, заполненный данными, 	 * актуальными для передаваемого объекта {@link ReceivedText} 	 *  	 * @param receivedText 	 *            {@link ReceivedText} 	 * @return возврашает заполненный {@link WordStat} 	 */ 	public abstract WordStat getWordStat(ReceivedText receivedText);  } 

Нам будет достаточно всего одного внешнего метода, который нам вернёт WordStat (DTO), из которого мы потом и вытащим слова.

Реализация анализатора текстов

TextAnalyzerImp.java

package ru.lexmin.lexm_core;  import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream;  import ru.lexmin.lexm_core.dto.ReceivedText; import ru.lexmin.lexm_core.dto.WordStat;  /**  * Этот класс является реализацией интерфейса TextAnalyzer  *  */ public class TextAnalyzerImp implements TextAnalyzer {  	/* Константы */ 	private final int PERCENT_100 = 100; 	private final int ONE_WORD = 1; 	private final String SPACE = " ";  	// регулярное выражение: все испольуемые апострофы 	private final String ANY_APOSTROPHE = "[’]";  	// применяемый, стандартный апостроф 	private final String AVAILABLE_APOSTROPHE = "'";  	// регулярное выражение: не маленькие латинские буквы, не пробел и не 	// апостроф(') 	private final String ONLY_LATIN_CHARACTERS = "[^a-z\\s']";  	// регулярное выражение: пробелы, более двух подрят 	private final String SPACES_MORE_ONE = "\\s{2,}";  	/** 	 * Метод преобразует передаваемый текст в нижнеме регистру, производит 	 * фильтрацию текста. В тексте отсаются только латинские буквы, пробельные 	 * символы и верхний апостроф. Пробелы два и более подрят заменяются одним. 	 *  	 * @param text 	 *            {@link String} 	 * @return отфильтрованный текст 	 */ 	private String filterText(String text) {  		String resultText = text.toLowerCase().replaceAll(ANY_APOSTROPHE, AVAILABLE_APOSTROPHE) 				.replaceAll(ONLY_LATIN_CHARACTERS, SPACE).replaceAll(SPACES_MORE_ONE, SPACE);  		return resultText; 	}  	/** 	 * Метод преобразует получаемый текст в Map<{слво}, {количество}> 	 *  	 * @param text 	 *            {@link String} 	 * @return заполненный Map 	 */ 	private Map<String, Integer> getWordsMap(String text) {  		Map<String, Integer> wordsMap = new HashMap<String, Integer>();  		String newWord = "";  		Pattern patternWord = Pattern.compile("(?<word>[a-z']+)"); 		Matcher matcherWord = patternWord.matcher(text);  		// поиск слов в тексте по паттерну 		while (matcherWord.find()) {  			newWord = matcherWord.group("word");  			if (wordsMap.containsKey(newWord)) {  				// если слово уже есть в Map то увеличиваеи его количество на 1 				wordsMap.replace(newWord, wordsMap.get(newWord) + ONE_WORD);  			} else {  				// если слова в Map нет то добавляем его со значением 1 				wordsMap.put(newWord, ONE_WORD);  			} 		}  		return wordsMap; 	}  	/** 	 * Метод возвращает общее количество слов, суммируя частоту употребления 	 * слов в получаемом Map 	 *  	 * @param wordsMap 	 *            {@link Map} 	 * @return общее количество слов в тексте, по которому составлен Map 	 */ 	private int getCountOfWords(Map<String, Integer> wordsMap) {  		int countOfWords = 0;  		// считаем в цикле сумму значений для всех слов в Map 		for (Integer value : wordsMap.values()) 			countOfWords += value;  		return countOfWords; 	}  	/** 	 * Метод производит вычисление процентрого соотнашения аргумента 	 * numberXPercents от аргумента number100Percents 	 *  	 * @param number100Percents 	 *            int 	 * @param numberXPercents 	 *            int 	 * @return прочентное соотношение 	 */ 	private int getPercent(int number100Percents, int numberXPercents) {  		return (numberXPercents * PERCENT_100) / number100Percents; 	}  	/** 	 * Метод выполняет фильтрацию слов в массива, чтобы их количество покрывало 	 * заданный процент понимания текста 	 *  	 * @param wordsMap 	 *            {@link Map} 	 * @param countOfWords 	 *            int 	 * @param percent 	 *            int 	 * @return возвращает отфильтрованный массив, элементы когорого 	 *         отсорвированы по убывающей 	 */ 	private Map<String, Integer> filterWordsMap(Map<String, Integer> wordsMap, int countOfWords, int percent) {  		// LinkedHashMap - ассоциативный массив, который запоминает порядок 		// добавления элементов 		Map<String, Integer> resultMap = new LinkedHashMap<String, Integer>();  		int sumPercentOfWords = 0;  		// создаёт поток из Map с записями Entry<String, Integer>, 		// отсортированными по убыванию 		Stream<Entry<String, Integer>> streamWords = wordsMap.entrySet()                             .stream().sorted(Map.Entry.comparingByValue( 				       (Integer value1, Integer value2) -> (                                                  value1.equals(value2)) ? 0 : ((value1 < value2) ? 1 : -1)                                        )                              );  		// создаём итератор для обхода всех записей потока 		Iterator<Entry<String, Integer>> iterator = streamWords.iterator();  		// добавляем в resultMap каждую последующую запись из итератора, пока не 		// будет тостигнут заданный процент понимания 		while (iterator.hasNext() && (sumPercentOfWords < percent)) {  			Entry<String, Integer> wordEntry = iterator.next();  			resultMap.put(wordEntry.getKey(), wordEntry.getValue());  			sumPercentOfWords += getPercent(countOfWords, wordEntry.getValue());  		}  		return resultMap; 	}  	/* 	 * (non-Javadoc) 	 *  	 * @see 	 * ru.lexmin.lexm_core.TextAnalyzer#getWordStat(ru.lexmin.lexm_core.dto. 	 * ReceivedText) 	 */ 	@Override 	public WordStat getWordStat(ReceivedText receivedText) {  		WordStat wordStat = new WordStat(receivedText);  		Map<String, Integer> wordsMap = getWordsMap(filterText(receivedText.getText()));  		wordStat.setCountOfWords(getCountOfWords(wordsMap));  		wordStat.setFrequencyWords(                           filterWordsMap(wordsMap, wordStat.getCountOfWords(), receivedText.getPercent())                 );  		return wordStat; 	}  } 

Я постарался максимально подробно закомментировать все методы.

Если кратко, то происходит следующее:
Сначала из текста вырезается всё что является латинскими буквами, апострофами или пробелами. Количество пробелов более 2х подряд заменяется одним. Делается это в методе метод filterText(String text).

Далее из подготовленного текста формируется массив слов — Map<слово, количество в тексте>. За это отвечает метод getWordsMap(String text).

Подсчитываем общее количество слов методом getCountOfWords(Map<String, Integer> wordsMap)

И наконец фильтруем нужные нам слова, для того чтобы покрыть N% текста методом filterWordsMap(Map<String, Integer> wordsMap, int countOfWords, int percent)

Ставим эксперимент (выведем в консоль список слов)

package testText;  import ru.lexmin.lexm_core.TextAnalyzer; import ru.lexmin.lexm_core.TextAnalyzerImp; import ru.lexmin.lexm_core.dto.ReceivedText; import ru.lexmin.lexm_core.dto.WordStat;  public class Main {   public static void main(String[] args) {  	final int PERCENT = 80;  	TextAnalyzer ta = new TextAnalyzerImp();  	String friends = "There's nothing to tell!  He's .... тут текст двух серий первого сезона";  	ReceivedText receivedText = new ReceivedText(friends, PERCENT);  	WordStat wordStat = ta.getWordStat(receivedText);  	System.out.println("Количество слов в тексте: " + wordStat.getCountOfWords()); 	System.out.println("Количество слов, покрывающие 80% текста: " + wordStat.getFrequencyWords().size()); 	System.out.println("Список слов, покрывающих 80% текста"); 	wordStat.getFrequencyWords().forEach((word, count) -> System.out.println(word));  	}  } 

Результат

Количество слов в тексте: 1481
Количество слов, покрывающие 80% текста: 501
Список слов, покрывающих 80% текста: i, a, and, you, the, to, just, this, it, be, is, my, no, of, that, me, don’t, with, it’s, out, paul, you’r, have, her, okay, … и так далее

Заключение

В данном эксперименте мы проанализировали только две серии первого сезона и делать какие-либо выводы рано, но две серии идут около 80-90 мин и для их понимания (остальные 20% оставляем на додумывание, логику и зрительное восприятие) достаточно всего 501 слово.

P.S.: От себя хочу сказать что мои друзья заинтересовались этим экспериментом, и мы будем развивать эту тему. Было принято решение начать создание портала, на котором любой желающий сможет проанализировать интересующий его текст. По результатам таких анализов будет собираться статистика и формироваться ТОРы английских слов.

О всех наших проблемах и достижениях на этом тернистом пути я буду писать в следующих постах. Спасибо за внимание и, надеюсь, до новых встреч.

ссылка на оригинал статьи https://habrahabr.ru/post/282750/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *