2016-12-13 5 views
4

12 CO2 + 6 H2O -> 2 C6H12O6 + 12 O2와 같이 사용자의 화학 반응식을 입력으로 사용하는 프로그램을 작성해야합니다. 원자 금액이 두 사이트 모두 같으면 쉽게 계산하고 분석 할 수있는 방법이 있습니까? 예를 들어화학 반응을 나타내는 문자열 파싱 및 반응 가능 여부 확인

:

12 CO2 + 6 H2O -> 2 C6H12O6 + 12 O2

12 * 2 + 6 * 2 -> 2 * 6 + 2 * 12 + 2 * 6 + 12 * 2

이 경우 Output "false"가 있어야합니다.

이 내 코드입니다하지만 실제로 뭔가 시도해 아니라 :

public static void main(String[] args) { 
    Scanner s = new Scanner(System.in); 
    List<String> list = new ArrayList<String>(); 
    String input = ""; 
    while (!(input.equals("end"))) { 
     input = s.nextLine(); 
     list.add(input); 
    } 
    list.remove(list.size() - 1); 
    for (int i = 0; i < list.size(); i++) { 
     int before = 0; 
     int after = 0; 
     String string = list.get(i); 
     string = besserUmwandeln(string); 
     System.out.println(string); 
    } 
} 

public static String besserUmwandeln(String string) { 
    string = string.replace("-", ""); 
    string = string.trim().replaceAll(("\\s+"), " "); 
    string = string.replace(' ', '*'); 
    StringBuilder builder = new StringBuilder(string); 
    System.out.println(string); 
    for (int k = 0; k < builder.length(); k++) { 
     if (Character.isUpperCase(builder.charAt(k))) { 
      builder.setCharAt(k, ':'); 
     } 
     if (Character.isLowerCase(builder.charAt(k))) { 
      builder.setCharAt(k, '.'); 
     } 
     if (Character.isDigit(builder.charAt(k))) { 
     } else { 
     } 
    } 
    for (int j = 0; j < builder.length(); j++) { 
     if (j < builder.length() && builder.charAt(j) == ':' && builder.charAt(j + 1) == '.') { 
      builder.deleteCharAt(j + 1); 
     } 
    } 
    for (int i = 0; i < builder.length(); i++) { 
     if (i < builder.length() - 1 && builder.charAt(i) == ':' && builder.charAt(i + 1) == ':') { 
      builder.deleteCharAt(i); 
     } 
    } 
    for (int i = 0; i < builder.length(); i++) { 
     if (i < builder.length() - 1 && builder.charAt(i) == '+' && builder.charAt(i + 1) == '*') { 
      builder.deleteCharAt(i + 1); 
     } 
    } 
    for (int i = 0; i < builder.length(); i++) { 
     if (i < builder.length() - 1 && builder.charAt(i) == '*' && builder.charAt(i + 1) == '+') { 
      builder.deleteCharAt(i); 
     } 
    } 
    for (int i = 0; i < builder.length(); i++) { 
     if (i < builder.length() - 1 && builder.charAt(i) == '*' && builder.charAt(i + 1) == '>') { 
      builder.deleteCharAt(i); 
     } 
    } 
    for (int i = 0; i < builder.length(); i++) { 
     if (i < builder.length() - 1 && builder.charAt(i) == '>' && builder.charAt(i + 1) == '*') { 
      builder.deleteCharAt(i + 1); 
     } 
    } 
    for (int i = 0; i < builder.length(); i++) { 
     if (i < builder.length() - 1 && builder.charAt(i) == '*' && builder.charAt(i + 1) == ':') { 
      builder.deleteCharAt(i + 1); 
     } 
    } 


    return builder.toString(); 
} 
+0

한 가지 방법은 EBNF 문법을 정의하고 구문 분석 도구를 작성하는 것입니다. 이 작업은 C++에서 ** 아름답게 할 수 있습니다. 멤버 연산자'->'에 대한 포인터를 오버로드 할 수도 있습니다. 부스트 스피릿을 참조하십시오. IMHO Java는 심각한 과학 프로그래밍을위한 것이 아닙니다. – Bathsheba

+0

@ Bathsheba 답변 해 주셔서 감사합니다. 나는 자바가 심각한 과학 프로그램을 가지고 있지 않다고 생각하지만,이 경우 나는 다른 선택의 여지가 없기 때문에 자바로 개발할 필요가있다. –

+0

호그 워시! C++로 빌드하고 JNI를 제공하십시오. Java의'native' 키워드를보십시오. 나는 광합성 장난 소리를 피할거야! – Bathsheba

