Показаны сообщения с ярлыком ООП. Показать все сообщения
Показаны сообщения с ярлыком ООП. Показать все сообщения

понедельник, 14 мая 2012 г.

Парсер-анализатор химических формул. C#

    В данном посте я расскажу о парсинге химических формул с целью дальнейшей адаптации для отображения в HTML.

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


Модель
    Функциональная схема может иметь следующий вид:


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

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


public enum LexemClass { undefined, element, number, separator, factor, valence, atom };

public class Lexem
{
     private string str_set;
     private LexemClass lex_class;
       
     public string Set
     {
         get { return this.str_set; }
         set { this.str_set = value; }
     }
     public LexemClass Type
     {
         get { return lex_class; }
         set { lex_class = value; }
     }
}


    Здесь LexemClass - перечисление, содержащее набор предопределенных классов лексем.
  В процессе лексического анализа выделенным из входной последовательности лексемам присваивается определенный класс, который является идентификатором типа лексемы. Тип лексемы является ключевым "свойством", в прямом и переносном смыслах этого слова, класса С# Lexem, которое в дальнейшем используется непосредственно для  преобразования набора лексем в HTML разметку.
    В рамках семантики химических формул я выделил следующие классы лексем:
  1. element - химический элемент в составе формулы;
  2. number - количество молекул вещества;
  3. separator - разделитель (круглые и квадратные скобки, ",");
  4. factor - знак умножения;
  5. valence - валентность атомов элемента;
  6. atom - количество атомов;
  7. undefined - рабочий класс по умолчанию.
    Класс Lexem определяет непосредственно лексему, как объект модели предметной области. Содержит два члена-переменных str_set и lex_class типа LexemClass, взаимно однозначно определяющих лексему и ее тип, и два свойства доступа к приватным переменным Set и Type.



public class FormulaParser
{
private string str_formula, buffer;
private char symbol;
private int i;
private LexemClass temp_defined_type;
private List<Lexem> LexemsList;

public string Formula
{...}

public List<Lexem> Lexems
{...}

public FormulaParser(string str_input)
{
str_formula = str_input;
temp_defined_type = LexemClass.undefined;
...
}

public void ParseFormula()
{
int formula_length = Formula.Length;

while(i<formula_length)
{
// Если буква
symbol = Convert.ToChar(Formula[i]);
if (char.IsLetter(symbol))
{
buffer+=symbol.ToString();
if(i!=formula_length-1)
{
if(!char.IsLetter(Convert.ToChar(Formula[i+1])))
{
Lexems.Add(new Lexem{Set=buffer, Type=LexemClass.element});
buffer=string.Empty;
}
}
else
Lexems.Add(new Lexem { Set = buffer, Type=LexemClass.element});
}

// Если цифра
else if (char.IsDigit(symbol))
{
buffer+=symbol.ToString();
if (i!=formula_length-1)
{
if (!char.IsDigit(Convert.ToChar(Formula[i + 1])) &&
    !char.Equals(Convert.ToChar(Formula[i + 1]), ','))
{
Lexems.Add(new Lexem{Set = buffer,Type =
Enum.Equals(temp_defined_type, LexemClass.undefined) ?
LexemClass.atom : (temp_defined_type)});

temp_defined_type = LexemClass.undefined;
buffer = string.Empty;
}
}
else
{
Lexems.Add(new Lexem{Set = buffer,Type =
Enum.Equals(temp_defined_type, LexemClass.undefined) ?
LexemClass.number : (temp_defined_type)});
temp_defined_type = LexemClass.undefined;
}
}

// Если ","
else if (char.Equals(symbol, ','))
{
// если предыдущая лекскма - элемент -> это разделитель
if (Lexems.Last().Type.Equals("element"))
Lexems.Add(new Lexem { Set = symbol.ToString(),
Type = LexemClass.separator });
else
buffer += symbol.ToString();
}

// Если "[" или "("
...

// Если "]" или ")"
else if (symbol.Equals(']') || symbol.Equals(')'))
...

// Если "+"
else if (symbol.Equals('+'))
{
buffer += symbol.ToString();
if (i != formula_length - 1)
{
if (!char.Equals(Convert.ToChar(Formula[i + 1]), '+'))
{
buffer = buffer.Length>1?(buffer.Length.ToString() + "+"):("+");
Lexems.Add(new Lexem { Set = buffer, Type = LexemClass.valence });
buffer = string.Empty;
}
}
}

// Если "∙"
...

// Не забываем об инкрементном счетчике
i++;
}
}
}


    Класс FormulaParser помимо свойств Formula, Lexems и членов-переменных содержит конструктор, который в качестве параметра получает объект типа String - исходную формулу для дальнейшей обработки. Словарь лексем будет содержаться во внутренней переменной LexemsList  типа List<Lexem>Обработка выполняется в методе ParseFormula().
    Принцип работы лексического анализатора заключается в последовательной проверке каждого символа входной строки с поэтапным выделением и записью в словарь лексем.
    Рассмотрим некоторые части внутренней реализации метода ParseFormula(). Итак, в методе выполняется проверка массива символов строки. Наиболее важные и емкие части кода - это проверка букв и цифр входной последовательности.
    Если i-й символ является буквой - метод начинает формировать лексему с классом element заполняя внутреннюю член-переменную buffer. Решение о занесении лексемы в коллекцию принимается на основе изменения типа i+1-го символа входной последовательности или по достижению ее конца.
    Как можно увидеть, данная реализация предусматривает выделение частей формулы в виде совокупности химических элементов, а не выделение каждого отдельного химического элемента в отдельную лексему словаря. Для достижения подобного результата можно доработать алгоритм выделения лексем опираясь на регистр символов входной строки.
     Если i-й символ является цифрой - опять заполняется переменная buffer. Если следующий символ не является цифрой или знаком "," (для дробных значений) - принимается решение о занесении значения переменной buffer в список лексем. Тип лексемы определяется в зависимости от того, каким значением инициализирована вспомогательная переменная temp_defined_type.
     Если i-й символ является символом ",". "," может быть либо разделителем между химическими элементами, либо - частью дробного числа. Здесь я использовал несколько иной подход к формированию списка лексем. Решение о занесении n-ой лексемы в список принимается на основе типа класса предыдущего n-1 элемента этого списка.
   Последняя часть кода - если символ является знаком "+". В этом случае формируется лексема с классом valence. Как и прежде, решение о занесении лексемы в коллекцию принимается на основе изменения типа i+1-го символа входной последовательности. Перед инициализацией нового элемента коллекции LexemsList в переменной buffer формируется строка, которая состоит из длинны самой строки данных и символа "+". 

