Bilancia equazioni chimiche!


30

Bernd è uno studente delle superiori che ha dei problemi in chimica. In classe deve progettare equazioni chimiche per alcuni esperimenti che stanno facendo, come la combustione di eptano:

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

Poiché la matematica non è esattamente la materia più forte di Bernd, spesso fa fatica a trovare i rapporti esatti tra i professionisti e gli educatori della reazione. Dato che sei il tutor di Bernd, il tuo compito è aiutarlo! Scrivi un programma che calcoli la quantità di ogni sostanza necessaria per ottenere un'equazione chimica valida.

Ingresso

L'input è un'equazione chimica senza importi. Per renderlo possibile in puro ASCII, scriviamo tutti gli abbonamenti come numeri ordinari. I nomi degli elementi iniziano sempre con una lettera maiuscola e possono essere seguiti da un minuscolo. Le molecole sono separate da +segni, una freccia di arte ASCII ->è inserita tra i due lati dell'equazione:

Al+Fe2O4->Fe+Al2O3

L'input termina con una nuova riga e non contiene spazi. Se l'input non è valido, il tuo programma potrebbe fare quello che vuoi.

Si può presumere che l'input non sia mai più lungo di 1024 caratteri. Il tuo programma può leggere l'input dall'input standard, dal primo argomento o in modo definito in fase di esecuzione se nessuno dei due è possibile.

Produzione

L'output del tuo programma è l'equazione di input aumentata con numeri extra. Il numero di atomi per ciascun elemento deve essere lo stesso su entrambi i lati della freccia. Per l'esempio sopra, un output valido è:

2Al+Fe2O3->2Fe+Al2O3

Se il numero per una molecola è 1, rilasciarlo. Un numero deve essere sempre un numero intero positivo. Il tuo programma deve fornire numeri in modo tale che la loro somma sia minima. Ad esempio, quanto segue è illegale:

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

Se non c'è soluzione, stampare

Nope!

anziché. Un input di esempio che non ha soluzione è

Pb->Au

Regole

  • Questo è code-golf. Vince il codice più corto.
  • Il programma deve terminare in tempo ragionevole per tutti gli input ragionevoli.

Casi test

Ogni test case ha due righe: un input e un output corretto.

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

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

Pb->Au
Nope!

1
Potrei sbagliarmi, ma questo sembra un candidato naturale per una sfida di programmazione piuttosto che programmare il golf.
DavidC,

1
Una volta ho scritto un risolutore di equazioni chimiche sul mio calcolatore grafico TI-89, usando la solve(funzione integrata e eval(per interpretare l'input :)
mellamokb

3
@mellamokb perché non lo pubblichi, riceverai un voto da me per originalità
maniaco del cricchetto

5
"Dato che sei il tutor di Bernds, il tuo compito è aiutarlo!" - Avrei pensato che un tutor avrebbe dovuto insegnare a Bernd a pensare da solo, piuttosto che scrivere software per lui, così non dovrebbe: P
nought101

1
@KuilinLi Non è sbagliato, solo diverso.
FUZxxl,

Risposte:


7

C, 442 505 caratteri

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

Correre come:

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

risultati:

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

+1 questo è molto più rispettabile della pres. dibattito
ardnew,

2
Prova a utilizzare le virgole come separatori di istruzioni per evitare parentesi graffe. Prova anche a sostituire i costrutti if-then-else-build con operatori ternari per abbreviare il codice. t [i]> 1 printf ( "% s", t [i]): 0;? è più corto di un byte. Inoltre: m [0] è uguale a * m.
FUZxxl,

6

Mathematica 507

Ho impiegato l'approccio della matrice di composizione chimica aumentata descritto in

LRThorne, Un approccio innovativo al bilanciamento delle equazioni di reazione chimica: una matrice semplificata - tecnica inversa per determinare lo spazio nullo della matrice. Chem.Educator , 2010, 15, 304-308 .

È stata aggiunta una leggera modifica: ho diviso la trasposizione del vettore spazio nullo per il massimo comune divisore degli elementi per garantire valori interi in qualsiasi soluzione. La mia implementazione non gestisce ancora i casi in cui esiste più di una soluzione per bilanciare l'equazione.

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!"]

test

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

inserisci qui la descrizione dell'immagine

Analisi

Funziona impostando la seguente tabella di composizione chimica, composta da specie chimiche per elementi, a cui viene aggiunto un vettore di nullità di addizione (diventando la tabella di composizione chimica aumentata:

tabella di composizione chimica

Le cellule interne vengono rimosse come matrice e invertite, cedendo.

inversione

Viene estratta la colonna più a destra, producendo:

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

Ogni elemento nel vettore è diviso per il gcd degli elementi (1/8), dando:

{-1, -11, 7, 8}

dove i valori negativi verranno posizionati sul lato sinistro della freccia. I valori assoluti di questi sono i numeri necessari per bilanciare l'equazione originale:

soluzione


non dimenticare di aggiungere il punto esclamativo!
Ardnew,

:} ok, e ho aumentato il conteggio dei personaggi
DavidC

Penso che intendi la colonna di destra, non quella di sinistra. Apprezzo la spiegazione (+1) ma mi chiedo: se non fosse il caso che il numero di molecole è uno in più rispetto al numero di elementi, come si fa a pad? Off per leggere il giornale ora.
Peter Taylor,

Per qualche ragione, ho trovato il tuo commento solo oggi. Sì, intendevo dire "colonna di destra". È passato così tanto tempo da quando ho lavorato su questo che non riesco a vedere (o ricordare) dove viene usato il padding. Scusate.
David C

3

Python, 880 caratteri

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!'

test:

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

Produzione:

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

Potrebbe essere molto meno di 880, ma i miei occhi mi stanno già uccidendo ...


2

Python 2, 635 byte

conteggi di byte precedenti: 794, 776, 774, 765, 759, 747, 735, 734, 720, 683, 658, 655, 654, 653, 651, 638, 637, 636 byte.

Il secondo livello di rientro è solo una scheda, il terzo è una scheda quindi uno spazio.

Ad essere onesti, questa è la risposta di jadkik94, ma sono stati rasati così tanti byte, che ho dovuto farlo. Dimmi se posso eliminare tutti i byte!

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!'

salva tre byte:: ''.join(map(chr,range(97,122)))D
aliqandil,

:(, non funziona. Tuttavia, map(chr,range(97,123))funziona con 12 byte salvati.
Zacharý

Oh giusto! è Python 2!
Aliqandil,

1

JavaScript, 682 byte

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);}

Questa è una risposta molto più giocosa (decenni di personaggi!) Della risposta di Kuilin. Potrebbe non essere competitivo perché alcune funzionalità di JS postdatano la sfida.


0

Javascript, 705 byte

(non competitiva, alcune funzionalità dopo la sfida)

Altre soluzioni avevano tutte elementi di forza bruta. Ho cercato un approccio più deterministico, rappresentando l'equazione chimica come un insieme di equazioni lineari, e quindi risolvendo usando l'algoritmo di Gauss-Jordan per prendere la forma ridotta di gherma di quella matrice. Per isolare il banale caso in cui tutto è zero, suppongo che uno degli elementi sia un numero costante - e quel numero è determinato solo da tutti i numeri moltiplicati insieme, per non avere frazioni. Quindi come ultimo passo divideremo ciascuno per il gcd per soddisfare l'ultima condizione.

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"));

golfed

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
Non competitiva, poiché alcune funzionalità postdatano la sfida.
Zacharý,

Oh wow, non ho notato quanti anni avesse. Grazie!
Kuilin Li
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.