답변

2

그래서, Java 일부 텍스트를 구문 분석 할 때마다, 나는 대부분은 단지 Regex을 사용하게합니다. 그래서 나는 또한 그렇게하도록 권하고 싶습니다.

regex101.com에서 정규식을 테스트 할 수 있습니다.

또한 쉽게 Java에서 사용 : 당신이 ()로 캡처 그룹을 정의하고 matcher.group(int)하여 결과를 잡을 수

final inputText = ... 
final Pattern pattern = Patern.compile("Some regex code"); 
final Matcher matcher = pattern.matcher(input); 
if (matcher.find()) { 
    System.out.println(matcher.group(0)); 
} 

Regex 내부.

예를 들어 (.*) -> (.*)을 사용하여 방정식을 분리 할 수 ​​있습니다.

find을 사용하여 왼쪽 그룹과 오른쪽 그룹을 반복합니다. (\d+) (\w+)(?: \+| -|$).

그 후 금액에 group(1)을, 요소에 group(2)을 사용할 수 있습니다.

그리고 필요한 경우 (\w)(\d?)을 사용하여 정확한 요소 분포에 대한 두 번째 그룹 (요소)을 반복합니다. 첫 번째 그룹은 요소입니다. 예를 들어 CO2 텍스트의 경우 두 번의 조회가 발생하고 첫 번째 조회의 경우 두 번째 그룹은 group(1) -> C입니다. 두 번째 히트는 group(1) -> Ogroup(2) -> 2입니다.

테스트 여기 정규식 :이 질문은 방정식의 간단한 유형에 대한 간단한 파서 요구하고있다 regex101#Q6KMJo

+1

나는 downvote하지 않겠지 만 이것이 정말로 도움이되는지 의심한다 : 중요한 질문은 다음과 같다. ** ** 정규 표현식을 사용할 것인가? (당신은 그들이 정규 표현식과 문제에 대해 말하는 것을 알고 있습니다 ....). 그 외에도, 나는 더 복잡한 수식이 정규식으로 구문 분석 될 수 있는지 의심 스럽다. – Marco13

+0

정규식을 제공하면 정말 좋을 것이다. –

+0

나는 정확하게 사용할 기술을 설명하고 주어진 수식을 분석하는 데 필요한 모든 ** Regex ** 코드를 제공했습니다. 예, 복잡한 공식에는 어려울 수 있습니다. – Zabuza

4

. 나는 괄호와 이상한 기호로 모든 종류의 불규칙한 방정식을 지원할 필요가 없다고 가정하고 있습니다.

단지 안전하기 때문에 정규식 대신 String.split()을 많이 사용합니다.

A (상대적으로) 간단한 솔루션은 다음을 수행하게

  1. 분할
  2. ->에 두 조각이 있는지 확인
  3. 각 조각
  4. 합계 최대 :
    1. 분할 +
    2. 을에
    3. 각 분자를 분석하고 원자를 합산합니다.
      1. 구문 분석 옵션 승수
  5. 결과를 비교 숫자를 변환 및 요소에 의해 그들을 추가 정규식 분자에 모든 일치를 찾기

분석의 각 레벨 수 별도의 방법으로 쉽게 처리 할 수 ​​있습니다. 정규 표현식을 사용하는 것이 개별 분자를 파싱하는 가장 좋은 방법 일 수 있으므로 여기에서 표현식을 빌렸다 : https://codereview.stackexchange.com/questions/2345/simplify-splitting-a-string-into-alpha-and-numeric-parts. 정규식은 거의 사소한, 그래서 나와 함께 곰하시기 바랍니다 :

import java.util.Map; 
import java.util.HashMap; 
import java.util.regex.Pattern; 
import java.util.regex.Matcher; 

public class SimpleChemicalEquationParser 
{ 
    // Counts of elements on each side 
    private Map<String, Integer> left; 
    private Map<String, Integer> right; 

    public SimpleChemicalEquationParser(String eqn) 
    { 
     this.left = new HashMap<>(); 
     this.right = new HashMap<>(); 
     parse(eqn); 
    } 

    public boolean isBalanced() 
    { 
     return left.equals(right); 
    } 

    public boolean isSimpleBalanced() 
    { 
     return leftCount() == rightCount(); 
    } 

    public int leftCount() 
    { 
     return left.values().stream().mapToInt(Integer::intValue).sum(); 
    } 

    public int rightCount() 
    { 
     return right.values().stream().mapToInt(Integer::intValue).sum(); 
    } 