public static class HTMLBuilder
{
public static string GenerateHTML(List<Lexem> LexemsList)
{
     string str_HTML="<span style=\"color:Black;font-size:11pt;\">";
            foreach (var lexem in LexemsList)
            {
                switch (lexem.Type)
                {
                    case LexemClass.atom:
                        str_HTML += "<sub>" + lexem.Set + "</sub>";
                        break;
                    case LexemClass.element:
                    case LexemClass.separator:
                    case LexemClass.number:
                    case LexemClass.factor:
                        str_HTML += lexem.Set;
                        break;
                    case LexemClass.valence:
                        str_HTML += "<sup>" + lexem.Set + "</sup>";
                        break;
                }
            }
            str_HTML += "</span></p>";
            
            return str_HTML;
        }
}

    В завершении самая простая часть работы - обработка коллекции лексем.
    Для генерации HTML-разметки можно использовать специальные классы C#, как, например, класс TagBuilder (http://msdn.microsoft.com/en-us/library/system.web.mvc.tagbuilder.aspx). В данном случае я делал разметку вручную в виду ее тривиальности.
    Статический класс HTMLBuilder имеет один метод - GenerateHTML(), который принимает в качестве аргумента коллекцию типа List<Lexem> и возвращает строку со сформированной разметкой.
   Работа метода GenerateHTML() заключается в последовательной проверке свойства Type всех элементов коллекции List<Lexem>. В зависимости от значения этого свойства для каждого элемента из списка лексем формируется свой вариант HTML-разметки.
    В данном случае для перевода атомного числа и валентности в подстрочный и соответственно надстрочный регистры используются теги HTML <sub></sub> и <sup></sup>.
Пример


суббота, 21 января 2012 г.

Объектно-ориентированный анализ и проектирование с примерами приложений Третье издание


Object-oriented analysis and design with application Third edition
Авторы: Буч(Booch), Максимчук(Maksimchuk)



Общее впечатление

Довольно неоднозначная книга. Палитра ощущений при прочтении, у меня лично, менялась от сильной заинтересованности до безразличной сонливости. Видимо, мое такое неоднозначное восприятие обусловлено тем, что книга по своей сути – для обучения проектировщиков и менеджеров, а не девелоперов. Как следствие - некоторые главы были для меня не интересны, а некоторые - наоборот.
Раньше я сталкивался, в основном, с книгами по программированию, поэтому данное издание выглядело довольно контрастно, в сравнении с остальной прочитанной литературой.
Целевая аудитория

Мое мнение - книга для новичков-проектировщиков. Присутствует хорошее поясняющее изложение, детальное описание, UML – схемы. Рассматриваются разнообразные международные стандарты в областях проектирования и разработки и  соприкасаемых с ними областях. Приводятся ссылки и цитаты на труды специалистов в областях проектирования и ООА\ ООP\OOD. Есть словарь терминов.
Особенности

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


Основной недочет русской редакции – местами, довольно объемные информационные блоки-врезки, вставлены не продуманно. Они перебивают основные текстовые блоки, в результате – теряется смысл, приходится пролистывать несколько страниц назад/вперд, чтоб вспомнить о чем шла речь в тексте. Особенно четко чувствуется дискомфорт, когда читаешь главы с примерами.
По главам


Первые 4 главы (1-ая часть) - ввод в ООП. Написаны, конечно, очень четко, на 10 баллов. Я бы рекомендовал для тех, кто собирается изучать данную тему.
 5-ая глава посвящена рассмотрению инструмента проектирования UML. В основном, приводятся всевозможные виды UML-диаграмм. Так как я не изучал UML то мне сложно однозначно охарактеризовать степень качества изложенного.
В 6-ой главе рассматриваются основные принципи объектно-ориентированного анализа и проектирования. Также отлично все расписано, как и в первой части книги.
7-ая глава показалась мне немного мутной. Я так понял, что эта глава ориентирована на менеджеров а не разработчиков, видимо поэтому мне было откровенно скучно ее читать. Написано много о разных аспектах связанных с околоразработочной суетой.
Главы 8-12 – примеры. 5 абстрактных примеров для наглядной демонстрации использования связки ООА\ООП\ООП. Весь процесс разработки подается постепенно от проектирования, в первом примере, – до реализации, в последнем.
Библиография очень большая и занимает около 80 страниц, но все специализированные научные труды датируются, в основном, 60-90-ми годами (здесь, конечно, следует учитывать тот факт, что это 3-е переиздание).