C2h8n2燃烧化学式:化学式解析器 C++(chemical formula naming calculator)

我目前正在开发一个程序,该程序可以解析一个化学式并返回分子量和百分比组成。以下代码对诸如 H2O,LiOH,CaCO3,甚至 C12H22O11的化合物都非常有效。但是,它不能理解具有多

我不找人一定要为我写的程序,但只是给我一些提示,我怎么可能完成这样的任务。

目前,程序遍历输入的字符串raw_molecule,首先找到每个元素的原子序数,以存储在向量中(我使用map<string, int>来存储名称和原子 #)。

bool Compound::pString() {
map<string,int>::const_iterator search;
string s_temp;
int i_temp;
for (int i=0; i<=raw_molecule.length(); i++) {
    if ((isupper(raw_molecule[i]))&&(i==0))
        s_temp=raw_molecule[i];
    else if(isupper(raw_molecule[i])&&(i!=0)) {
        // New element- so, convert s_temp to atomic # then store in v_Elements
        search=ATOMIC_NUMBER.find (s_temp);
        if (search==ATOMIC_NUMBER.end()) 
            return false;// There is a problem
        else
            v_Elements.push_back(search->second); // Add atomic number into vector
        s_temp=raw_molecule[i]; // Replace temp with the new element
    }
    else if(islower(raw_molecule[i]))
        s_temp+=raw_molecule[i]; // E.g. N+=a which means temp=="Na"
    else
        continue; // It is a number/parentheses or something
}
// Whatever's in temp must be converted to atomic number and stored in vector
search=ATOMIC_NUMBER.find (s_temp);
if (search==ATOMIC_NUMBER.end()) 
    return false;// There is a problem
else
    v_Elements.push_back(search->second); // Add atomic number into vector
// --- Find quantities next --- // 
for (int i=0; i<=raw_molecule.length(); i++) {
    if (isdigit(raw_molecule[i])) {
        if (toInt(raw_molecule[i])==0)
            return false;
        else if (isdigit(raw_molecule[i+1])) {
            if (isdigit(raw_molecule[i+2])) {
                i_temp=(toInt(raw_molecule[i])*100)+(toInt(raw_molecule[i+1])*10)+toInt(raw_molecule[i+2]);
                v_Quantities.push_back(i_temp);
            }
            else {
                i_temp=(toInt(raw_molecule[i])*10)+toInt(raw_molecule[i+1]);
                v_Quantities.push_back(i_temp);
            }
        }
        else if(!isdigit(raw_molecule[i-1])) { // Look back to make sure the digit is not part of a larger number
            v_Quantities.push_back(toInt(raw_molecule[i])); // This will not work for polyatomic ions
        }
    }
    else if(i<(raw_molecule.length()-1)) {
        if (isupper(raw_molecule[i+1])) {
            v_Quantities.push_back(1);
        }
    }
    // If there is no number, there is only 1 atom. Between O and N for example: O is upper, N is upper, O has 1.
    else if(i==(raw_molecule.length()-1)) {
        if (isalpha(raw_molecule[i]))
            v_Quantities.push_back(1);
    }
}
return true;
}

这是我的第一篇文章,所以如果我包含的信息太少(或者太多),请原谅我。

6

虽然您可能能够做一个类似 ad-hoc scanner 的事情,可以处理一个级别的括号,但用于这样的事情的规范技术是编写一个真正的解析器。

有两种常见的方法可以做到这一点...

Recursive descent

基于语法规范文件的机器生成的自底向上解析器。

(从技术上讲,还有第三类,PEG,即机器生成-自上而下。)

无论如何,对于情况 1,当您看到(时,您需要对解析器进行递归调用,然后在)令牌上从此递归级别返回。

通常创建一个树状的内部表示;这被称为一个语法树,但在你的情况下,你可以跳过它,只从递归调用返回原子量,添加到你将从第一个实例返回的级别。

对于情况 2,您需要使用yacc之类的工具将语法转换为解析器。

4

您的解析器理解某些事情。它知道当它看到N时,这意味着“氮原子类型”。当它看到O时,它意味着“氧原子类型”。

这与 C++ 中标识符的概念非常相似。当编译器看到int someNumber = 5;时,它说:“存在一个名为someNumberint类型的变量,其中存储了数字5。”

回到你的原子解析器。当你的解析器看到一个原子后跟一个数字时,它知道把这个数字应用到那个原子上。所以O2意味着“2 个氧原子类型”。N2意味着“2 个氮原子类型”。

这对您的解析器意味着什么。这意味着看到一个原子并不足够。这是一个好的开始,但不足以知道分子中存在多少个原子。它需要读取下一件事。因此,如果它看到O后跟N类型,则它知道0 的原子后面是“If it”。

这就是你目前拥有的。但它是错误的。因为数字并不总是修改原子;有时,它们会修改原子的。如(NH4)2SO4

所以现在,您需要更改您的解析器的工作方式。当它看到O时,它需要知道这不是“氧气原子类型”。它是一个“包含氧气”。O2是“2 组包含氧气”。

一个组可以包含一个或多个原子。因此,当您看到(时,您就知道您正在创建一个。因此,当您看到(...)3时,您会看到“3 个包含...的组”。

那么,什么是(NH4)2?它是“2 个含有 [1 个含氮基团,然后是 4 个含氢基团] 的基团”。

这样做的关键是理解我刚刚写的内容。组可以包含其他组。在组中有嵌套。你如何实现嵌套?

好吧,你的解析器看起来像这样目前:

NumericAtom PAtom(input)
{
  Atom = ReadAtom(input); //Gets the atom and removes it from the current input.
  if(IsNumber(input)) //Returns true if the input is looking at a number.
  {
    int Count = ReadNumber(input); //Gets the number and removes it from the current input.
    return NumericAtom(Atom, Count);
  }
  return NumericAtom(Atom, 1);
}
vector<NumericAtom> P(input)
{
  vector<NumericAtom> molecule;
  while(IsAtom(input))
    molecule.push_back(PAtom(input));
  return molecule;
}

您的代码调用PAtom(),直到输入运行干,将每个 atom + count 存储在一个数组中。

您需要做的是停止解析原子,您需要解析,它们要么是单个原子,要么是由()对表示的一组原子。

Group PGroup(input)
{
    Group myGroup; //Empty group
    if(IsLeftParen(input)) //Are we looking at a `(` character?
    {
        EatLeftParen(input); //Removes the `(` from the input.
        myGroup.SetSequence(PGroupSequence(input)); //RECURSIVE CALL!!!
        if(!IsRightParen(input)) //Groups started by `(` must end with `)`
            throw PError("Inner groups must end with `)`.");
        else
            EatRightParen(input); //Remove the `)` from the input.
    }
    else if(IsAtom(input))
    {
        myGroup.SetAtom(ReadAtom(input)); //Group contains one atom.
    }
    else
        throw PError("Unexpected input."); //error
    //Read the number.
    if(IsNumber(input))
        myGroup.SetCount(ReadNumber(input));
    else
        myGroup.SetCount(1);
    return myGroup;
}
vector<Group> PGroupSequence(input)
{
    vector<Group> groups;
    //Groups continue until the end of input or `)` is reached.
    while(!IsRightParen(input) and !IsEndOfInput(input)) 
        groups.push_back(PGroup(input));
    return groups;
}

这里最大的区别是PGroupPAtom函数的模拟)将调用PGroupSequence。它将调用PGroup。它可以调用PGroupSequence。等等。AGroup可以包含一个原子或一个Groups 的序列(例如7)。

当函数可以调用自己(直接或间接)时,它被称为recursion。这很好,只要它不无限递归。而且没有机会,因为它只会在每次看到(时递归。

那么这是如何工作的呢?好吧,让我们考虑一些可能的输入:

NH3

PGroupSequence被调用。它不在 input 或)的末尾,因此它调用PGroup

PGroup看到一个N,它是一个原子。它将这个原子添加到Group。然后它看到一个H,它不是一个数字。因此它将Group的计数设置为 1,然后返回Group

回到PGroupSeqeunce,我们将返回的组存储在序列中,然后在我们的循环中迭代,我们没有看到输入或)的结束,所以它调用PGroup

