Équilibrez les équations chimiques!

30

Bernd est un lycéen qui a des problèmes de chimie. En classe, il doit concevoir des équations chimiques pour certaines expériences qu'ils font, telles que la combustion de l'heptane:

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

Comme les mathématiques ne sont pas exactement le sujet le plus solide de Bernd, il a souvent du mal à trouver les rapports exacts entre les pro- et les produits de la réaction. Puisque vous êtes le tuteur de Bernd, c'est votre travail de l'aider! Écrivez un programme qui calcule la quantité de chaque substance nécessaire pour obtenir une équation chimique valide.

Contribution

L'entrée est une équation chimique sans quantités. Afin de rendre cela possible en ASCII pur, nous écrivons tous les abonnements sous forme de nombres ordinaires. Les noms d'élément commencent toujours par une majuscule et peuvent être suivis d'une minuscule. Les molécules sont séparées par des +signes, une flèche de type ASCII ->est insérée entre les deux côtés de l'équation:

Al+Fe2O4->Fe+Al2O3

L'entrée se termine par une nouvelle ligne et ne contiendra aucun espace. Si l'entrée n'est pas valide, votre programme peut faire ce que vous voulez.

Vous pouvez supposer que l'entrée ne dépasse jamais 1024 caractères. Votre programme peut soit lire l'entrée à partir de l'entrée standard, du premier argument ou d'une manière définie par l'implémentation lors de l'exécution si aucun n'est possible.

Sortie

La sortie de votre programme est l'équation d'entrée augmentée de nombres supplémentaires. Le nombre d'atomes pour chaque élément doit être le même des deux côtés de la flèche. Pour l'exemple ci-dessus, une sortie valide est:

2Al+Fe2O3->2Fe+Al2O3

Si le nombre d'une molécule est 1, laissez-le tomber. Un nombre doit toujours être un entier positif. Votre programme doit produire des nombres tels que leur somme soit minimale. Par exemple, ce qui suit est illégal:

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

S'il n'y a pas de solution, imprimez

Nope!

au lieu. Un exemple d'entrée qui n'a pas de solution est

Pb->Au

Règles

  • C'est du code-golf. Le code le plus court gagne.
  • Votre programme doit se terminer dans un délai raisonnable pour toutes les entrées raisonnables.

Cas de test

Chaque scénario de test a deux lignes: une entrée et une sortie correcte.

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

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

