Cân bằng phương trình hóa học!


30

Bernd là một học sinh trung học có một số vấn đề về hóa học. Trong lớp, anh ta phải thiết kế các phương trình hóa học cho một số thí nghiệm họ đang làm, chẳng hạn như đốt cháy heptan:

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

Vì toán học không chính xác là môn học mạnh nhất của Bernd, anh ta thường gặp khó khăn trong việc tìm ra các tỷ lệ chính xác giữa các hoạt động và giáo dục về phản ứng. Vì bạn là gia sư của Bernd, nên công việc của bạn là giúp anh ấy! Viết chương trình, tính toán số lượng của từng chất cần thiết để có được phương trình hóa học hợp lệ.

Đầu vào

Đầu vào là một phương trình hóa học không có số lượng. Để thực hiện điều này trong ASCII thuần túy, chúng tôi viết bất kỳ đăng ký nào dưới dạng số thông thường. Tên các phần tử luôn bắt đầu bằng chữ in hoa và có thể được theo sau bởi một chữ nhỏ. Các phân tử được phân tách bằng +các dấu hiệu, một mũi tên nghệ thuật ASCII ->được chèn vào giữa cả hai mặt của phương trình:

Al+Fe2O4->Fe+Al2O3

Đầu vào được kết thúc bằng một dòng mới và sẽ không chứa bất kỳ khoảng trắng nào. Nếu đầu vào không hợp lệ, chương trình của bạn có thể làm bất cứ điều gì bạn muốn.

Bạn có thể giả sử rằng đầu vào không bao giờ dài hơn 1024 ký tự. Chương trình của bạn có thể đọc đầu vào từ đầu vào tiêu chuẩn, từ đối số đầu tiên hoặc theo cách thực hiện được xác định trong thời gian chạy nếu không thể.

Đầu ra

Đầu ra của chương trình của bạn là phương trình đầu vào tăng thêm số. Số lượng nguyên tử cho mỗi nguyên tố phải giống nhau ở cả hai phía của mũi tên. Đối với ví dụ trên, một đầu ra hợp lệ là:

2Al+Fe2O3->2Fe+Al2O3

Nếu số cho một phân tử là 1, hãy bỏ nó. Một số phải luôn là số nguyên dương. Chương trình của bạn phải mang lại số lượng sao cho tổng của chúng là tối thiểu. Chẳng hạn, những điều sau đây là bất hợp pháp:

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

Nếu không có giải pháp, hãy in

Nope!

thay thế. Một đầu vào mẫu không có giải pháp là

Pb->Au

Quy tắc

  • Đây là mã golf. Mã ngắn nhất sẽ thắng.
  • Chương trình của bạn phải chấm dứt trong thời gian hợp lý cho tất cả các đầu vào hợp lý.

Các trường hợp thử nghiệm

Mỗi trường hợp kiểm tra có hai dòng: Một đầu vào và một đầu ra đúng.

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

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

Pb->Au
Nope!

1
Tôi có thể sai, nhưng điều này có vẻ như là một ứng cử viên tự nhiên cho một thách thức lập trình thay vì chơi golf.
DavidC

