Seimbangkan persamaan kimia!

30

Bernd adalah siswa sekolah menengah yang memiliki beberapa masalah dalam kimia. Di kelas ia harus merancang persamaan kimia untuk beberapa percobaan yang mereka lakukan, seperti pembakaran heptana:

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

Karena matematika bukanlah subjek terkuat Bernd, ia sering kesulitan menemukan rasio yang tepat antara pro dan educts dari reaksi. Karena Anda adalah tutor Bernd, tugas Anda adalah membantunya! Tulis program, yang menghitung jumlah masing-masing zat yang dibutuhkan untuk mendapatkan persamaan kimia yang valid.

Memasukkan

Input adalah persamaan kimia tanpa jumlah. Untuk memungkinkan hal ini dalam ASCII murni, kami menulis langganan apa pun sebagai angka biasa. Nama elemen selalu dimulai dengan huruf kapital dan dapat diikuti oleh sebuah sangat kecil. Molekul dipisahkan dengan +tanda - tanda, panah ASCII-art ->dimasukkan di antara kedua sisi persamaan:

Al+Fe2O4->Fe+Al2O3

Input diakhiri dengan baris baru dan tidak akan berisi spasi apa pun. Jika input tidak valid, program Anda dapat melakukan apa pun yang Anda suka.

Anda dapat berasumsi, bahwa input tidak pernah lebih dari 1024 karakter. Program Anda dapat membaca input dari input standar, dari argumen pertama atau dalam implementasi yang ditentukan saat runtime jika tidak ada yang mungkin.

Keluaran

Output dari program Anda adalah persamaan input yang ditambah dengan angka tambahan. Jumlah atom untuk setiap elemen harus sama di kedua sisi panah. Untuk contoh di atas, output yang valid adalah:

2Al+Fe2O3->2Fe+Al2O3

Jika nomor untuk molekul adalah 1, jatuhkan. Angka harus selalu berupa bilangan bulat positif. Program Anda harus menghasilkan angka sedemikian sehingga jumlah mereka minimal. Misalnya, berikut ini ilegal:

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

Jika tidak ada solusi, cetak

Nope!

sebagai gantinya. Input sampel yang tidak memiliki solusi adalah

Pb->Au

Aturan

  • Ini adalah kode-golf. Kode terpendek menang.
  • Program Anda harus diakhiri dalam waktu yang wajar untuk semua input yang masuk akal.

Uji Kasus

Setiap test case memiliki dua jalur: Input dan output yang benar.

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

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

