Pull to refresh

Покорим Ruby вместе! Капля шестая

Reading time 6 min
Views 25K
Сегодня мы с вами создадим наше первое полноценное приложение на Руби, попутно обучаясь новым методам классов String и File и регулярными выражениями.

Наше приложение: Текстовый анализатор


Собственно программа простая: она будет считывать текстовый файл, анализировать его по некоторым паттернам, считать статистику и выводить результат. Руби замечательно подходит для анализа документов и текстов с помощью регулярных выражений и методов scan и split. В этом приложении мы сконцентрируемся на простом и быстром программировании и не будем организовывать объектно-ориентированную структуру.


Основные возможности


Вот список возможностей, которые мы должны реализовать:
  • подсчет символов
  • подсчет символов без пробелов
  • подсчет строк
  • подсчет слов
  • подсчет абзацев
  • среднее число слов в предложении
  • среднее число предложений в абзаце

Реализация


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

Создайте новый, пустой файл исходников и сохраните его как analyzer.rb.

Ищем какой-нибудь текст


Перед тем, как начать писать код, неоходимо найти кусок текста для тестов. В нашем примере мы будем использовать первую часть рассказа Оливер Твист, которую вы можете загрузить здесь. Сохраните файл под именем text.txt там же, где находится analyzer.rb

Загрузка файла и подсчет строк


Настало время для кодинга! Первый шаг — загрузка файла. Руби предоставляет достаточный список методов для файловых манипуляций в классе File. Вот код, который откроет наш text.txt:

File.open("text.txt").each { |line| puts line }

Внесите код в analyzer.rb и запустите программу. В результате вы увидите бегущие по экрану строки текста.

Вы запрашиваете класс File открыть text.txt, а затем, как в случае с массивами, вызываете метод each прямо на файл, заставляя передавать каждую строку одну за другой во внутренний блок, где puts отпраляет их на экран. Отредактируйте код, чтобы он выглядел так:

line_count = 0
File.open("text.txt").each { |line| line_count += 1 }
puts line_count


Вы определяете переменную line_count, чтобы хранить в ней счетчик строк, затем окрываете файл и итерируете по строкам, увеличивая line_count на 1 каждый раз. В конце вы выводите результат на экран (около 127 в нашем примере). У нас есть первый кусочек для статистики!

Вы посчитали строки, однако у нас по-прежнему нет возможности посчитать слова, предложения, абзацы. Это легко исправить. Давайте немного изменим код и добавим переменную text, чтобы налету собрать в нее все строки:

text=''
line_count = 0
File.open("text.txt").each do |line| 
   line_count += 1
   text << line
end

puts "#{line_count} lines"


В отличие от предыдущего кода, этот представляет переменную text и добавляет в нее каждую строку. Когда итерация окончена, в text находится весь наш текст.

Казалось бы код максимально лаконичен и прост, однако в File также существуют другие методы, которые могут быть использованы в нашем случае. Например, мы можем переписать наш код так:

lines = File.readlines("text.txt")
line_count = lines.size
text = lines.join

puts "#{line_count} lines"


Намного проще! Метод readlines считывает весь файл в массив, строка за строкой.

Считаем символы


Так как мы собрали весь файл в text, мы можем использовать применимый для строк метод length, который вернет размер (количество символов) в строке, и, соответственно, во всем нашем тексте. Допишем код:

total_characters = text.length
puts "#{total_characters} characters"


Еще один элемент статистики, который нам необходим — это подсчёт символов без пробелов. Для этого воспользуемся методами замены символов. Вот пример:

puts "foobar".sub('bar', 'foo') #foofoo

Метод sub нашел набор символов, переданный в первом параметре, и заменил их на символы из второго. Однако sub находит и изменяет только одно, первое встретившееся вхождение символов, метод gsub производит все возможные замены за раз.

Регулярные выражения


А как насчет замены более сложных патернов? Для этого используются регулярные выражения (regular expression). Целые книги были написаны на эту тему, поэтому мы ограничимся лишь кратким обзором этого мощного инструмента для работы с текстом. В Руби регулярные выражения создаются с помощью заключения патерна между слэшами (/pattern/). И в Руби, естественно, это также объекты. Например, вы можете задать следующий патерн для того, чтобы выбрать строки, содержащие текст Perl или текст Python: /Perl|Python/. В слэшах заключен наш патерн, состоящий из двух необходимых нам слов, разделенных прямой чертой (пайпом, pipe, |). Это символ означает «или то, что слева, или то, что справа». Вы также можете использовать скобки, как в числовых выражениях: /P(erl|ython)/.