PGroup看到一个H,这是一个原子。它将这个原子添加到Group。然后它看到一个3,这是一个数字。因此,它读取这个数字,将其设置为Group的计数,并返回Group

回到PGroupSeqeunce,我们将返回的Group存储在序列中,然后在我们的循环中迭代。我们没有看到),但是我们do看到输入的结束。所以我们返回当前的vector<Group>

(NH3)2

PGroupSequence被调用。它不在 input 或)的末尾,因此它调用PGroup

PGroup看到一个(,它是Group的开始。它吃掉这个字符(从输入中删除它),并在Group上调用PGroupSequence

PGroupSequence不在输入或)的末尾,因此它调用PGroup

PGroup看到一个N,它是一个原子。它将这个原子添加到Group。然后它看到一个H,它不是一个数字。因此它将组的计数设置为 1,然后返回Group

回到PGroupSeqeunce,我们将返回的组存储在序列中,然后在我们的循环中迭代,我们没有看到输入或)的结束,所以它调用PGroup

PGroup看到一个H,这是一个原子。它将这个原子添加到Group。然后它看到一个3,这是一个数字。因此,它读取这个数字,将其设置为Group的计数,并返回Group

回到PGroupSeqeunce,我们将返回的组存储在序列中,然后在我们的循环中迭代。我们没有看到输入的结束,但是我们dosee)。所以我们返回当前的vector<Group>

