8

文字列からC#の化学式(たとえば、Al2O3orO3またはCorの形式)を解析しようとしています。C11H22O12特定の元素の原子が1つしかない場合(たとえば、の酸素原子H2O)を除いて、正常に機能します。その問題をどのように修正できますか?さらに、化学式の文字列を解析するためのより良い方法はありますか?

ChemicalElementは、化学元素を表すクラスです。これには、AtomicNumber(int)、Name(string)、Symbol(string)のプロパティがあります。ChemicalFormulaComponentは、化学元素と原子数(式の一部など)を表すクラスです。これには、Element(ChemicalElement)、AtomCount(int)のプロパティがあります。

残りは理解できるほど明確でなければなりませんが(私は願っています)、答える前に、何か明確にできることがあればコメントで知らせてください。

これが私の現在のコードです:

    /// <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;
    }
4

4 に答える 4

11

正規表現を使用してパーサーを書き直しました。正規表現は、あなたがしていることに完全に適合します。お役に立てれば。

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;
}
于 2010-11-07T07:21:53.480 に答える
2

あなたの方法の問題はここにあります:

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

数字がない場合、カウントバッファは0になります。これでうまくいくと思います

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

これは、HO2などの数式の場合に機能します。私はあなたの方法がformula化学式のlas要素をコレクションに挿入することは決してないと信じています。

次のように、結果を返す前に、buferの最後の要素をコレクションに追加する必要があります。

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

    return formula;
}
于 2010-11-07T07:22:30.007 に答える
1

まず第一に、私は.netでパーサジェネレータを使用していませんが、適切なものを見つけることができると確信しています。これにより、化学式の文法をはるかに読みやすい形式で書くことができます。最初のスタートについては、たとえばこの質問を参照してください。

アプローチを維持したい場合:番号があるかどうかに関係なく、最後の要素を追加しない可能性はありますか?あなたはあなたがあなたのフォーミュラにあなたが持っているものを追加するi<= chemicalFormula.Length場合にもあなたのループを実行したいかもしれません。次に、countBufferが実際にはゼロになる可能性があるため、条件i==chemicalFormula.Lengthを削除する必要があります。if (countBuffer > 0)

于 2010-11-07T07:24:02.723 に答える
0

次のようなものを分割する場合、正規表現は単純な数式で正常に機能するはずです。

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

パーサーを使用する方が簡単な場合があります(複合ネストのため)。すべてのパーサーはそれを処理できる必要があります。

数日前にこの問題を発見したので、パーサーの文法を書く方法の良い例だと思ったので、単純な化学式の文法をNLTスイートに含めました。重要なルールは次のとおりです-レクサーの場合:

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

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

およびパーサーの場合:

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)) }
      ;
于 2013-11-13T14:51:22.067 に答える