Equilibre equações químicas!

30

Bernd é um estudante do ensino médio que tem alguns problemas em química. Na aula, ele tem que projetar equações químicas para alguns experimentos que estão fazendo, como a combustão de heptano:

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

Como a matemática não é exatamente o assunto mais forte de Bernd, ele costuma ter dificuldade em encontrar as proporções exatas entre os profissionais e os educadores da reação. Como você é o tutor de Bernd, é seu trabalho ajudá-lo! Escreva um programa que calcule a quantidade de cada substância necessária para obter uma equação química válida.

Entrada

A entrada é uma equação química sem quantidades. Para tornar isso possível em ASCII puro, escrevemos quaisquer assinaturas como números comuns. Os nomes dos elementos sempre começam com uma letra maiúscula e podem ser seguidos por um minúsculo. As moléculas são separadas por +sinais; uma seta da arte ASCII ->é inserida entre os dois lados da equação:

Al+Fe2O4->Fe+Al2O3

A entrada é finalizada com uma nova linha e não contém espaços. Se a entrada for inválida, seu programa poderá fazer o que quiser.

Você pode supor que a entrada nunca tenha mais que 1024 caracteres. Seu programa pode ler a entrada da entrada padrão, do primeiro argumento ou de uma maneira definida por implementação em tempo de execução, se nada for possível.

Saída

A saída do seu programa é a equação de entrada aumentada com números extras. O número de átomos para cada elemento deve ser o mesmo nos dois lados da seta. Para o exemplo acima, uma saída válida é:

2Al+Fe2O3->2Fe+Al2O3

Se o número de uma molécula for 1, solte-o. Um número deve sempre ser um número inteiro positivo. Seu programa deve gerar números de forma que sua soma seja mínima. Por exemplo, o seguinte é ilegal:

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

Se não houver solução, imprima

Nope!

em vez de. Uma entrada de amostra que não tem solução é

Pb->Au

Regras

  • Isso é código-golfe. O código mais curto vence.
  • Seu programa deve terminar em tempo razoável para todas as entradas razoáveis.

Casos de teste

Cada caso de teste possui duas linhas: uma entrada e uma saída correta.

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

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

Pb->Au
Nope!
FUZxxl
fonte
11
Eu posso estar errado, mas isso parece ser um candidato natural a um desafio de programação, em vez de codificar golfe.
21412
11
Uma vez escrevi um solucionador de equação química na minha TI-89 calculadora gráfica, usando o built-in solve(função e eval(para interpretar a entrada :)
mellamokb
3
@mellamokb por que você não publicá-la, você terá uma upvote de mim por originalidade
catraca aberração
5
"Como você é o tutor de Bernds, é seu trabalho ajudá-lo!" - Eu pensaria que um tutor deveria estar ensinando Bernd a pensar por si mesmo, em vez de escrever um software para ele, para que ele não precise: P
naught101
11
@KuilinLi Não está errado, apenas diferente.
FUZxxl

Respostas:

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!
coelho bebê
fonte
+1 isso é muito mais respeitável que o pres. debate
ardnew 17/10/12
2
Tente usar vírgulas como separadores de instruções para evitar chaves. Tente também substituir construções if-then-else-por operadores ternários para reduzir o código. t [i]> 1? printf ("% s", t [i]): 0; é um byte mais curto. Além disso: m [0] é igual a * m.
FUZxxl 17/10/12
6

Mathematica 507

Empreguei a abordagem de matriz de composição química aumentada descrita em

LRThorne, Uma abordagem inovadora para equilibrar equações de reação química: uma técnica simplificada inversa de matriz para determinar o espaço nulo da matriz. Chem.Educator , 2010, 15, .

Um pequeno ajuste foi adicionado: eu dividi a transposição do vetor de espaço nulo pelo maior divisor comum dos elementos para garantir valores inteiros em qualquer solução. Minha implementação ainda não lida com casos em que há mais de uma solução para equilibrar a equação.

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

Testes

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

insira a descrição da imagem aqui

Análise

Ele funciona configurando a seguinte tabela de composição química, consistindo em espécies químicas por elementos, à qual um vetor de nulidade adicional é adicionado (tornando-se a tabela de composição química aumentada:

tabela de composição química

As células internas são removidas como uma matriz e invertidas, produzindo.

inversão

A coluna mais à direita é extraída, produzindo:

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

Cada elemento do vetor é dividido pelo MDC dos elementos (1/8), fornecendo:

{-1, -11, 7, 8}

onde os valores negativos serão colocados no lado esquerdo da seta. Os valores absolutos destes são os números necessários para equilibrar a equação original:

solução

DavidC
fonte
não esqueça de adicionar o ponto de exclamação!
ardnew
:} ok, e eu aumentei a contagem de caracteres #
1860
Acho que você quer dizer a coluna da direita, não a da esquerda. Aprecio a explicação (+1), mas me pergunto: se não foi o caso de o número de moléculas ser um a mais do que o número de elementos, como você preenche? Desligado para ler o jornal agora.
22412 Peter
Por alguma razão, me deparei com seu comentário hoje. Sim, eu quis dizer "coluna da direita". Passou tanto tempo desde que trabalhei nisso que não consigo ver (ou lembrar) onde o preenchimento é usado. Desculpe.
DavidC 26/06
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!'

Testes:

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

Saída:

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

Pode ser bem menor que 880, mas meus olhos já estão me matando ...

jadkik94
fonte
2

Python 2, 635 bytes

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

O segundo nível de recuo é apenas uma guia, o terceiro é uma guia e, em seguida, um espaço.

Para ser honesto, esta é a resposta de jadkik94, mas muitos bytes foram raspados, eu tive que fazê-lo. Diga-me se consigo raspar alguns 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ý
fonte
salve três bytes:: ''.join(map(chr,range(97,122)))D
aliqandil
:(, isso não funciona. No entanto, map(chr,range(97,123))funciona com 12 bytes salvos.
Zacharý
Oh, certo! é 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);}

Essa é uma resposta muito mais desafiadora (décadas de personagens!) De Kuilin. Pode não ser competitivo porque certos recursos de JS pós-datam o desafio.

Zacharý
fonte
0

Javascript, 705 bytes

(não concorrente, alguns recursos pós-datam o desafio)

Todas as outras soluções tinham elementos de força bruta. Tentei uma abordagem mais determinística, representando a equação química como um conjunto de equações lineares e, em seguida, resolvendo usando o algoritmo de Gauss-Jordan para assumir a forma reduzida de escalão de linha dessa matriz. Para isolar o caso trivial em que tudo é zero, presumo que um dos elementos seja um número constante - e esse número seja determinado apenas por todos os números multiplicados juntos, para não ter frações. Como passo final, dividiremos cada um pelo MDC para satisfazer a última condição.

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

Golfe

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
fonte
11
Não-competitivo, pois alguns recursos pós-datam o desafio.
Zacharý
Oh uau, eu não notei quantos anos isso tinha. Obrigado!
Kuilin Li