В патернах можно реализовать повторения: /ab+c/ (это не сложение, рассматриваем как a, затем b+, затем с). Такому патерну соответствует строка, содержащая вхождение a, затем одного или более b, и, наконец, с. Заменим плюс на звездочку, теперь /ab*c/ соответствует строка содержащая a, ноль или больше b и с. + и * — это так называемые квантификаторы, чьё назначение, я думаю, понятно

Мы также можем выбирать строки, содержащие определенные символы. Наиболее простой пример — это патерны классов символов, например, \s означает whitespace (это пробелы, табы, переносы строки и т. п.), под \d попадают все числа, и др. Вот сводка, взятая из учебника по Руби на Wikibooks:



Продолжаем считать символы


Итак, теперь мы знаем как убрать из строки все ненужные символы:

total_characters_nospaces = text.gsub(/\s+/, '').length
puts "#{total_characters_nospaces} characters excluding spaces"


Добавим этот код в конец нашего файла и переходим к подсчету слов.

Считаем слова


Для подсчета количества слов есть два подхода:
  1. Посчитать число групп непрерывных символов, используя метод scan
  2. Разбить текст по пробельным символам и посчитать получившиеся фрагменты с помощью split и size.

Пойдем по второму пути. По умолчанию (без параметров) split разобьет строку по пробельным символам и поместит фрагменты в массив. Нам остается только узнать длину массива. Дописываем код:

word_count = text.split.length
puts "#{word_count} words"


Считаем предложения и абзацы


Если нам понятно, как был реализован подсчет символов, то с предложениями и абзацами сложностей не будет. Единственное отличие в патерне, по которому мы разбиваем текст. Для предложения это точка, вопросительный и восклицательный знаки, для абзацев — двойной перенос строки. Вот код:

paragraph_count = text.split(/\n\n/).length
puts "#{paragraph_count} paragraphs"
sentence_count = text.split(/\.|\?|!/).length
puts "#{sentence_count} sentences"


Думаю, код понятен. Единственную сложность может составить патерн у предложений. Однако, он только выглядит страшно. Мы не можем просто ставить символы . и ? — мы их «экранируем» наклонной чертой.

Подсчет остальных значений


У нас уже есть количество слов, абзацев и предложений в переменных word_count,
paragraph_count и sentence_count соответственно, поэтому дальше работает только арифметика:

puts "#{sentence_count / paragraph_count} sentences per paragraph (average)"
puts "#{word_count / sentence_count} words per sentence (average)"


Исходный код


Мы дополняли исходный код шаг за шагом, поэтому логика и вывод на экран у нас смешались. Давайте расставим все на свои места. Далее только косметические изменения:

lines = File.readlines("text.txt")
line_count = lines.size
text = lines.join
word_count = text.split.length
character_count = text.length
character_count_nospaces = text.gsub(/\s+/, '').length
paragraph_count = text.split(/\n\n/).length
sentence_count = text.split(/\.|\?|!/).length

puts "#{line_count} lines"
puts "#{character_count} characters"
puts "#{character_count_nospaces} characters excluding spaces"
puts "#{word_count} words"
puts "#{paragraph_count} paragraphs"
puts "#{sentence_count} sentences"
puts "#{sentence_count / paragraph_count} sentences per paragraph (average)"
puts "#{word_count / sentence_count} words per sentence (average)"


Если всё накоденное выше тебе понятно — мои поздравления! Значит мы не зря «капали» ;)

Эпилог


Еще одна крупная капля. Написана более для совсем начинающих программистов, для знающих другие ЯП — это хорошая возможность сравнить способности Руби. Предлагаю и дальше периодически часто делать подобные выпуски с разбором готовых решений. Спасибо за пример Питеру Куперу! Жду отзывов и замечаний! Ждем следущую каплю ;)

Tags:
Hubs:
+19
Comments 47
Comments Comments 47

Articles