Збалансуйте хімічні рівняння!


30

Бернд - гімназист, який має деякі проблеми в хімії. У класі він повинен розробити хімічні рівняння для деяких експериментів, які вони роблять, наприклад, спалювання гептану:

З 7 Н 16 + 11O 2 → 7CO 2 + 8H 2 O

Оскільки математика не є найсильнішим предметом Бернда, йому часто важко знайти точні співвідношення між про- та вихованцями реакції. Оскільки ви вихователь Бернда, ваша робота допомогти йому! Напишіть програму, яка обчислює кількість кожної речовини, необхідної для отримання дійсного хімічного рівняння.

Вхідні дані

Вхід - хімічне рівняння без сум. Щоб зробити це можливим у чистому ASCII, ми пишемо будь-які підписки як звичайні номери. Імена елементів завжди починаються з великої літери і може супроводжуватися мінусом. Молекули розділені +знаками, ->між обома сторонами рівняння вставлена стрілка ASCII :

Al+Fe2O4->Fe+Al2O3

Вхід закінчується новим рядком і не містить пробілів. Якщо введення недійсне, програма може робити все, що завгодно.

Ви можете припустити, що вхід ніколи не перевищує 1024 символів. Ваша програма може або читати вхід зі стандартного вводу, з першого аргументу, або у визначений реалізацією спосіб під час виконання, якщо жодне з них неможливо.

Вихідні дані

Вихідною програмою є вхідне рівняння, доповнене додатковими числами. Кількість атомів для кожного елемента повинна бути однаковою з обох сторін стрілки. У наведеному вище прикладі допустимим результатом є:

2Al+Fe2O3->2Fe+Al2O3

Якщо число молекули дорівнює 1, киньте її. Число завжди повинно бути додатним цілим числом. Ваша програма повинна отримати такі цифри, щоб їх сума була мінімальною. Наприклад, таке незаконне:

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

Якщо рішення немає, надрукуйте

Nope!

замість цього. Зразок вводу, який не має рішення

Pb->Au

Правила

  • Це код-гольф. Виграє найкоротший код.
  • Ваша програма повинна закінчитися в розумний час для всіх розумних вкладів.

Випробування

Кожен тестовий випадок має два рядки: введення та правильний вихід.

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

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

Pb->Au
Nope!

1
Я можу помилятися, але це здається природним кандидатом на програмування, а не гольф коду.
DavidC

1
Одного разу я написав розв’язувач хімічного рівняння на моєму графічному калькуляторі TI-89, використовуючи вбудовану solve(функцію та eval(інтерпретувати вхід :)
mellamokb

3
@mellamokb чому б ти не опублікував це, ти отримаєш нагороду від мене за оригінальність
ratchet freak

5
"Оскільки ви вихователь Берндса, ваша робота допомогти йому!" - Я б подумав, що репетитор повинен навчити Бернда думати про себе, а не писати для нього програмне забезпечення, щоб йому не довелося: P
naught101

1
@KuilinLi Це не помиляється, просто різне.
FUZxxl

Відповіді:


7

C, 442 505 символів

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

Виконувати як:

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

Результати:

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

+1 це набагато респектабельніше, ніж прес. дебати
ardnew

2
Спробуйте використовувати коми як роздільники тверджень, щоб уникнути фігурних дужок. Також спробуйте замінити if-then-else-конструкції на потрійні оператори, щоб скоротити код. t [i]> 1? printf ("% s", t [i]): 0; на один байт коротше. Також: m [0] те саме, що * m.
FUZxxl

6

Математика 507

Я використовував матричний підхід доповненого хімічного складу, описаний в

LRThorne, Інноваційний підхід до збалансування рівнянь хімічних реакцій: спрощена матриця - зворотна методика визначення нульового простору матриці. Chem.Educator , 2010, 15, 304 - 308.

Додано ще одне незначне налаштування: я розділив перенесення нульового простору на найбільший спільний дільник елементів для забезпечення цілих значень у будь-яких рішеннях. Моя реалізація ще не розглядає випадки, коли існує рівновага одного балансування рівняння.

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

Тести

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

enter image description here

Аналіз

Це працює, встановивши таку таблицю хімічного складу, що складається з хімічних видів за елементами, до якої додається вектор нульового значення (стає таблицею доповненого хімічного складу:

chemical composition table

Внутрішні клітини видаляються у вигляді матриці та перевертаються, отримуючи вихід.

inversion

Витягується найправіший стовпець, отримуючи:

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

Кожен елемент у векторі ділиться на gcd елементів (1/8), даючи:

{-1, -11, 7, 8}

де негативні значення будуть розміщені на лівій частині стрілки. Абсолютні значення - це числа, необхідні для збалансування вихідного рівняння:

solution


не забудьте додати знак оклику!
ardnew

:} добре, і я збільшив кількість персонажів
DavidC

Я думаю, ви маєте на увазі правий стовпчик, а не лівий. Я вдячний за пояснення (+1), але мені цікаво: якщо це не було так, що кількість молекул на один більше, ніж кількість елементів, то як ти робиш колодки? Вимкнено, щоб прочитати документ зараз.
Пітер Тейлор

Чомусь я сьогодні натрапив лише на ваш коментар. Так, я мав на увазі "правий стовпчик". З моменту того, як я працював над цим, пройшло так багато часу, що я не бачу (або пам’ятаю) місця, де використовується підкладка. Вибачте.
DavidC

3

Пітон, 880 символів

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

Тести:

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

Вихід:

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

Може бути набагато менше 880, але очі вже вбивають мене ...


2

Python 2, 635 байт

число попередніх байтів: 794, 776, 774, 765, 759, 747, 735, 734, 720, 683, 658, 655, 654, 653, 651, 638, 637, 636 байт.

Другий рівень відступу - це лише вкладка, третій - вкладка, а потім пробіл.

Якщо чесно, це відповідь jadkik94, але стільки байтів було поголено, я повинен був це зробити. Скажи мені, чи можу я поголити будь-які байти!

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

збережіть три байти:: ''.join(map(chr,range(97,122)))D
aliqandil

:(, це не працює. Однак map(chr,range(97,123))працює на 12 байт.
Zacharý

Авжеж! це пітон 2!
aliqandil

1

JavaScript, 682 байти

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

Це набагато більше гольфу (десятиліття персонажів!) Відповіді Куйліна. Це може бути неконкурентоспроможним, оскільки деякі функції JS відкладають виклик.


0

Javascript, 705 байт

(неконкурентоздатні, деякі функції відкладають виклик)

В інших рішеннях усі були елементи жорстокого форсування. Я намагався зробити більш детермінований підхід, представляючи хімічне рівняння як сукупність лінійних рівнянь, а потім вирішуючи за допомогою алгоритму Гаусса-Йордана прийняти скорочену рядок-ешелон форму цієї матриці. Для того, щоб виділити тривіальний випадок, коли все дорівнює нулю, я вважаю, що один з елементів є постійним числом - і це число визначається просто всіма числами, помноженими разом, щоб не було дробів. Тоді як заключний крок ми розділимо кожен на gcd, щоб задовольнити останню умову.

Безголовки:

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

Гольф

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
Неконкурентні, оскільки деякі функції відкладають завдання.
Zacharý

О, уау, я не помітив, скільки це було років. Спасибі!
Куїлін Лі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.