Balance chemische Gleichungen!


30

Bernd ist ein Gymnasiast, der Probleme mit der Chemie hat. In der Klasse muss er chemische Gleichungen für einige Experimente entwerfen, die sie durchführen, wie zum Beispiel die Verbrennung von Heptan:

C 7 H 16 + 11 O 2 → 7 CO 2 + 8 H 2 O

Da Mathematik nicht gerade das stärkste Fach von Bernd ist, fällt es ihm oft schwer, die genauen Verhältnisse zwischen Pro- und Edukten der Reaktion zu finden. Da Sie Bernd's Tutor sind, ist es Ihre Aufgabe, ihm zu helfen! Schreiben Sie ein Programm, das die Menge jeder Substanz berechnet, die benötigt wird, um eine gültige chemische Gleichung zu erhalten.

Eingang

Die Eingabe ist eine chemische Gleichung ohne Mengen. Um dies in reinem ASCII zu ermöglichen, schreiben wir alle Abonnements als normale Zahlen. Elementnamen beginnen immer mit einem Großbuchstaben und können von einem Minuszeichen gefolgt werden. Die Moleküle werden mit +Vorzeichen getrennt , ein ASCII-Pfeil ->wird zwischen beide Seiten der Gleichung eingefügt:

Al+Fe2O4->Fe+Al2O3

Die Eingabe wird mit einem Zeilenumbruch abgeschlossen und enthält keine Leerzeichen. Wenn die Eingabe ungültig ist, kann Ihr Programm tun, was Sie möchten.

Sie können davon ausgehen, dass die Eingabe niemals länger als 1024 Zeichen ist. Ihr Programm kann entweder die Eingabe von der Standardeingabe, vom ersten Argument oder auf eine durch die Implementierung definierte Weise zur Laufzeit lesen, wenn beides nicht möglich ist.

Ausgabe

Die Ausgabe Ihres Programms ist die Eingabegleichung, die mit zusätzlichen Zahlen ergänzt wird. Die Anzahl der Atome für jedes Element muss auf beiden Seiten des Pfeils gleich sein. Für das obige Beispiel ist eine gültige Ausgabe:

2Al+Fe2O3->2Fe+Al2O3

Wenn die Zahl für ein Molekül 1 ist, lassen Sie es fallen. Eine Zahl muss immer eine positive ganze Zahl sein. Ihr Programm muss Zahlen liefern, deren Summe minimal ist. Zum Beispiel ist Folgendes illegal:

40Al+20Fe2O3->40Fe+20Al2O3

Wenn es keine Lösung gibt, drucken Sie

Nope!

stattdessen. Eine Beispieleingabe, die keine Lösung hat, ist

Pb->Au

Regeln

  • Das ist Code-Golf. Der kürzeste Code gewinnt.
  • Ihr Programm muss für alle angemessenen Eingaben in angemessener Zeit beendet werden.

Testfälle

Jeder Testfall hat zwei Zeilen: Eine Eingabe und eine korrekte Ausgabe.

C7H16+O2->CO2+H2O
C7H16+11O2->7CO2+8H2O

Al+Fe2O3->Fe+Al2O3
2Al+Fe2O3->2Fe+Al2O3

Pb->Au
Nope!

1
Ich könnte mich irren, aber dies scheint ein natürlicher Kandidat für eine Programmierherausforderung zu sein, eher für Codegolf.
DavidC

