Question

Je suis en train d'analyser une formule chimique (dans le format, par exemple: Al2O3 ou O3 ou C ou C11H22O12) en C # d'une chaîne. Cela fonctionne très bien à moins qu'il n'y a qu'un seul atome d'un élément particulier (par exemple l'atome d'oxygène dans H2O). Comment puis-je résoudre ce problème, et en plus, est-il une meilleure façon d'analyser une chaîne de formule chimique que je fais?

ChemicalElement est une classe qui représente un élément chimique. Il a des propriétés AtomicNumber (int), nom (string), Symbole (string). ChemicalFormulaComponent est une classe qui représente un comptage élément chimique et un atome (par exemple partie d'une formule). Il a des propriétés d'élément (ChemicalElement), atomCount (int).

Le reste doit être suffisamment clair pour comprendre (je l'espère), mais s'il vous plaît laissez-moi savoir avec un commentaire si je peux préciser quoi que ce soit, avant de répondre.

Voici mon code actuel:

    /// <summary>
    /// Parses a chemical formula from a string.
    /// </summary>
    /// <param name="chemicalFormula">The string to parse.</param>
    /// <exception cref="FormatException">The chemical formula was in an invalid format.</exception>
    public static Collection<ChemicalFormulaComponent> FormulaFromString(string chemicalFormula)
    {
        Collection<ChemicalFormulaComponent> formula = new Collection<ChemicalFormulaComponent>();

        string nameBuffer = string.Empty;
        int countBuffer = 0;

        for (int i = 0; i < chemicalFormula.Length; i++)
        {
            char c = chemicalFormula[i];

            if (!char.IsLetterOrDigit(c) || !char.IsUpper(chemicalFormula, 0))
            {
                throw new FormatException("Input string was in an incorrect format.");
            }
            else if (char.IsUpper(c))
            {
                // Add the chemical element and its atom count
                if (countBuffer > 0)
                {
                    formula.Add(new ChemicalFormulaComponent(ChemicalElement.ElementFromSymbol(nameBuffer), countBuffer));

                    // Reset
                    nameBuffer = string.Empty;
                    countBuffer = 0;
                }

                nameBuffer += c;
            }
            else if (char.IsLower(c))
            {
                nameBuffer += c;
            }
            else if (char.IsDigit(c))
            {
                if (countBuffer == 0)
                {
                    countBuffer = c - '0';
                }
                else
                {
                    countBuffer = (countBuffer * 10) + (c - '0');
                }
            }
        }

        return formula;
    }
Était-ce utile?

La solution

Je réécrit votre analyseur en utilisant des expressions régulières. Les expressions régulières allait très bien pour ce que vous faites. Espérons que cela aide.

public static void Main(string[] args)
{
    var testCases = new List<string>
    {
        "C11H22O12",
        "Al2O3",
        "O3",
        "C",
        "H2O"
    };

    foreach (string testCase in testCases)
    {
        Console.WriteLine("Testing {0}", testCase);

        var formula = FormulaFromString(testCase);

        foreach (var element in formula)
        {
            Console.WriteLine("{0} : {1}", element.Element, element.Count);
        }
        Console.WriteLine();
    }

    /* Produced the following output

    Testing C11H22O12
    C : 11
    H : 22
    O : 12

    Testing Al2O3
    Al : 2
    O : 3

    Testing O3
    O : 3

    Testing C
    C : 1

    Testing H2O
    H : 2
    O : 1
        */
}

private static Collection<ChemicalFormulaComponent> FormulaFromString(string chemicalFormula)
{
    Collection<ChemicalFormulaComponent> formula = new Collection<ChemicalFormulaComponent>();
    string elementRegex = "([A-Z][a-z]*)([0-9]*)";
    string validateRegex = "^(" + elementRegex + ")+$";

    if (!Regex.IsMatch(chemicalFormula, validateRegex))
        throw new FormatException("Input string was in an incorrect format.");

    foreach (Match match in Regex.Matches(chemicalFormula, elementRegex))
    {
        string name = match.Groups[1].Value;

        int count =
            match.Groups[2].Value != "" ?
            int.Parse(match.Groups[2].Value) :
            1;

        formula.Add(new ChemicalFormulaComponent(ChemicalElement.ElementFromSymbol(name), count));
    }

    return formula;
}

Autres conseils

Le problème avec votre méthode est ici:

            // Add the chemical element and its atom count
            if (countBuffer > 0)

Si vous ne disposez pas d'un numéro, un tampon de comptage sera de 0, je pense que ce sera le travail

            // Add the chemical element and its atom count
            if (countBuffer > 0 || nameBuffer != String.Empty)

Cela fonctionne quand des formules comme HO2 ou quelque chose comme ça. Je crois que votre méthode ne sera jamais insérer dans la collection formula l'élément las de la formule chimique.

Vous devez ajouter le dernier élément de la bufer à la collecte avant le retour le résultat, comme ceci:

    formula.Add(new ChemicalFormulaComponent(ChemicalElement.ElementFromSymbol(nameBuffer), countBuffer));

    return formula;
}

d'abord: je n'ai pas utilisé un générateur d'analyseur en .net, mais je suis sûr que vous pourriez trouver quelque chose d'approprié. Cela vous permettra d'écrire la grammaire des formules chimiques sous une forme beaucoup plus facile à lire. Voir, par exemple cette question pour un premier départ.

Si vous voulez garder votre approche: Est-il possible que vous ne pas ajouter votre dernier élément, peu importe si elle a un certain nombre ou non? Vous pouvez lancer votre boucle avec i<= chemicalFormula.Length et en cas de i==chemicalFormula.Length ajouter aussi ce que vous avez à votre formule. Ensuite, vous devez également retirer votre condition de if (countBuffer > 0) car countBuffer peut effectivement être zéro!

Regex devrait fonctionner correctement avec la formule simple, si vous voulez partager quelque chose comme:

(Zn2(Ca(BrO4))K(Pb)2Rb)3

il pourrait être plus facile à utiliser l'analyseur pour elle (en raison de l'imbrication composé). Tout analyseur doit être capable de le manipuler.

Je repéré ce problème il y a quelques jours, je pensais que ce serait bon exemple comment on peut écrire la grammaire pour un analyseur, donc j'inclus la grammaire de formule chimique simple dans mon la suite de NLT. touche Les règles sont - pour lexer:

"(" -> LPAREN;
")" -> RPAREN;

/[0-9]+/ -> NUM, Convert.ToInt32($text);
/[A-Z][a-z]*/ -> ATOM;

et analyseur syntaxique:

comp -> e:elem { e };

elem -> LPAREN e:elem RPAREN n:NUM? { new Element(e,$(n : 1)) }
      | e:elem++ { new Element(e,1) }
      | a:ATOM n:NUM? { new Element(a,$(n : 1)) }
      ;
scroll top