回到第一次调用PGroup,我们得到vector<Group>。我们把它作为一个序列插入我们当前的Group。我们检查下一个字符是否是),吃掉它,然后继续。我们看到一个2,这是一个数字。所以它读取2

现在,way回到原始的PGroupSequence调用,我们将返回的Group存储在序列中,然后在我们的循环中迭代。我们没有看到),但是我们do看到输入的结束。所以我们返回当前的vector<Group>

这个解析器使用递归来“下降”到每个组中。因此,这种解析器被称为“递归下降解析器”(对于这种事情有一个正式的定义,但这是对概念的一个很好的理解)。

3

在编写程序之前和编写程序时使用语法是有帮助的,并且可能会被输入到解析器生成器中 (如 DigitalRoss 所述)。

例如,没有多原子离子的简单化合物的规则如下:

Compound:  Component { Component };
Component: Atom [Quantity] 
Atom: 'H' | 'He' | 'Li' | 'Be' ...
Quantity: Digit { Digit }
Digit: '0' | '1' | ... '9'

[...]被读取为可选,并且将是程序中的if测试(要么在那里,要么丢失)

|是替代方案,if..else if..else 或 switch 'test' 也是如此,它说输入必须与其中一个匹配

{ ... }被读取为 0 或更多的重复,并且将是程序中的 while 循环

引号之间的字符是将在字符串中的文字字符。所有其他单词都是规则的名称,对于递归下降解析器,最终是被调用以截断并处理输入的函数的名称。

例如,实现“数量”规则的函数只需要读取一个或多个数字字符,并将它们转换为整数。实现Atom规则的函数读取足够的字符来确定它是哪个原子,并将其存储。

递归下降解析器的一个好处是错误消息可能非常有帮助,并且形式为“期望一个 Atom 名称,但得到了 % c”或“期望一个 ')”,但到达了字符串的结尾。在错误后恢复有点复杂,所以您可能希望在第一个错误时抛出异常。

那么多原子离子只是括号的一个层次吗?如果是这样,语法可能是:

Compound: Component { Component }  
Component: Atom [Quantity] | '(' Component { Component } ')' [Quantity];
Atom: 'H' | 'He' | 'Li' ...
Quantity: Digit { Digit }
Digit: '0' | '1' | ... '9'

或者它更复杂,并且符号必须允许嵌套括号。

我不知道你的问题的整个范围,但递归下降解析器是相对简单的写,看起来足够你的问题。

1

考虑将程序重新构造为一个简单的Recursive Descent Pr

首先,您需要更改pString函数以获取要解析的string,以及通过引用传递的开始解析的当前位置。

这样,您可以构造代码,以便当您看到(时,您可以在下一个位置调用相同的函数返回Composite,并使用结束)。当您看到)本身时,您无需消耗它就可以返回。这可以让您使用具有的无限嵌套的公式

这样,您只需编写一次解析复合的代码,并根据需要重复使用多次。这将很容易补充您的读者使用破折号等公式,因为您的解析器只需要处理基本的构建块。

本站系公益性非盈利分享网址,本文来自用户投稿,不代表边看边学立场,如若转载,请注明出处

(449)
Cloud vc:我们可以使用VCredist2015而不是VC2012和VC2013吗
上一篇
Toke:Java“VariableDeclaratorId应该在这个令牌之后”
下一篇

相关推荐

发表评论

登录 后才能评论

评论列表(17条)