Pb->Au
Nope!
FUZxxl
sumber
1
Saya bisa saja salah, tetapi ini sepertinya kandidat alami untuk tantangan pemrograman, bukan kode golf.
DavidC
1
Saya pernah menulis pemecah persamaan kimia pada kalkulator grafik TI-89 saya, menggunakan solve(fungsi eval(
bawaan
3
@mellamokb kenapa tidak Anda posting, Anda akan mendapatkan upvote dari saya untuk orisinalitas
ratchet freak
5
"Karena kamu tutor Bernds, tugasmu adalah membantunya!" - Saya akan berpikir seorang tutor seharusnya mengajar Bernd untuk berpikir untuk dirinya sendiri, daripada menulis perangkat lunak untuknya sehingga dia tidak harus: P
naught101
1
@ Kuilinli Tidak salah, hanya berbeda.
FUZxxl

Jawaban:

7

C, 442 505 karakter

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

Jalankan sebagai:

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

Hasil:

C7H16+11O2->7CO2+8H2O
8Al+3Fe2O4->6Fe+4Al2O3
Nope!
bayi-kelinci
sumber
+1 ini jauh lebih terhormat daripada hadiah. debat
ardnew
2
Coba gunakan koma sebagai pemisah pernyataan untuk menghindari kurung kurawal. Juga cobalah mengganti if-then-else-constructs dengan operator ternary untuk mempersingkat kode. t [i]> 1? printf ("% s", t [i]): 0; lebih pendek satu byte. Juga: m [0] sama dengan * m.
FUZxxl
6

Mathematica 507

Saya menggunakan pendekatan matriks komposisi kimia tambahan yang dijelaskan dalam

LRThorne, Suatu pendekatan inovatif untuk menyeimbangkan persamaan reaksi kimia: teknik pembalikan matriks yang disederhanakan untuk menentukan ruang nol matriks. Chem. Pendidik , 2010, 15, 304 - 308.

Satu perubahan kecil ditambahkan: Saya membagi transpos vektor ruang-nol dengan pembagi umum terbesar dari elemen untuk memastikan nilai integer dalam solusi apa pun. Implementasi saya belum menangani kasus di mana ada lebih dari satu solusi untuk menyeimbangkan persamaan.

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

Tes

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

masukkan deskripsi gambar di sini

Analisis

Ia bekerja dengan mengatur tabel komposisi kimia berikut, yang terdiri dari spesies kimia oleh unsur-unsur, yang ditambahkan vektor nullity (menjadi tabel komposisi kimia yang ditambah:

tabel komposisi kimia

Sel-sel dalam dihapus sebagai matriks dan terbalik, menghasilkan.

inversi

Kolom paling kanan diekstraksi, menghasilkan:

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

Setiap elemen dalam vektor dibagi oleh gcd elemen (1/8), memberikan:

{-1, -11, 7, 8}

di mana nilai negatif akan ditempatkan di sisi kiri panah. Nilai absolut dari ini adalah angka yang diperlukan untuk menyeimbangkan persamaan asli:

larutan

DavidC
sumber
jangan lupa menambahkan tanda seru!
ardnew
:} ok, dan saya menaikkan jumlah karakter
DavidC
Saya pikir maksud Anda kolom kanan, bukan yang kiri. Saya menghargai penjelasannya (+1) tetapi saya bertanya-tanya: jika bukan karena jumlah molekulnya satu lebih banyak dari jumlah elemen, bagaimana menurut Anda? Mati untuk membaca koran sekarang.
Peter Taylor
Untuk beberapa alasan, saya hanya menemukan komentar Anda hari ini. Ya, maksud saya adalah "kolom kanan". Begitu banyak waktu telah berlalu sejak saya mengerjakan ini sehingga saya tidak bisa melihat (atau mengingat) di mana bantalan digunakan. Maaf.
DavidC
3

Python, 880 karakter

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

Tes:

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

Keluaran:

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

Bisa jadi kurang dari 880, tapi mataku sudah membunuhku ...

jadkik94
sumber
2

Python 2, 635 byte

jumlah byte sebelumnya: 794, 776, 774, 765, 759, 747, 735, 734, 720, 683, 658, 654, 653, 653, 651, 638, 637, 636 byte.

Level indentasi kedua hanya tab, yang ketiga adalah tab lalu spasi.

Sejujurnya, ini adalah jawaban jadkik94, tetapi begitu banyak byte yang dicukur, saya harus melakukannya. Katakan padaku jika aku bisa memotong byte apa pun!

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ý
sumber
simpan tiga byte:: ''.join(map(chr,range(97,122)))D
aliqandil
:(, itu tidak berhasil. Namun, map(chr,range(97,123))berfungsi untuk 12 byte yang disimpan.
Zacharý
oh benar! itu python 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);}

Ini adalah jawaban karakter Kuilin yang jauh lebih golf (dekade karakter!). Mungkin tidak bersaing karena fitur JS tertentu menunda tantangan.

Zacharý
sumber
0

Javascript, 705 byte

(non-bersaing, beberapa fitur mengungguli tantangan)

Solusi lain semuanya memiliki elemen brute-forcing. Saya mencoba pendekatan yang lebih deterministik dengan merepresentasikan persamaan kimia sebagai seperangkat persamaan linear, dan kemudian menyelesaikannya dengan menggunakan algoritma Gauss-Jordan untuk mengambil bentuk eselon baris tereduksi dari matriks itu. Untuk mengisolasi kasus sepele di mana semuanya nol, saya berasumsi bahwa salah satu elemen adalah angka konstan - dan angka itu ditentukan oleh semua angka yang dikalikan bersama, agar tidak memiliki pecahan. Kemudian sebagai langkah terakhir kita akan membagi masing-masing dengan gcd untuk memenuhi kondisi terakhir.

Tidak Disatukan:

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
sumber
1
Noncompeting, karena beberapa fitur menunda tantangan.
Zacharý
Oh wow aku tidak menyadari berapa umur ini. Terima kasih!
Kuilin Li