Главная » C# » Реализация приложения TextProcessor в Visual C# (Sharp)

0

При реализации обработчика текста мы сталкиваемся с проблемой, каким образом исправить поток данных, чтобы он был однородным. Основными причинами приворечивых данных являются ошибки и небрежность оператора.

На рис. 10.3 показан пример вывода содержимого текстового файла с номерами предыдущих тиражей лотереи в текстовом редакторе Notepad (Блокнот). Очевидно, что содержимое в таком виде не несет легко улавливаемой смысловой информации для людей.

Но внешний вид данных, выведенных в Notepad, не является  настоящей  проблой. Когда содержимое файла загружено в другой текстовый редактор, например Vim, то оно выглядит намного более информативным (рис.  10.4).  Как  можно  веть, в Vim содержимое файла выводится отформатированным должным образом.

ПРИМЕЧАНИЕ

Текстовый  редактор  Vim  можно  загрузить  с  Web-сайта  http://www.vim.org.  Это  клон версии для  UNIX-систем,  который  можно  использовать  на Windows-системах.

Рис. 10.3. Содержимое файла с лотерейными номерами в Notepad

Рис. 10.4. Содержимое файла с лотерейными номерами в редакторе Vim

Но реальная насущная проблема заключается в структуре данных (рис. 10.5). Вио,  что данные  представлены  в другом  формате,  с дополнительными  столбцами, а формат даты в первом столбце неправильный. Что еще хуже, данные содержат повторяющуюся информацию.

Таким образом, нам необходимо, чтобы приложение могло читать поток и исприть все имеющиеся в нем проблемные аспекты. Для этого необходимо иметь оовательное понимание процесса обработки строк и разных способов хранения текста (см. главу 3). При обработке  потоковых данных необходимо знать их фоат. В данном примере мы работаем с текстом в формате ASCII и поэтому будем манипулировать битами согласно правилам таблицы соответствий ASCII.

Особое место в таблице соответствий занимают непечатаемые символы. Эти сиолы имеют соответствующие коды, но они представляются не в виде печатных символов,  а  в  виде  действия.  Например,  символ  между  одиночными  кавычками (1   1) представляет пробел, символ  \t — табуляцию, а символ  \п — возврат кареи с переходом на новую строку. Причина, по которой содержимое файла лотереых номеров не выводится отформатированным в Notepad, заключается в непечаемых символах перехода на новую строку. На рис. 10.6 выделенный код ОА указывает символ перевода строки в файле лотерейных номеров.

А на рис. 10.7 показано содержимое файла, созданного в Notepad. Для перехода на новую строку Notepad ожидает не один символ, а два: OD и ОА.

Рис. 10.7. Символы перехода на новую строку для Notepad

Расшифровка формата

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

Это задача не из легких, т. к. здесь нам опять приходится иметь дело с проблемой состояний. Мы не хотим исправить одну часть потока, только чтобы получить прлему в другой его части. Поэтому процесс исправления должен осуществляться пошагово, с проверкой после каждого шага на отсутствие нежелательных стороих эффектов.

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

в используемых в них непечатаемых символах. Поэтому первым шагом будет удение непечатаемых символов.

Далее приводится  код для  выполнения этой задачи. Он считывает текст из буфера и разбивает его на поля, после чего снова собирает содержимое в новом буфере. Код выполняет промежуточную обработку, добавляя специальные метки в виде скобок, чтобы указать содержимое текста.

using System.10; namespace TextProcessor {

// ТОГО:  Fix up this class

class LottoTicketProcessor : IProcessor { public string Process(string input) {

TextReader reader = new StringReader(input); StringBuilder retval = new StringBuilder(); while (reader.Peek() != -1) {

string lineOfText = reader.ReadLineO;

string[] splitUpText = lineOfText.Split(new char[] { ‘   ‘\t’ }); foreach (string item in splitUpText) {

retval.Append("(" + item + ")");

}

retval.Append("\n");

}

return retval.ToString();

}

}

}

В методе Process () текст сначала разбивается на строки, после чего каждая строка разбивается на отдельные поля. Можно было бы написать процедуры преобразовия текста в строки и поля самому, но для этого эффективнее использовать объект stringBuilder. Объект stringBuilder принимает в качестве параметра строку, корую   нужно   преобразовать,   а   потом   присваивается   экземпляру   интерфейса

TextReader.

Так как преобразуется каждая строка текста, то использование объекта stringBuilder будет наиболее эффективным способом создать буфер данных. Можно было бы использовать операцию +=, предоставляемую каждой строкой, но слишком частое применение этой операции отрицательно отразится на производительности прраммы. Причиной этому является необходимость отслеживания использования памяти, что становится узким местом, а также необходимость отслеживания болого числа идентификаторов ссылок на объекты.

Строки являются неизменяемым типом. Это означает, что после инициализации объекта его состояние нельзя изменить. Преимущество неизменяемых типов состоит

в  том,   что  они   позволяют  ускорить  исполнение  приложения,  т. к.   код  может* быть уверенным  в том, что  присвоенное объекту значение  никогда не изменится. А  недостатком  является то,  что для того,  чтобы  изменить  присвоенное значение, необходимо  создавать  новый  объект.  Это  и  происходит  в  случае с  применением оператора +=. Тип  stringBuilder похож на строку,  но содержащийся в нем текст можно модифицировать.

