¡Balancea las ecuaciones químicas!

30

Bernd es un estudiante de secundaria que tiene algunos problemas en química. En clase tiene que diseñar ecuaciones químicas para algunos experimentos que están haciendo, como la combustión de heptano:

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

Como las matemáticas no son exactamente la asignatura más sólida de Bernd, a menudo le resulta difícil encontrar las proporciones exactas entre los pro- y eductos de la reacción. Como eres el tutor de Bernd, ¡es tu trabajo ayudarlo! Escriba un programa que calcule la cantidad de cada sustancia necesaria para obtener una ecuación química válida.

Entrada

La entrada es una ecuación química sin cantidades. Para hacer esto posible en ASCII puro, escribimos cualquier suscripción como números ordinarios. Los nombres de los elementos siempre comienzan con una letra mayúscula y pueden ir seguidos de una minúscula. Las moléculas se separan con +signos, ->se inserta una flecha ASCII-art entre ambos lados de la ecuación:

Al+Fe2O4->Fe+Al2O3

La entrada se termina con una nueva línea y no contendrá espacios. Si la entrada no es válida, su programa puede hacer lo que quiera.

Puede suponer que la entrada nunca tiene más de 1024 caracteres. Su programa puede leer la entrada desde la entrada estándar, desde el primer argumento o de una manera definida por la implementación en tiempo de ejecución si ninguno es posible.

Salida

La salida de su programa es la ecuación de entrada aumentada con números adicionales. El número de átomos para cada elemento debe ser el mismo en ambos lados de la flecha. Para el ejemplo anterior, una salida válida es:

2Al+Fe2O3->2Fe+Al2O3

Si el número de una molécula es 1, suéltelo. Un número siempre debe ser un entero positivo. Su programa debe producir números tales que su suma sea mínima Por ejemplo, lo siguiente es ilegal:

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

Si no hay solución, imprima

Nope!

en lugar. Una entrada de muestra que no tiene solución es

Pb->Au

Reglas

  • Este es el código de golf. El código más corto gana.
  • Su programa debe finalizar en un tiempo razonable para todas las entradas razonables.

Casos de prueba

Cada caso de prueba tiene dos líneas: una entrada y una salida correcta.

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

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

Pb->Au
Nope!
FUZxxl
fuente
1
Podría estar equivocado, pero esto parece un candidato natural para un desafío de programación en lugar de código de golf.
DavidC
1
Una vez escribí un solucionador de ecuaciones químicas en mi calculadora gráfica TI-89, usando la solve(función incorporada e eval(interpretando la entrada :)
mellamokb
3
@mellamokb, ¿por qué no lo publicas? Recibirás un voto positivo de mí por originalidad
fanático del trinquete el
55
"Ya que eres tutor de Bernds, ¡es tu trabajo ayudarlo!" - Pensé que un tutor debería enseñarle a Bernd a pensar por sí mismo, en lugar de escribir un software para él, de modo que no tenga que: P
nada101
1
@KuilinLi No está mal, solo es diferente.
FUZxxl

Respuestas:

7

C, 442 505 caracteres

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

Correr como:

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

Resultados:

C7H16+11O2->7CO2+8H2O
8Al+3Fe2O4->6Fe+4Al2O3
Nope!
conejo bebé
fuente
+1 esto es mucho más respetable que el pres. debate
ardnew
2
Intente usar comas como separadores de enunciados para evitar llaves. También intente reemplazar if-then-else-constructs con operadores ternarios para acortar el código. t [i]> 1? printf ("% s", t [i]): 0; es un byte más corto. Además: m [0] es lo mismo que * m.
FUZxxl
6

Mathematica 507

Empleé el enfoque de matriz de composición química aumentada descrito en

LRThorne, un enfoque innovador para equilibrar las ecuaciones de reacción química: una técnica inversa de matriz simplificada para determinar el espacio nulo de la matriz. Chem.Educator , 2010, 15, 304-308 .

Se agregó un ligero ajuste: dividí la transposición del vector de espacio nulo por el máximo divisor común de los elementos para garantizar valores enteros en cualquier solución. Mi implementación aún no maneja casos en los que haya más de una solución para equilibrar la ecuación.

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

Pruebas

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

ingrese la descripción de la imagen aquí

Análisis

Funciona configurando la siguiente tabla de composición química, que consta de especies químicas por elementos, a la que se agrega un vector de nulidad de adición (convirtiéndose en la tabla de composición química aumentada:

tabla de composición química

Las células internas se eliminan como una matriz y se invierten, produciendo.

inversión

Se extrae la columna de la derecha, obteniendo:

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

Cada elemento en el vector se divide por el mcd de los elementos (1/8), dando:

{-1, -11, 7, 8}

donde los valores negativos se colocarán en el lado izquierdo de la flecha. Los valores absolutos de estos son los números necesarios para equilibrar la ecuación original:

solución

DavidC
fuente
¡no olvides agregar el signo de exclamación!
ardnew
:} ok, y subí el número de personajes
DavidC
Creo que te refieres a la columna de la derecha, no a la izquierda. Agradezco la explicación (+1) pero me pregunto: si no fuera el caso de que el número de moléculas sea uno más que el número de elementos, ¿cómo se rellena? Fuera para leer el periódico ahora.
Peter Taylor
Por alguna razón, solo encontré tu comentario hoy. Sí, quise decir "columna de la derecha". Ha pasado tanto tiempo desde que trabajé en esto que no puedo ver (ni recordar) dónde se usa el relleno. Lo siento.
DavidC
3

Python, 880 caracteres

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

Pruebas:

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

Salida:

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

Podría ser mucho menos de 880, pero mis ojos ya me están matando ...

jadkik94
fuente
2

Python 2, 635 bytes

conteo de bytes anteriores: 794, 776, 774, 765, 759, 747, 735, 734, 720, 683, 658, 655, 654, 653, 651, 638, 637, 636 bytes.

El segundo nivel de sangría es solo una pestaña, el tercero es una pestaña y luego un espacio.

Para ser honesto, esta es la respuesta de jadkik94, pero tantos bytes se afeitaron, tuve que hacerlo. ¡Dime si puedo eliminar los bytes!

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!'
Zacharý
fuente
guardar tres bytes:: ''.join(map(chr,range(97,122)))D
aliqandil
:(, eso no funciona. Sin embargo, map(chr,range(97,123))funciona para 12 bytes guardados.
Zacharý
¡Correcto! ¡Es 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);}

Esta es una respuesta mucho más desarrollada (¡décadas de personajes!) De Kuilin. Puede no ser competitivo porque ciertas características de JS son posteriores al desafío.

Zacharý
fuente
0

Javascript, 705 bytes

(no competitiva, algunas características son posteriores al desafío)

Todas las otras soluciones tenían elementos de fuerza bruta. Intenté un enfoque más determinista representando la ecuación química como un conjunto de ecuaciones lineales, y luego resolviendo usando el algoritmo de Gauss-Jordan para tomar la forma reducida de fila-escalón de esa matriz. Para aislar el caso trivial donde todo es cero, supongo que uno de los elementos es un número constante, y ese número está determinado por todos los números multiplicados juntos, para no tener fracciones. Luego, como paso final, dividiremos cada uno por el mcd para satisfacer la última condición.

Sin golf:

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

Kuilin Li
fuente
1
Sin competencia, ya que algunas características son posteriores al desafío.
Zacharý
Oh wow, no me di cuenta de cuántos años tenía esto. ¡Gracias!
Kuilin Li el