1
Có lần tôi đã viết một bộ giải phương trình hóa học trên máy tính vẽ đồ thị TI-89 của mình, sử dụng hàm dựng sẵn solve(eval(để giải thích đầu vào :)
mellamokb

3
@mellamokb tại sao bạn không đăng nó, bạn sẽ nhận được một upvote từ tôi về tính độc đáo
ratchet freak

5
"Vì bạn là gia sư của Bernds, nên công việc của bạn là giúp anh ấy!" - Tôi đã nghĩ rằng một gia sư nên dạy Bernd tự suy nghĩ, thay vì viết phần mềm cho anh ta để anh ta không phải: P
n101 101

1
@KuilinLi Không sai, chỉ khác.
FUZxxl

Câu trả lời:


7

C, 438 505 ký tự

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

Chạy như:

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

Các kết quả:

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

+1 này là đáng kính hơn nhiều so với trước. cuộc tranh luận
ardnew

2
Hãy thử sử dụng dấu phẩy làm dấu phân cách câu lệnh để tránh dấu ngoặc nhọn. Ngoài ra, hãy thử thay thế các cấu trúc if-then-other-bằng các toán tử ternary để rút ngắn mã. t [i]> 1? printf ("% s", t [i]): 0; ngắn hơn một byte. Ngoài ra: m [0] giống với * m.
FUZxxl

6

Toán học 507

Tôi sử dụng cách tiếp cận ma trận thành phần hóa học tăng cường được mô tả trong

LRThorne, Một cách tiếp cận sáng tạo để cân bằng hóa học - phương trình phản ứng: một ma trận đơn giản hóa - kỹ thuật nghịch đảo để xác định không gian ma trận null. Hóa học.Giáo dục , 2010, 15, 304 - 308.

Một điều chỉnh nhỏ đã được thêm vào: Tôi chia chuyển vị của vectơ không gian null cho ước số chung lớn nhất của các phần tử để đảm bảo giá trị nguyên trong bất kỳ giải pháp nào. Việc thực hiện của tôi chưa xử lý các trường hợp có nhiều hơn một giải pháp để cân bằng phương trình.

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

Xét nghiệm

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

nhập mô tả hình ảnh ở đây

Phân tích

Nó hoạt động bằng cách thiết lập bảng thành phần hóa học sau, bao gồm các loại hóa chất theo các nguyên tố, trong đó một vectơ vô hiệu bổ sung được thêm vào (trở thành bảng thành phần hóa học tăng cường:

bảng thành phần hóa học

Các tế bào bên trong được loại bỏ như một ma trận và đảo ngược, cho năng suất.

đảo ngược

Cột ngoài cùng bên phải được trích xuất, mang lại:

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

Mỗi phần tử trong vectơ được chia cho gcd của các phần tử (1/8), cho:

{-1, -11, 7, 8}

trong đó các giá trị âm sẽ được đặt ở phía bên trái của mũi tên. Các giá trị tuyệt đối của chúng là các số cần thiết để cân bằng phương trình ban đầu:

dung dịch


đừng quên thêm dấu chấm than!
ardew

:} ok, và tôi đã tăng số lượng nhân vật
DavidC

Tôi nghĩ bạn có nghĩa là cột bên tay phải, không phải cột bên trái. Tôi đánh giá cao lời giải thích (+1) nhưng tôi tự hỏi: nếu không phải là số lượng phân tử nhiều hơn số lượng nguyên tố, làm thế nào để bạn đệm? Tắt để đọc giấy bây giờ.
Peter Taylor

Vì một số lý do, tôi chỉ bắt gặp bình luận của bạn ngày hôm nay. Vâng, tôi đã có nghĩa là "cột bên tay phải". Đã có rất nhiều thời gian trôi qua kể từ khi tôi làm việc này mà tôi không thể thấy (hoặc nhớ) nơi sử dụng đệm. Lấy làm tiếc.
DavidC

3

Python, 880 ký tự

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

Các xét nghiệm:

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

Đầu ra:

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

Có thể ít hơn 880, nhưng đôi mắt của tôi đã giết chết tôi ...


2

Python 2, 635 byte

số byte trước đó: 794, 776, 774, 765, 759, 747, 735, 734, 720, 683, 658, 655, 654, 653, 651, 638, 637, 636 byte.

Cấp độ thụt thứ hai chỉ là một tab, thứ ba là một tab sau đó là một khoảng trắng.

Thành thật mà nói, đây là câu trả lời của jadkik94, nhưng rất nhiều byte đã bị xóa, tôi phải làm điều đó. Hãy cho tôi biết nếu tôi có thể cạo bất kỳ byte nào!

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

lưu ba byte :: ''.join(map(chr,range(97,122)))D
aliqandil

:(, điều đó không hoạt động. Tuy nhiên, map(chr,range(97,123))hoạt động được lưu 12 byte.
Zacharý

ô đung rôi! đó là con trăn 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);}

Đây là một câu trả lời nhiều hơn (nhiều thập kỷ của nhân vật!) Của Kuilin. Có thể không được soạn thảo vì các tính năng JS nhất định hoãn lại thử thách.


0

Javascript, 705 byte

(không cạnh tranh, một số tính năng hoãn thử thách)

Các giải pháp khác đều có yếu tố cưỡng bức. Tôi đã cố gắng cho một cách tiếp cận xác định hơn bằng cách biểu diễn phương trình hóa học như một tập hợp các phương trình tuyến tính, và sau đó giải bằng thuật toán Gauss-Jordan để lấy dạng phản xạ hàng giảm của ma trận đó. Để tách biệt trường hợp tầm thường trong đó mọi thứ đều bằng 0, tôi giả sử rằng một trong các phần tử là một số không đổi - và số đó được xác định bởi tất cả các số nhân với nhau, để không có phân số. Sau đó, bước cuối cùng, chúng ta sẽ chia từng phần cho gcd để đáp ứng điều kiện cuối cùng.

Ung dung:

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

Chơi gôn

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
Không cấu hình, như một số tính năng hoãn lại thách thức.
Zacharý

Ồ wow, tôi không nhận ra cái này bao nhiêu tuổi. Cảm ơn!
Kuilin Li
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.