В реализации метода Process о цикл while вызывает метод Рееко , который счывает, но не удаляет значение символа из потока. Если больше нет данных для чтения,  то  метод  возвращает значение -1 .  В  противном  случае данные  имеются, и можно вызывать метод ReadLine (). Метод ReadLine () считывает символы буфа до первого символа перехода на новую строку или символа возвращения кареи (символы \п и \г, соответственно). Прочитанная строка текста присваивается переменной lineOfText. После этого применяется метод spli t о, который разбает строку на отдельные поля, в качестве разделителей между которыми примяются символы пробела и табуляции (\t) .

По возвращению управления методом spli t о отдельные поля сохраняются в маиве splitUpText. Элементы массива обрабатываются в цикле и добавляются в кец переменой retval типа stringBuilder, но при этом каждый элемент заключтся в скобки. Скобки служат видимой границей символа, чтобы можно было проверить, какие данные были считаны. Скобки применяются с единственной цью облегчения отладки. Так как мы пытаемся переформатировать поток, то в кец содержимого переменной retval добавляется символ перехода на новую стру (\п).

Когда все строки текста и все поля в каждой строке будут обработаны, возвращаея строка, содержащаяся в объекте stringBuilder, для чего применяется метод Tostring объекта. Исполнение данного кода предоставляет информацию о количтве символов в каждой строке текста и о том, как нужно отформатировать текстый файл. Таким образом, мы получаем представление о структуре файла.

Далее приводится вывод для файла lotto.txt:

(2000.01.15)(6)(10)(25)(26)(38)(42)(20)

(2000.01.19)(2)(16)(18)(23)(32)(43)(26)

(2000.01.22) (4) (5) (6) (24) (34) (38) (9)

(2000.01.26) (3) (20) (22) (24) (34) (39) (9)

(2000.01.29)(7)(12)(13)(34)(38)(39)(28)

(2000.02.02)(1)(18)(22)(28)(35)(43)(32)

(2000.02.05)(4)(13)(15)(31)(32)(45)(37)

(2000.02.09) (1) (29) (31) (34) (39) (41) (25)

(2006-12-27) (11) (13) (17) (21) (24) (26) (38) (578199) (735993) О ()

(2006-12-30) (3) (13) (22) (30) (35) (41) (34) (142968) (472679) ()

О О

О

(2007-01-03)(5)(24)(37)(39)(41)(44)(9)(049802)(133875)() О

(2007-01-06)(3)(7)(23)(27)(30)(32)(38)(687442)(874814)() О

(2007-01-10) (7) (9) (13) (23) (35) (37) (25) (039498) (648301) О ()

(2007-01-13) (3) (17) (22) (37)(39)(43)(34)(968842)(162860) ()О

(2007-01-17) (12) (16) (27) (33) (37) (41) (24) (663824) (765917) О ()

И з   полученног о   вывод а   м ы   видим ,   чт о   в   файл е   имеютс я   следующи е   проблемы , требующи е   исправления :

•   пусты е  строк и  текста ,  н е  имеющи е  определенны х  данных ;

•   в  некоторы х  строчка х  в  конц е  имеютс я  пусты е  поля ;

•   некоторы е  дат ы  указан ы  в  неправильно м  формате ;

•   некоторы е  дат ы   имею т  дубликаты ,  которы е  нужн о  удалить ;

•   некоторы е  строчк и   имею т  слишко м   мног о   полей .   На м   необходим о  определить , каки е  пол я  м ы  хоти м  оставить ,  а  каки е удалить .

ПРИМЕЧАНИЕ

При обработке и упорядочивании потоков важно сначала разбить поток на состаяющие и определить, с чем мы имеем дело. Не следует делать никаких  предполений до тех пор, пока вы не исследовали отдельные элементы данных. Только после этого вы сможете определить, что необходимо предпринять, чтобы исправить  поток.

Исправление потока

В   конечно м   решени и   применяетс я   ко д  дл я   разбивк и  текст а   н а  строчк и   и   строче к н а  поля :

IList<string> _dates = new List<string>О;

public string Process(string input) { TextReader reader = new StringReader(input); StringBuilder retval = new StringBuilder)); while (reader.Peek() != -1) {

string lineOfText = reader.ReadLine();

string[] splitUpText = lineOfText.Split(new chart] {‘ ‘, ‘\t’ }); if (_dates.Contains(splitUpText[0])) {

continue;

}

if (splitUpText[0].Length == 0) { continue;

}

if (splitUpText[0].Contains("-")) {

string[] dateSplit = splitUpText[0].Split(‘-‘);

string newDate = dateSplit[0] + "." + dateSplit[l] + "." + dateSplit[2];

if (_dates.Contains(newDate)) { continue;

}

_dates.Add(newDate); retval.Append(newDate);

for (int cl = 1; cl < 8; cl++) { retval.Append(" " + splitUpText[cl]);

}

}

else {

_dates.Add(spli tUpText[0]); retval.Append(lineOfText);

}