Pb->Au
Nope!
FUZxxl
la source
1
Je peux me tromper, mais cela semble être un candidat naturel pour un défi de programmation plutôt que de coder le golf.
DavidC
1
J'ai écrit une fois un solveur d'équation chimique sur ma calculatrice graphique TI-89, en utilisant la solve(fonction intégrée et eval(pour interpréter l'entrée :)
mellamokb
3
@mellamokb pourquoi ne le postez-vous pas, vous obtiendrez un vote positif de ma part pour l'originalité
ratchet freak
5
"Puisque tu es le tuteur de Bernds, c'est à toi de l'aider!" - J'aurais pensé qu'un tuteur devrait enseigner à Bernd à penser par lui-même, plutôt que d'écrire des logiciels pour lui afin qu'il n'ait pas à: P
naught101
1
@KuilinLi Ce n'est pas faux, juste différent.
FUZxxl

Réponses:

7

C, 442 505 caractères

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

Courir comme:

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

Résultats:

C7H16+11O2->7CO2+8H2O
8Al+3Fe2O4->6Fe+4Al2O3
Nope!
bébé-lapin
la source
+1 c'est beaucoup plus respectable que le président. débat
ardnew
2
Essayez d'utiliser des virgules comme séparateurs d'instructions pour éviter les accolades. Essayez également de remplacer les constructions if-then-else par des opérateurs ternaires pour raccourcir le code. t [i]> 1? printf ("% s", t [i]): 0; est un octet plus court. Aussi: m [0] est identique à * m.
FUZxxl
6

Mathematica 507

J'ai utilisé l'approche de la matrice de composition chimique augmentée décrite dans

LRThorne, Une approche innovante pour équilibrer les équations de réaction chimique: une technique matrice-inverse simplifiée pour déterminer l'espace nul de la matrice. Chem.Educator , 2010, 15, 304-308 .

Un léger ajustement a été ajouté: j'ai divisé la transposition du vecteur d'espace nul par le plus grand diviseur commun des éléments pour garantir des valeurs entières dans toutes les solutions. Mon implémentation ne gère pas encore les cas où il existe plusieurs solutions pour équilibrer l'équation.

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

Les tests

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

entrez la description de l'image ici

Une analyse

Il fonctionne en créant le tableau de composition chimique suivant, composé d'espèces chimiques par éléments, auquel un vecteur de nullité d'addition est ajouté (devenant le tableau de composition chimique augmentée:

tableau de composition chimique

Les cellules internes sont retirées sous forme de matrice et inversées, ce qui donne.

inversion

La colonne la plus à droite est extraite, donnant:

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

Chaque élément du vecteur est divisé par le pgcd des éléments (1/8), donnant:

{-1, -11, 7, 8}

où les valeurs négatives seront placées sur le côté gauche de la flèche. Les valeurs absolues de ceux-ci sont les nombres nécessaires pour équilibrer l'équation originale:

Solution

DavidC
la source
n'oubliez pas d'ajouter le point d'exclamation!
ardnew
:} ok, et j'ai augmenté le nombre de caractères
DavidC
Je pense que vous voulez dire la colonne de droite, pas celle de gauche. J'apprécie l'explication (+1) mais je me demande: si ce n'était pas le cas, le nombre de molécules est une de plus que le nombre d'éléments, comment rembourrez-vous? Off pour lire le papier maintenant.
Peter Taylor
Pour une raison quelconque, je n'ai rencontré votre commentaire qu'aujourd'hui. Oui, je voulais dire "colonne de droite". Il s'est passé tellement de temps depuis que j'ai travaillé là-dessus que je ne peux pas voir (ou me souvenir) où le rembourrage est utilisé. Désolé.
DavidC
3

Python, 880 caractères

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"

Sortie:

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

Ça pourrait être bien moins que 880, mais mes yeux me tuent déjà ...

jadkik94
la source
2

Python 2, 635 octets

nombre d'octets précédents: 794, 776, 774, 765, 759, 747, 735, 734, 720, 683, 658, 655, 654, 653, 651, 638, 637, 636 octets.

Le deuxième niveau d'indentation n'est qu'un onglet, le troisième est un onglet puis un espace.

Pour être honnête, c'est la réponse de jadkik94, mais tant d'octets ont été rasés, j'ai dû le faire. Dites-moi si je peux raser des octets!

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ý
la source
enregistrer trois octets:: ''.join(map(chr,range(97,122)))D
aliqandil
:(, cela ne fonctionne pas. Cependant, cela map(chr,range(97,123))fonctionne pour 12 octets enregistrés.
Zacharý
oh c'est vrai! c'est python 2!
aliqandil
1

JavaScript, 682 octets

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

C'est une réponse beaucoup plus golfée (des décennies de personnages!) De Kuilin. Peut ne pas être compétitif car certaines fonctionnalités JS sont postérieures au défi.

Zacharý
la source
0

Javascript, 705 octets

(non compétitif, certaines fonctionnalités sont postérieures au défi)

D'autres solutions comportaient toutes des éléments de forçage brutal. J'ai essayé une approche plus déterministe en représentant l'équation chimique comme un ensemble d'équations linéaires, puis en résolvant en utilisant l'algorithme de Gauss-Jordan pour prendre la forme réduite en échelons de rangées de cette matrice. Afin d'isoler le cas trivial où tout est zéro, je suppose que l'un des éléments est un nombre constant - et ce nombre est déterminé par seulement tous les nombres multipliés ensemble, afin de ne pas avoir de fractions. Ensuite, comme étape finale, nous diviserons chacun par le pgcd pour satisfaire la dernière condition.

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

Golfé

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
la source
1
Non compétitif, car certaines fonctionnalités sont postérieures au défi.
Zacharý
Oh wow, je n'ai pas remarqué son âge. Merci!
Kuilin Li