ugrás a tartalomhoz

képletelemzés (kémia)

jeti · 2007. Már. 31. (Szo), 17.35
Sziasztok!

Kémiai (félkonstitúciós) képleteket szeretnék elemezni. A képlet alapján a php programommal szeretném megmondani, hogy melyik atomból mennyi van az adott molekulában. Elsősorban szerves anyagokat.
Pl.:
A metán a legegyszerűbb eset. [CH4: 1db C és 4 db H atom]
Még a hex-1-én is könnyű. [C6H12: 6 db C és 12 db H atom]
De az ecetsavnál már bonyolódik a helyzet. [CH3COOH: 2 db C, 4db H és 2db O atom]
Nem beszélve például a trimetil-amin-ról [(CH3)3N: 3 db C, 9 db H és 1 db N atom]
és a N-metil-formamid-ról. [CH3N(H)HCO: 2 db C, 5 db H, 1 db O és 1db N atom]
Ha még ez sem volna elég, akkor lehet még fűszerezni kationokkal. :-)
Pl.: A nátrium-palmitát (egyfajta szappan). [CH3(CH2)14COONa: 16 db C atom, 31 db H, 2 db O és 1 db Na (!)]

Szóval a rövid probléma felvázolás után a segítségeteket szeretném kérni. Már többféleképpen próbáltam megoldani a problémát, a nagy része már megvan, de még az egyszerű zárójeleket (az egymásba ágyazottakról nem beszélve) nem tudom kezelni.
Az első megközelítésben a szabványos kifejezésekhez fordultam, de mivel nem nagyon ismerem ezt a témát (most olvastam el az ide vonatkozó részeket), nem jutottam velük előrébb. Utána a képleten karakterenként mentem végig, de így borzasztó bonyolult lett, és megmaradt a fent említetett probléma. Biztos van egyszerűbb megoldás is, csak már nem látom az erdőtől a fát... (Fordítva jobb, de így kifejezőbb. :-) )
A forráskódhoz lásd az első hozzászólást. A print()-el megjelenített információk csak a tesztelést segítik.

A szabályok, amiket figyelembe kell venni. (Leegyszerűsítve, nem definíció szerint.):
1.) Minden atom nagybetűvel kezdődik, amit követhet a kisbetűs folytatása, ha több betűs. (pl.: C: szén, Na: nátrium) Összesen max. 2 betűs lehet.
2.) Az atom utáni alsó indexben lévő szám (itt nincs alsó indexelve), azt jelenti, hogy az adott atomból hány darab van. (C6H12: 6 db C és 12 db H atom)
3.) Ha egy szám a zárójel után szerepel, akkor a zárójelben lévő összes atom számát, ennyivel kell megszorozni. Ha nincs az atom után szám, akkor 1 van belőle. [(CH3)3N: 3 db C, 9 db H és 1 db N atom]

Kérem, aki tud, segítsen. Előre is köszönöm.
 
1

forráskód

jeti · 2007. Már. 31. (Szo), 17.38

<FORM METHOD="POST" ACTION="">
<INPUT TYPE="text" NAME="keplet">
<INPUT TYPE="submit">
</FORM>
<?php
print("<BR>Átalakítás előtt: ".$keplet);
print("<BR>Összegképlet: ".osszkeplet($keplet));

function osszkeplet($mezo)
{
$as=-1;
$an=""; 
$db="";
//$zj="";
for($i=0; $i<=strlen($mezo); $i++)
{
print("<BR>*".$mezo{$i}."*<BR>");
if ($mezo{$i}=="") 
 {
 if ($db[$as]=="") {$db[$as]=1;}
continue;
 }
if ($mezo{$i}=="(") 
 {
 if ($db[$as]=="") {$db[$as]=1;}
// $zj ???
continue;
 }
if (ereg("[A-Z]",$mezo{$i},$t)) 
{
if (($as!=-1) and ($db[$as]=="")) {$db[$as]=1;}
$as++; $an[$as]=$mezo{$i};
 }
if ((ereg("[a-z]",$mezo{$i},$t)) and (ereg("[A-Z]",$an[$as],$t))) {$an[$as].=$mezo{$i};}
if (ereg("[0-9]",$mezo{$i},$t)) 
{
if (! isset($db[$as])) {$db[$as]=$mezo{$i};} else {$db[$as].=$mezo{$i};}
 }
}
print("<BR><HR><BR>");
$keplet="";
for($i=0; $i<=$as; $i++)
{
if ($an[$i]!="")
{
for($ii=0; $ii<=$as; $ii++)
{
if (($an[$i]==$an[$ii]) and ($i!=$ii)) {$db[$i]+=$db[$ii]; unset($an[$ii]); unset($db[$ii]);}
}
print("<BR>".$an[$i].": ".$db[$i]."<BR>");
if ($db[$i]=="1") $db[$i]="";
$okeplet.=$an[$i].$db[$i];
}
}
return $okeplet;
}
?>
2