    private void parse(String eqn) 
    { 
     String[] sides = eqn.split("->"); 
     if(sides.length != 2) { 
      throw new RuntimeException("Check your equation. There should be exactly one -> symbol somewhere"); 
     } 
     parseSide(sides[0], this.left); 
     parseSide(sides[1], this.right); 
    } 

    private void parseSide(String side, Map<String, Integer> counter) 
    { 
     String[] molecules = side.split("\\+"); 
     for(String molecule : molecules) { 
      parseMolecule(molecule, counter); 
     } 
    } 

    private void parseMolecule(String molecule, Map<String, Integer> counter) 
    { 
     molecule = molecule.trim(); 
     Matcher matcher = Pattern.compile("([a-zA-Z]+)\\s*([0-9]*)").matcher(molecule); 
     int multiplier = 1; 
     int endIndex = 0; 
     while(matcher.find()) { 
      String separator = molecule.substring(endIndex, matcher.start()).trim(); 
      if(!separator.isEmpty()) { 
       // Check if there is a premultiplier before the first element 
       if(endIndex == 0) { 
        String multiplierString = molecule.substring(0, matcher.start()).trim(); 
        try { 
         multiplier = Integer.parseInt(multiplierString); 
        } catch(NumberFormatException nfe) { 
         throw new RuntimeException("Invalid prefix \"" + multiplierString + 
                "\" to molecule \"" + molecule.substring(matcher.start()) + "\""); 
        } 
       } else { 
        throw new RuntimeException("Nonsensical characters \"" + separator + 
               "\" in molecule \"" + molecule + "\""); 
       } 
      } 
      parseElement(multiplier, matcher.group(1), matcher.group(2), counter); 
      endIndex = matcher.end(); 
     } 
     if(endIndex != molecule.length()) { 
      throw new RuntimeException("Invalid end to side: \"" + molecule.substring(endIndex) + "\""); 
     } 
    } 

    private void parseElement(int multiplier, String element, String atoms, Map<String, Integer> counter) 
    { 
     if(!atoms.isEmpty()) 
      multiplier *= Integer.parseInt(atoms); 
     if(counter.containsKey(element)) 
      multiplier += counter.get(element); 
     counter.put(element, multiplier); 
    } 

    public static void main(String[] args) 
    { 
     // Collect all command line arguments into one equation 
     StringBuilder sb = new StringBuilder(); 
     for(String arg : args) 
      sb.append(arg).append(' '); 

     String eqn = sb.toString(); 
     SimpleChemicalEquationParser parser = new SimpleChemicalEquationParser(eqn); 
     boolean simpleBalanced = parser.isSimpleBalanced(); 
     boolean balanced = parser.isBalanced(); 

     System.out.println("Left: " + parser.leftCount()); 
     for(Map.Entry<String, Integer> entry : parser.left.entrySet()) { 
      System.out.println(" " + entry.getKey() + ": " + entry.getValue()); 
     } 
     System.out.println(); 

     System.out.println("Right: " + parser.rightCount()); 
     for(Map.Entry<String, Integer> entry : parser.right.entrySet()) { 
      System.out.println(" " + entry.getKey() + ": " + entry.getValue()); 
     } 
     System.out.println(); 

     System.out.println("Atom counts match: " + simpleBalanced); 
     System.out.println("Elements match: " + balanced); 
    } 
} 

모든 작업은 가상 호출 트리의 종류를 만드는 parse 방법 및 그것의 부하 직원에 의해 수행된다. 이 방법을 사용하면 각 원소의 원자가 실제로 균형을 이루는지 확인하는 것이 특히 쉬워 지므로 여기에서 앞서 나가 보았습니다. 이 클래스는 미가공 카운트가 균형을 이루는 지 여부와 요소 유형과 일치하는지 여부와 상관없이 방정식의 각면에있는 원자 수를 인쇄합니다. 다음은 예를 몇 가지를 실행합니다 :

영업 이익의 원래 예 :

$ java -cp . SimpleChemicalEquationParser '12 C O2 + 6 H2O -> 2 C6H12O6 + 12 O2' 
Left: 54 
    C: 12 
    H: 12 
    O: 30 

Right: 72 
    C: 12 
    H: 24 
    O: 36 

Atom counts match: false 
Elements match: false 

추가 된 오존을 원자의 수는

를 일치 모든 것을 만들 수
$ java -cp . SimpleChemicalEquationParser '12 C O2 + 6 H2O + 6 O3 -> 2 C6H12O6 + 12 O2' 
Left: 72 
    C: 12 
    H: 12 
    O: 48 