1
Ich habe einmal einen chemischen Gleichungslöser auf meinem TI-89-Grafikrechner geschrieben, wobei ich die eingebaute solve(Funktion verwendet und eval(die Eingabe interpretiert habe :)
mellamokb 16.10.12

3
@mellamokb, warum postest du es nicht, du bekommst eine Gegenstimme von mir für Originalität
Ratschenfreak

5
"Da du Bernds Tutor bist, ist es deine Aufgabe, ihm zu helfen!" - Ich hätte gedacht, ein Tutor sollte Bernd lehren, für sich selbst zu denken, anstatt Software für ihn zu schreiben, damit er nicht: P
naught101

1
@ KuilinLi Es ist nicht falsch, nur anders.
FUZxxl

Antworten:


7

C 442 505 Zeichen

// element use table, then once parsed reused as molecule weights
u,t[99];

// molecules
char*s,*m[99]; // name and following separator
c,v[99][99]; // count-1, element vector

i,j,n;

// brute force solver, n==0 upon solution - assume at most 30 of each molecule
b(k){
    if(k<0)for(n=j=0;!n&&j<u;j++)for(i=0;i<=c;i++)n+=t[i]*v[i][j]; // check if sums to zero
    else for(t[k]=0;n&&t[k]++<30;)b(k-1); // loop through all combos of weights
}

main(int r,char**a){
    // parse
    for(s=m[0]=a[1];*s;){
        // parse separator, advance next molecule
        if(*s==45)r=0,s++;
        if(*s<65)m[++c]=++s;
        // parse element
        j=*s++;
        if(*s>96)j=*s+++j<<8;            
        // lookup element index
        for(i=0,t[u]=j;t[i]-j;i++);
        u+=i==u;
        // parse amount
        for(n=0;*s>>4==3;)n=n*10+*s++-48;
        n+=!n;
        // store element count in molecule vector, flip sign for other side of '->'
        v[c][i]=r?n:-n;
    }
    // solve
    b(c);
    // output
    for(i=0,s=n?"Nope!":a[1];*s;putchar(*s++))s==m[i]&&t[i++]>1?printf("%d",t[i-1]):0;
    putchar(10);
}

Rennen wie:

./a.out "C7H16+O2->CO2+H2O"
./a.out "Al+Fe2O4->Fe+Al2O3"
./a.out "Pb->Au"

Ergebnisse:

C7H16+11O2->7CO2+8H2O
8Al+3Fe2O4->6Fe+4Al2O3
Nope!

+1 das ist viel respektabler als der pres. Debatte
ardnew

2
Verwenden Sie Kommas als Anweisungstrennzeichen, um geschweifte Klammern zu vermeiden. Versuchen Sie auch, if-then-else-Konstrukte durch ternäre Operatoren zu ersetzen, um den Code zu verkürzen. t [i]> 1? printf ("% s", t [i]): 0; ist ein Byte kürzer. Auch: m [0] ist dasselbe wie * m.
FUZxxl

6

Mathematica 507

Ich habe den Ansatz der erweiterten Matrix der chemischen Zusammensetzung angewendet, der in beschrieben ist

LRThorne, Ein innovativer Ansatz zum Ausgleich chemischer Reaktionsgleichungen: eine vereinfachte Matrix - Inverse - Technik zur Bestimmung des Matrix - Nullraums. Chem.Educator , 2010, 15, 304 - 308.

Eine kleine Änderung wurde hinzugefügt: Ich habe die Transponierte des Nullraumvektors durch den größten gemeinsamen Teiler der Elemente geteilt, um in allen Lösungen ganzzahlige Werte sicherzustellen. Meine Implementierung behandelt noch nicht Fälle, in denen es mehr als eine Lösung zum Ausgleichen der Gleichung gibt.

b@t_ :=Quiet@Check[Module[{s = StringSplit[t, "+" | "->"], g = StringCases, k = Length, 
  e, v, f, z, r},
e = Union@Flatten[g[#, _?UpperCaseQ ~~ ___?LowerCaseQ] & /@ s];v = k@e;
s_~f~e_ := If[g[s, e] == {}, 0, If[(r = g[s, e ~~ p__?DigitQ :> p]) == {}, 1, 
   r /. {{x_} :> ToExpression@x}]];z = k@s - v;
r = #/(GCD @@ #) &[Inverse[Join[SparseArray[{{i_, j_} :> f[s[[j]], e[[i]]]}, k /@ {e, s}], 
Table[Join[ConstantArray[0, {z, v}][[i]], #[[i]]], {i, k[#]}]]][[All, -1]] &
   [IdentityMatrix@z]];
Row@Flatten[ReplacePart[Riffle[Partition[Riffle[Abs@r, s], 2], " + "], 
   2 Count[r, _?Negative] -> " -> "]]], "Nope!"]

Tests

b["C7H16+O2->CO2+H2O"]
b["Al+Fe2O3->Fe+Al2O3"]
b["Pb->Au"]

Bildbeschreibung hier eingeben

Analyse

Es funktioniert, indem die folgende Tabelle der chemischen Zusammensetzung erstellt wird, die aus chemischen Spezies nach Elementen besteht, zu denen ein Additionsnullitätsvektor hinzugefügt wird (der zur erweiterten Tabelle der chemischen Zusammensetzung wird):

Tabelle der chemischen Zusammensetzung

Die inneren Zellen werden als Matrix entfernt und invertiert, was ergibt.

Inversion

Die Spalte ganz rechts wird extrahiert und ergibt:

{- (1/8), - (11/8), 7/8, 1}

Jedes Element im Vektor wird durch die gcd der Elemente (1/8) geteilt, was ergibt:

{-1, -11, 7, 8}

wo die negativen Werte auf der linken Seite des Pfeils platziert werden. Die absoluten Werte von diesen sind die Zahlen, die benötigt werden, um die ursprüngliche Gleichung auszugleichen:

Lösung


Vergessen Sie nicht, das Ausrufezeichen hinzuzufügen!
Mittwoch,

:} OK, und ich erhöhte die Anzahl der Zeichen
DavidC

Ich denke, Sie meinen die rechte Spalte, nicht die linke. Ich schätze die Erklärung (+1), aber ich frage mich: Wenn die Anzahl der Moleküle nicht eins mehr ist als die Anzahl der Elemente, wie füllt man sie auf? Aus, um jetzt die Zeitung zu lesen.
Peter Taylor

Aus irgendeinem Grund bin ich heute nur auf Ihren Kommentar gestoßen. Ja, ich meinte "rechte Spalte". Es ist so viel Zeit vergangen, seit ich daran gearbeitet habe, dass ich nicht sehen (oder mich erinnern) kann, wo Polster verwendet werden. Es tut uns leid.
DavidC

3

Python, 880 Zeichen

import sys,re
from sympy.solvers import solve
from sympy import Symbol
from fractions import gcd
from collections import defaultdict

Ls=list('abcdefghijklmnopqrstuvwxyz')
eq=sys.argv[1]
Ss,Os,Es,a,i=defaultdict(list),Ls[:],[],1,1
for p in eq.split('->'):
 for k in p.split('+'):
  c = [Ls.pop(0), 1]
  for e,m in re.findall('([A-Z][a-z]?)([0-9]*)',k):
   m=1 if m=='' else int(m)
   a*=m
   d=[c[0],c[1]*m*i]
   Ss[e][:0],Es[:0]=[d],[[e,d]]
 i=-1
Ys=dict((s,eval('Symbol("'+s+'")')) for s in Os if s not in Ls)
Qs=[eval('+'.join('%d*%s'%(c[1],c[0]) for c in Ss[s]),{},Ys) for s in Ss]+[Ys['a']-a]
k=solve(Qs,*Ys)
if k:
 N=[k[Ys[s]] for s in sorted(Ys)]
 g=N[0]
 for a1, a2 in zip(N[0::2],N[1::2]):g=gcd(g,a2)
 N=[i/g for i in N]
 pM=lambda c: str(c) if c!=1 else ''
 print '->'.join('+'.join(pM(N.pop(0))+str(t) for t in p.split('+')) for p in eq.split('->'))
else:print 'Nope!'

Tests:

python chem-min.py "C7H16+O2->CO2+H2O"
python chem-min.py "Al+Fe2O4->Fe+Al2O3"
python chem-min.py "Pb->Au"

Ausgabe:

C7H16+11O2->7CO2+8H2O
8Al+3Fe2O4->6Fe+4Al2O3
Nope!

Könnte viel weniger als 880 sein, aber meine Augen töten mich schon ...


2

Python 2, 635 Bytes

Bisherige Byteanzahl: 794, 776, 774, 765, 759, 747, 735, 734, 720, 683, 658, 655, 654, 653, 651, 638, 637, 636 Bytes.

Die zweite Einrückungsstufe ist nur ein Tabulator, die dritte ein Tabulator und dann ein Leerzeichen.

Um ehrlich zu sein, das ist die Antwort von jadkik94, aber so viele Bytes wurden rasiert, dass ich es tun musste. Sag mir, ob ich alle Bytes abschneiden kann!

from sympy import*
import sys,re
from sympy.solvers import*
from collections import*
P=str.split
L=map(chr,range(97,123))
q=sys.argv[1]
S,O,a,i,u,v=defaultdict(list),L[:],1,1,'+','->'
w=u.join
for p in P(q,v):
 for k in P(p,u):
     c=L.pop(0)
     for e,m in re.findall('([A-Z][a-z]*)(\d*)',k):
      m=int(m or 1)
      a*=m
      S[e][:0]=[c,m*i],
 i=-1
Y=dict((s,Symbol(s))for s in set(O)-set(L))
Q=[eval(w('%d*%s'%(c[1],c[0])for c in S[s]),{},Y)for s in S]+[Y['a']-a]
k=solve(Q,*Y)
if k:
 N=[k[Y[s]]for s in sorted(Y)]
 g=gcd(N[:1]+N[1::2])
 print v.join(w((lambda c:str(c)*(c!=1))(N.pop(0)/g)+str(t)for t in P(p,u))for p in P(q,v))
else:print'Nope!'

Drei Bytes speichern ''.join(map(chr,range(97,122)))
::

:(, das funktioniert nicht. Funktioniert jedoch map(chr,range(97,123))für 12 Bytes gespeichert.
Zacharý

Oh, richtig! Es ist Python 2!
Aliqandil

1

JavaScript, 682 Bytes

x=>{m=1;x.split(/\D+/g).map(i=>i?m*=i:0);e=new Set(x.replace(/\d+|\+|->/g,"").match(/([A-Z][a-z]*)/g));e.delete``;A=[];for(let z of e){t=x.split`->`;u=[];for(c=1;Q=t.shift();c=-1)Q.split`+`.map(p=>u.push(c*((i=p.indexOf(z))==-1?0:(N=p.substring(i+z.length).match(/^\d+/g))?N[0]:1)));A.push(u)}J=A.length;for(P=0;P<J;P++){for(i=P;!A[i][P];i++);W=A.splice(i,1)[0];W=W.map(t=>t*m/W[P]);A=A.map(r=>r[P]?r.map((t,j)=>t-W[j]*r[P]/m):r);A.splice(P,0,W)}f=e.size;if(!A[0][f])return"Nope!";g=m=-m;_=(a,b)=>b?_(b,a%b):a;c=[];A.map(p=>c.push(t=p.pop())&(g=_(g,t)));c.push(m);j=x.match(/[^+>]+/g);return c.map(k=>k/g).map(t=>(t^1?t:"")+(z=j.shift())+(z.endsWith`-`?">":"+")).join``.slice(0,-1);}

Dies ist eine sehr viel bessere Antwort von Kuilin (Jahrzehnte von Charakteren!). Könnte nicht konkurrieren, da bestimmte JS-Features die Herausforderung nachträglich veröffentlichen.


0

Javascript, 705 Bytes

(Nicht konkurrierend, einige Features geben die Herausforderung bekannt)

Andere Lösungen hatten alle Elemente der rohen Gewalt. Ich versuchte einen deterministischeren Ansatz, indem ich die chemische Gleichung als einen Satz linearer Gleichungen darstellte und dann mit dem Gauß-Jordan-Algorithmus löste, um die reduzierte Reihen-Staffel-Form dieser Matrix anzunehmen. Um den trivialen Fall zu isolieren, bei dem alles Null ist, gehe ich davon aus, dass eines der Elemente eine konstante Zahl ist - und diese Zahl wird nur durch alle Zahlen bestimmt, die miteinander multipliziert werden, um keine Brüche zu haben. Dann werden wir als letzten Schritt jedes durch den gcd dividieren, um die letzte Bedingung zu erfüllen.

Ungolfed:

function solve(x) {
	//firstly we find bigNumber, which will be all numbers multiplied together, in order to assume the last element is a constant amount of that
	bigNumber = 1;
	arrayOfNumbers = new Set(x.split(/\D+/g));
	arrayOfNumbers.delete("");
	for (let i of arrayOfNumbers) bigNumber *= parseInt(i);
	
	//first actual step, we split into left hand side and right hand side, and then into separate molecules
	//number of molecules is number of variables, number of elements is number of equations, variables refer to the coefficients of the chemical equation
	//note, the structure of this is changed a lot in the golfed version since right is the same as negative left
	left = x.split("->")[0].split("+");
	righ = x.split("->")[1].split("+");
	molecules = left.length + righ.length;
	
	//then let's find what elements there are - this will also become how many equations we have, or the columns of our matrix minus one
	//we replace all the non-element characters, and then split based on the uppercase characters
	//this also sometimes adds a "" to the array, we don't need that so we just delete it
	//turn into a set in order to remove repeats
	elems = new Set(x.replace(/\d+|\+|->/g,"").match(/([A-Z][a-z]*)/g));
	elems.delete("");
	
	rrefArray = [];//first index is rows, second index columns - each row is an equation x*(A11)+y*(A21)+z*(A31)=A41 etc etc, to solve for xyz as coefficients
	//loop thru the elements, since for each element we'll have an equation, or a row in the array
	for (let elem of elems) {
		buildArr = [];
		//loop thru the sides
		for (let molecule of left) {
			//let's see how many of element elem are in molecule molecule
			//ASSUMPTION: each element happens only once per molecule (no shenanigans like CH3COOH)
			index = molecule.indexOf(elem);
			if (index == -1) buildArr.push(0);
			else {
				index += elem.length;
				numberAfterElement = molecule.substring(index).match(/^\d+/g);
				if (numberAfterElement == null) buildArr.push(1);
				else buildArr.push(parseInt(numberAfterElement));
			}
		}
		//same for right, except each item is negative
		for (let molecule of righ) {
			index = molecule.indexOf(elem);
			if (index == -1) buildArr.push(0);
			else {
				index += elem.length;
				numberAfterElement = molecule.substring(index).match(/^\d+/g);
				if (numberAfterElement == null) buildArr.push(-1);
				else buildArr.push(parseInt(numberAfterElement)*(-1));
			}
		}
		rrefArray.push(buildArr);
	}
	
	//Gauss-Jordan algorithm starts here, on rrefArray
	for (pivot=0;pivot<Math.min(molecules, elems.size);pivot++) {
		//for each pivot element, first we search for a row in which the pivot is nonzero
		//this is guaranteed to exist because there are no empty molecules
		for (i=pivot;i<rrefArray.length;i++) {
			row = rrefArray[i];
			if (row[pivot] != 0) {
				workingOnThisRow = rrefArray.splice(rrefArray.indexOf(row), 1)[0];
			}
		}
		//then multiply elements so the pivot element of workingOnThisRow is equal to bigNumber we determined above, this is all to keep everything in integer-space
		multiplyWhat = bigNumber / workingOnThisRow[pivot]
		for (i=0;i<workingOnThisRow.length;i++) workingOnThisRow[i] *= multiplyWhat
		//then we make sure the other rows don't have this column as a number, the other rows have to be zero, if not we can normalize to bigNumber and subtract
		for (let i in rrefArray) {
			row = rrefArray[i];
			if (row[pivot] != 0) {
				multiplyWhat = bigNumber / row[pivot]
				for (j=0;j<row.length;j++) {
					row[j] *= multiplyWhat;
					row[j] -= workingOnThisRow[j];
					row[j] /= multiplyWhat;
				}
				rrefArray[i]=row;
			}
		}
		//finally we put the row back
		rrefArray.splice(pivot, 0, workingOnThisRow);
	}
	
	//and finally we're done!
	//sanity check to make sure it succeeded, if not then the matrix is insolvable
	if (rrefArray[0][elems.size] == 0 || rrefArray[0][elems.size] == undefined) return "Nope!";
	
	//last step - get the results of the rref, which will be the coefficients of em except for the last one, which would be bigNumber (1 with typical implementation of the algorithm)
	bigNumber *= -1;
	gcd_calc = function(a, b) {
		if (!b) return a;
		return gcd_calc(b, a%b);
	};
	coEffs = [];
	gcd = bigNumber;
	for (i=0;i<rrefArray.length;i++) {
		num = rrefArray[i][molecules-1];
		coEffs.push(num);
		gcd = gcd_calc(gcd, num)
	}
	coEffs.push(bigNumber);
	for (i=0;i<coEffs.length;i++) coEffs[i] /= gcd;
	
	//now we make it human readable
	//we have left and right from before, let's not forget those!
	out = "";
	for (i=0;i<coEffs.length;i++) {
		coEff = coEffs[i];
		if (coEff != 1) out += coEff;
		out += left.shift();
		if (left.length == 0 && righ.length != 0) {
			out += "->";
			left = righ;
		} else if (i != coEffs.length-1) out += "+";
	}
	return out;
}
console.log(solve("Al+Fe2O4->Fe+Al2O3"));
console.log(solve("Al+Fe2O3->Fe+Al2O3"));
console.log(solve("C7H16+O2->CO2+H2O"));
console.log(solve("Pb->Au"));

Golf gespielt

s=x=>{m=1;x.split(/\D+/g).map(i=>i!=""?m*=i:0);e=(new Set(x.replace(/\d+|\+|->/g,"").match(/([A-Z][a-z]*)/g)));e.delete("");A=[];for(let z of e){t=x.split("->");u=[];for(c=1;Q=t.shift();c=-1)Q.split("+").map(p=>u.push(c*((i=p.indexOf(z))==-1?0:(N=p.substring(i+z.length).match(/^\d+/g))?N[0]:1)));A.push(u)}J=A.length;for(P=0;P<J;P++){for(i=P;!A[i][P];i++);W=A.splice(i,1)[0];W=W.map(t=>t*m/W[P]);A=A.map(r=>!r[P]?r:r.map((t,j)=>t-W[j]*r[P]/m));A.splice(P,0,W)}f=e.size;if (!A[0][f])return "Nope!";g=m=-m;_=(a,b)=>b?_(b,a%b):a;c=[];A.map(p=>c.push(t=p.pop())&(g=_(g,t)));c.push(m);j=x.match(/[^+>]+/g);return c.map(k=>k/g).map(t=>(t==1?"":t)+(z=j.shift())+(z.endsWith("-")?">":"+")).join("").slice(0,-1);}

console.log(s("Al+Fe2O4->Fe+Al2O3"));
console.log(s("Al+Fe2O3->Fe+Al2O3"));
console.log(s("C7H16+O2->CO2+H2O"));
console.log(s("Pb->Au"));


1
Nicht konkurrierend, da einige Features die Herausforderung nachholen.
Zacharý

Oh wow, ich habe nicht bemerkt, wie alt das war. Vielen Dank!
Kuilin Li
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.