2010-11-07 38 views
8

Tôi đang cố gắng phân tích cú pháp công thức hóa học (theo định dạng, ví dụ: Al2O3 hoặc O3 hoặc C hoặc C11H22O12) bằng C# từ một chuỗi. Nó hoạt động tốt trừ khi chỉ có một nguyên tử của một nguyên tố cụ thể (ví dụ: nguyên tử oxy trong H2O). Làm thế nào tôi có thể khắc phục vấn đề đó, và ngoài ra, có cách nào tốt hơn để phân tích một chuỗi công thức hóa học hơn tôi đang làm không?Phân tích cú pháp một công thức hóa học từ một chuỗi trong C#?

ChemicalElement là một lớp đại diện cho một nguyên tố hóa học. Nó có các thuộc tính AtomicNumber (int), Name (string), Symbol (string). ChemicalFormulaComponent là một lớp đại diện cho phần tử hóa học và số nguyên tử (ví dụ: một phần của công thức). Nó có các thuộc tính Element (ChemicalElement), AtomCount (int).

Phần còn lại phải đủ rõ ràng để hiểu (tôi hy vọng) nhưng xin vui lòng cho tôi biết bằng một bình luận nếu tôi có thể làm rõ bất cứ điều gì, trước khi bạn trả lời.

Đây là mã hiện tại của tôi:

/// <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; 
    } 
+0

Tại sao các bạn kiểm tra xem ký tự đầu tiên của công thức là chữ in hoa trên mỗi iteration của 'for' loop ('! char.IsUpper (chemicalFormula, 0)')? Chỉ mục ở đây luôn là '0'. –

+0

Tôi nghĩ rằng chức năng của bạn cũng có vấn đề với một cái gì đó như C4O2 là điều này đúng? –

+0

Xem thêm trang http://stackoverflow.com/questions/2974362/parsing-a-chemical-formula/3742985. Nó yêu cầu một trong Java, với một câu trả lời trong Python, và liên kết đến các giải pháp ANTLR và Python phức tạp hơn. –

Trả lời

10

Tôi viết lại phân tích cú pháp bằng cách sử dụng biểu thức thông thường. Biểu thức chính quy phù hợp với hóa đơn hoàn hảo cho những gì bạn đang làm. Hi vọng điêu nay co ich.

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; 
} 
+0

Điều này có vẻ hoàn hảo, cảm ơn rất nhiều. Sidenote mặc dù - không nên * gần [A-Z] [a-z] là dấu +? –

+0

'*' chỉ áp dụng cho một nhóm '[]'. Điều này có nghĩa rằng '[A-Z]' phải xuất hiện chính xác một lần (vì nó không có '*' hoặc '+'), và '[a-z]' phải xuất hiện bằng không hoặc nhiều lần. –

+0

Ah vâng, tất nhiên. Không đọc chính xác dấu ngoặc đơn của tôi. Cảm ơn một lần nữa! –

2

Vấn đề với phương pháp của bạn là ở đây:

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

Khi bạn không có một con số, đếm đệm sẽ là 0, tôi nghĩ rằng điều này sẽ làm việc

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

này sẽ làm việc khi cho các công thức như HO2 hoặc một cái gì đó như thế. Tôi tin rằng phương pháp của bạn sẽ không bao giờ chèn vào bộ sưu tập formula yếu tố las của công thức hóa học.

Bạn nên thêm yếu tố cuối cùng của bufer đến việc thu thập trước khi trả về kết quả, như thế này:

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

    return formula; 
} 
1

trước hết là: Tôi đã không được sử dụng một máy phát điện phân tích cú pháp trong .net, nhưng tôi m khá chắc chắn bạn có thể tìm thấy một cái gì đó thích hợp. Điều này sẽ cho phép bạn viết ngữ pháp của Công thức hóa học dưới dạng dễ đọc hơn nhiều. Xem ví dụ this question để bắt đầu lần đầu tiên.

Nếu bạn muốn giữ nguyên cách tiếp cận: Có thể bạn không thêm phần tử cuối cùng của mình cho dù nó có số hay không? Bạn có thể muốn chạy vòng lặp của mình với i<= chemicalFormula.Length và trong trường hợp i==chemicalFormula.Length cũng thêm những gì bạn có vào Công thức của mình. Sau đó, bạn cũng phải loại bỏ điều kiện if (countBuffer > 0) vì countBuffer thực sự có thể bằng không!

0

Regex nên làm việc tốt với công thức đơn giản, nếu bạn muốn chia cái gì đó như:

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

nó có thể được dễ dàng hơn để sử dụng các phân tích cú pháp cho nó (vì làm tổ hợp chất). Bất kỳ trình phân tích cú pháp nào đều có khả năng xử lý nó.

Tôi phát hiện vấn đề này vài ngày trước, tôi nghĩ rằng đó là ví dụ tốt về cách viết một ngữ pháp cho trình phân tích cú pháp, vì vậy tôi đưa ngữ pháp công thức hóa học đơn giản vào bộ NLT của mình.Các chính quy tắc là - cho lexer:

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

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

và cho phân tích cú pháp:

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)) } 
     ; 
Các vấn đề liên quan