Right: 72 
    C: 12 
    H: 24 
    O: 36 

Atom counts match: true 
Elements match: false 

추가 된 물을 일치하도록
$ java -cp . SimpleChemicalEquationParser '12 C O2 + 12 H2O -> 2 C6H12O6 + 12 O2' 
Left: 72 
    C: 12 
    H: 24 
    O: 36 

Right: 72 
    C: 12 
    H: 24 
    O: 36 

Atom counts match: true 
Elements match: true 

C과사이에 공백을 추가했습니다.은 CO2입니다. 이는 분자에 대한 나의 현재 정규식이 ([a-zA-Z]+)\\s*([0-9]*)이므로 글자의 어떤 조합도 요소를 나타낼 수 있기 때문입니다. 요소가 항상 간단한 한 문자 요소가되도록하려면 ([a-zA-Z])\\s*([0-9]*)으로 변경하십시오 (+ 한정 기호 제거). 두 번째 글자 조합이 항상 소문자 인 두 글자 조합 인 경우 ([A-Z][a-z]?)\\s*([0-9]*) 대신이 이름을 사용해야합니다. 나는 후자의 옵션을 추천한다. 수정 된 두 버전 모두 C O2의 공간은 더 이상 필요하지 않습니다.

0

ANTLR과 같은 적절한 구문 분석기는 1) 텍스트를 어휘 토큰 스트림으로 변환 한 다음 2) 구문 분석 트리를 사용하여 토큰을 구문 분석하여 구문 분석 트리로 구문 분석합니다.

Lookahead는 구문 분석의 특정 구조 수준을 "종료"할 때 유용합니다.

요구 사항에 따라 렉싱과 구문 분석의 구분을 건너 뛰고 텍스트에서 직접 구문 분석 할 수는 있지만 미리보기를 사용하면 유용 할 수 있습니다.

특히 다가올 (나머지) 텍스트를 유지하고 그 텍스트와 일치하는 테스트 (예 : 정규식) 및 전면에서 일치하는 항목을 사용하는 것이 유용 할 수 있습니다. 이것은 remaining 문자열을 수정하거나 문자열을 앞당겨서 구현할 수 있습니다.

class EquationParser { 
    protected ParseBuffer buffer; 

    // parse Equation;  
    //  -- of form "Sum -> Sum". 
    //  -- eg. "12 CO2 + 6 H2O -> 2 C6H12O6 + 12 O2" 
    public Equation parseEquation() { 

     Sum left = parseSum(); 
     matchAndConsume("->"); 
     Sum right = parseSum(); 
     return new Equation(left, right); 
    } 

    // parse Sum; 
    //  -- eg. "12 CO2 + 6 H2O" 
    public Sum parseSum() { 
     // parse 1 or more Product terms; 
     Sum result = new Sum(); 
     result.add(parseProduct()); 
     while (buffer.lookaheadMatch("\\+")) { 
      buffer.consumeMatch("\\+"); 
      result.add(parseProduct()); 
     } 
     return result; 
    } 

    // parse Product term; 
    //  -- of form "N formula", or just "formula". 
    //  -- eg. "12 CO2" or "CO2" 
    public Product parseProduct() { 
     int quantity = 1; 
     if (buffer.lookaheadMatch("\\d+")) { 
      quantity = Integer.parseInt(buffer.consumeMatch("\\d+")); 
     } 
     Formula formula = parseFormula(); 
     return new Product(quantity, formula); 
    } 

    // parse Formula; 
    //  -- eg. "C6H12O6" or "CO2" 
    public Formula parseFormula() { 
     Formula result = new Formula(); 
     result.add(parseTerm()); 
     while (buffer.lookaheadMatch("[A-Z][a-z]?\\d*")) { 
      result.add(parseTerm()); 
     } 
     return result; 
    } 

    // parse Term; 
    //  -- eg. "C6", "C", "Co6", or "Co6" 
    public Term parseTerm() { 
     // ... reader exercise to implement... 
    } 


    protected void matchAndConsume (String patt) { 
     if (! buffer.lookaheadMatch(patt)) 
      throw ParseFailed("parse failed: expected "+patt); 
     buffer.consumeMatch(patt); 
    } 
} 

이 버퍼 또는 전체 파서를 포함하지 않는 &을 테스트하지 개념 예제 코드, - 다음을 구체화 할 수있는 독자의 일이다 : 같은

이러한 버퍼 감안할 때, 당신의 의사가 보일 수 있습니다 완벽한 솔루션을 제공합니다.