Nekem sikerült :-)

N0r3i · 2007. Ápr. 2. (H), 14.21
Szia!

Annyi bizgatta a fantáziámat a dolog, hogy egy jó fél óra alatt összedobtam. Végtére is vegyész (is) lennék, vagy mi a szösz :D Nekem működik, bár a nem a legszebb kód.
Az egyetlen igazán hasznos észrevételem: a zárójeles dolog kiértékelését érdemes rrekurzívan megoldani, így tetszőlegesen sok zárójellel is működik.

<FORM METHOD="POST" ACTION="">
<INPUT TYPE="text" NAME="keplet">
<INPUT TYPE="submit">
</FORM>
<?php
$keplet = $_POST['keplet'];
print("<BR>Átalakítás előtt: ".$keplet);
print("<BR>Összegképlet: "); print_r(osszkeplet($keplet));

function osszkeplet($mezo)
{
	$nyz = '([{<';
	$zz  = ')]}>';
	$nagybetu = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
	$kisbetu = 'abcdefghijklmnopqrstuvwxyz';
	$szam = '0123456789';
	$pos = 0;
	$res = array();
	$length = strlen($mezo);
	
	while($pos<$length)
	{
		if (strpos($nyz, $mezo[$pos])===false)
		{
			// nem nyitó zárójel
			if (strpos($nagybetu, $mezo[$pos])===false) $res[] = 'HIBA'; // csak nagybetű lehet
			$pos++;	// következő
			if (@strpos($kisbetu, $mezo{$pos})===false)	// nem kisbetű
			{
				if (@strpos($szam, $mezo[$pos])===false) // nem szám
				{
					$res[$mezo[$pos-1]] += 1;
					continue;
				}
				else
				{
					// szám volt
					$pos++;	// következő
					if (@strpos($szam, $mezo[$pos])===false) // nem szám
					{
						$res[$mezo[$pos-2]] += $mezo[$pos-1];
						continue;
					}
					else
					{
						// a következő is szám - ez a 2.
						$res[$mezo[$pos-3].$mezo[$pos-2]] += 10*$mezo[$pos-1]+$mezo[$pos];
						$pos++;
						continue;
					}
				}
			}
			else
			{
				// kisbetű volt
				$pos++;
				if (@strpos($szam, $mezo[$pos])===false) // nem szám
				{
					$res[$mezo[$pos-2].$mezo[$pos-1]] += 1;
					continue;
				}
				else
				{
					// szám volt
					$pos++;	// következő
					if (@strpos($szam, $mezo[$pos])===false) // nem szám
					{
						$res[$mezo[$pos-3].$mezo[$pos-2]] += $mezo[$pos-1];
						continue;
					}
					else
					{
						// a következő is szám - ez a 2.
						$res[$mezo[$pos-3].$mezo[$pos-2]] += 10*$mezo[$pos-1]+$mezo[$pos];
						$pos++;
						continue;
					}
				}
			}
		}
		else
		{
			// nyitó zárójel -> keresem a párját
			$nyp = @strpos($nyz, $mezo[$pos]);
			$zaro = @strpos($mezo, $zz[$nyp], $pos);
			//echo "Nyp: $nyp ; Zaro: $zaro";
			if ($zaro===false) $res[] = 'HIBA!';	// kell hogy legyen záró
			$temp = osszkeplet(substr($mezo,$pos+1,$zaro-$pos-1));
			$pos = $zaro+1;
			
			// számot keresek
			if (@strpos($szam, $mezo[$pos])===false) // nem szám
			{
				foreach($temp as $e => $n)
					$res[$e] += $n;
				continue;
			}
			else
			{
				// szám volt
				$pos++;	// következő
				if (@strpos($szam, $mezo[$pos])===false) // nem szám
				{
					foreach($temp as $e => $n)
						$res[$e] += $n*$mezo[$pos-1];
					continue;
				}
				else
				{
					// a következő is szám - ez a 2.
					foreach($temp as $e => $n)
						$res[$e] += $n*(10*$mezo[$pos-1]+$mezo[$pos]);
					$pos++;
					continue;
				}
			}

		}
	}
	
	return $res;
}