retval.Append("\n");

}

return retval.ToString() ;

}

ПРИМЕЧАНИЕ

В исходном коде,  который   можно  загрузить  из  Интернета,  демонстрируютс я  отделые шаги по упорядочиванию потока данных. Промежуточные шаги разработки в иодном коде называются ProcessOl (),…, ProcessOS ().

Теперь  рассмотрим  в  подробностях,   каким  образом  этот  код  исправляет  обнаренные  погрешности  в  содержимом  файла лотерейных номеров.

Пустые текстовые строки

Пустые текстовые  строки  удаляются  следующим  кодом:

if (splitUpText[0].Length == 0) { continue;

}

При обработке файла lotto.txt для пустой строки создается массив из одного  элента. Поэтому мы знаем, что если длина первого элемента поля равна нулю, то данную  строчку  текста  следует  игнорировать.

Пустые и лишние поля

Следующей  проблемой,  которую  нужно  исправить,  являются  пустые   текстовые поля в конце строки. Данную проблему, наверное,  можно  было  бы  решить  спосом,  подобным  способу,  использованному  для   решения   предыдущей   проблемы. Но это решило бы только данную конкретную проблему, а нам необходимо  наиться думать в общем плане и смотреть, можно ли, решая одну проблему, заодно решить еще какую-либо. В данном случае решение проблемы  пустых  полей  может быть также  применимо  к проблеме лишних  полей.

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

retval.Append(newDate);

for (int cl = 1; cl < 8; cl++) { retval.Append("  " + splitUText[cl]);

}

В первой строчке кода дата добавляется в  буфер  объекта  stringBuilder (перемеая retval). Потом запускается цикл for, в котором в stringBuilder копируется пробел  и  поля с первого по седьмое.

Неправильный формат даты

Для некоторых полей в качестве разделителя частей даты используется точка, а для других — дефис. Правильным разделителем является точка. Код для исправления формата  выглядит  следующим  образом:

if (splitUpText[0].Contains("-")) {

string[] dateSplit = splitUpText[0].Split С-‘);

string newDate = dateSplit[0] + "." + dateSplit[l] + "." + dateSplit[2];

}

Сначала выполняется проверка, не содержит ли первое поле, т. е.  поле даты, дефис. Для этого применяется оператор if с методом  containsO. Если  поле даты  содеит дефис, значит, формат даты неправильный, и его нужно  исправить.  Для  этого поле разделяется по дефисам на три части,  каждая  представляет часть даты  (месяц, год  и  день).  Потом  эти  подполя  снова  объединяются,  но  с  использованием  точки в  качестве  разделителя.  Полученный  результат  присваивается  переменной  newDate.

Повторяющиеся даты

Последней  проблемой,  которую  нам  нужно  решить,  являются  повторяющиеся  даты в потоке данных. Эта проблема решается с помощью следующего кода (соответсующие  части  кода  выделены  жирным  шрифтом):

if (_<Sates.Contains (splitUpText [0])) { continue;

if (splitUpText[0].Length == 0) { continue;

}

if (splitUpText[0].Contains("-")) {

string[] dateSplit = splitUpText[0].Split(‘-‘); string newDate =

dateSplit[0] + "." + dateSplit[1] + "." + dateSplit[2] ;

i £ (_dates.Contains(newDate)) { continue;

_dates.Add(newDate);

retval.Append(newDate);

for (int cl = 1; cl < 8; cl++) { retval.Append(" " + splitUpText[cl]);

}

}

else {

„dates.Add(splitUpText[0]);

retval.Append(lineOfText);

}

Изо всех проблем, которые нам нужно было решить, проблема повторяющихся дат является самой сложной, т. к. код для ее решения разбросан по разным местам. При обработке потока данных код создает список дат. В список добавляются только те даты, которых в нем еще нет. Проверка на наличие конкретной даты в списке волняется с помощью метода contains (). Этот метод, предоставляемый объектами списка .NET, проверяет переданный ему объект на совпадение с одним из элемеов  списка.  Большинство  объектов  списка  реализует  данный  метод,  сравнивая в цикле с помощью метода Equals () кандидата на добавление в список с каждым элементом списка. Но здесь имеется проблема, заключающаяся в том, что для пользовательского типа действием по умолчанию метода Equals () является прерка равенства одного ссылочного значения другому. В таких случаях необходимо реализовывать свой метод Equals ().

ПРИМЕЧАНИЕ

Необходимость разбрасывать код для выполнения одной задачи по разным местам является признаком того, что получившийся код будет трудно поддерживать. В таком случае следует внимательно изучить этот код и, если это является возможным, перисать его. Конечно же, все зависит он множества факторов. Поэтому иногда просто приходится использовать код как есть, протестировать его и надеяться, что он будет работать  должным  образом.

На этом  последнем решении создание консольного приложения TextProcessor зерщено. Теперь при обработке файла lotto.txt выходной поток данных будет оорматирован должным образом.

Источник: Гросс  К. С# 2008:  Пер. с англ. — СПб.:  БХВ-Петербург, 2009. — 576 е.:  ил. — (Самоучитель)

По теме:

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