?>
3

az én változatom

jeti · 2007. Ápr. 3. (K), 19.58
Mivel sokáig nem kaptam választ, ezért végül sikerült nekem is megoldani a problémát, habár nem olyan komplex módon, mint neked. Már akartam kérni a téma törlését, de köszönöm szépen a megoldásodat, hasznos. Az enyém rövidebb, de sajnos csak egy zárójelre van felkészítve. Jelenleg ennyi elég, de majd később várhatóan úgyis szükségem lesz rá. Ekkor még annyival is kibővítem, hogy a töltéseket figyelmen kívül hagyja.
Ha már egy vegyésszel beszélek... , akkor ilyen alapon lehetne egy egyenletrendező programot is írni, így nem kell majd bajlódni az oxidációs számváltozásokkal... ;-)

Mit jelent a rekurzív módszer? Mitől lesz egy kiértékelés rekurzív?

Tehát a forráskódom:

print("<BR>Átalakítás előtt: ".$keplet);
$keplet=osszkeplet($keplet);
print("<BR>Összegképlet: ".$keplet[0]."<BR>");
print_r($keplet);
function osszkeplet($mezo)
{
$e[0]=""; $sz=1; $a=""; $db=""; $zjv="";
for($i=0; $i<=strlen($mezo)-1; $i++) {
  if ($mezo{$i}=="(") {
  $zjv=strpos($mezo,")");	
  if ($zjv==false) {print("Hiba! A képletből hiányzik a zárójel."); return;}
  $sz="";
  for($ii=$zjv+1; $ii<=strlen($mezo)-1; $ii++) {
  if (ereg("[0-9]",$mezo{$ii},$t)) $sz.=$mezo{$ii}; else break;
  }
  if ($sz=="") $sz=1;
  }
  if ($mezo{$i}==")")  {$sz=1; continue;}
  if ((ereg("[a-z]",$mezo{$i},$t)) or (ereg("[0-9]",$mezo{$i},$t))) continue;
  if (ereg("[A-Z]",$mezo{$i},$t)) 
  {
  $a=$mezo{$i}; $db="";
  if (ereg("[a-z]",$mezo{$i+1},$t)) $a.=$mezo{$i+1};
  for($ii=$i+1; $ii<=strlen($mezo)-1; $ii++) {
  if (ereg("[0-9]",$mezo{$ii},$t)) $db.=$mezo{$ii}; else break;
  }
  if ($db=="") $db=1;
  $e[$a]+=$db*$sz;
  }
}
foreach($e as $kulcs => $ertek)
{if ($kulcs!="0") {
if ($ertek=="1") $ertek="";
$e[0].=$kulcs.$ertek;
}}
return $e;
}
4

Veremautomata

janoszen · 2007. Ápr. 3. (K), 20.24
Tyű látom, itt rengeteg megoldás született, de akkor én is közzéteszem az én ötletemet.

A zárójelezés egy eleve bonyolult dolog, ezért reguláris kifejezésekkel nem biztos, hogy sikerre jut az ember, habár lehet. Az én ötletem a veremautomaták és a reguláris kifejezések használata lenne. Algoritmus következő:

Van egy függvény, ami elkezdi olvasni a karaktereket, amolyan sorjában. Ha nyitó zárójelet talál, akkor rekurzívan meghívja önmagát átadva neki a jelenlegi olvasási pozíticót. Ha záró zárójelet talál, akkor kilép és visszaadja az eddigi eredmény stacket.

Miután lehet olyan is, hogy a zárójel értékét kell valahányszor venni, azt le kell ellenőrizni, hogy van-e szám utána. Ha igen, akkor azt megszorzod. Ami részek pedig a zárójelek között kvázi kijönnek, azokat reguláris kifejezéssel értelmezed.

Ez az algoritmus persze csak akkor jó, ha helyes a képlet, de elég könnyen meg lehet oldani, hogy mást is ellenőrizzen.