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
  1. <FORM METHOD="POST" ACTION="">  
  2. <INPUT TYPE="text" NAME="keplet">  
  3. <INPUT TYPE="submit">  
  4. </FORM>  
  5. <?php  
  6. print("<BR>Átalakítás előtt: ".$keplet);  
  7. print("<BR>Összegképlet: ".osszkeplet($keplet));  
  8.   
  9. function osszkeplet($mezo)  
  10. {  
  11. $as=-1;  
  12. $an="";   
  13. $db="";  
  14. //$zj="";  
  15. for($i=0; $i<=strlen($mezo); $i++)  
  16. {  
  17. print("<BR>*".$mezo{$i}."*<BR>");  
  18. if ($mezo{$i}=="")   
  19.  {  
  20.  if ($db[$as]=="") {$db[$as]=1;}  
  21. continue;  
  22.  }  
  23. if ($mezo{$i}=="(")   
  24.  {  
  25.  if ($db[$as]=="") {$db[$as]=1;}  
  26. // $zj ???  
  27. continue;  
  28.  }  
  29. if (ereg("[A-Z]",$mezo{$i},$t))   
  30. {  
  31. if (($as!=-1) and ($db[$as]=="")) {$db[$as]=1;}  
  32. $as++; $an[$as]=$mezo{$i};  
  33.  }  
  34. if ((ereg("[a-z]",$mezo{$i},$t)) and (ereg("[A-Z]",$an[$as],$t))) {$an[$as].=$mezo{$i};}  
  35. if (ereg("[0-9]",$mezo{$i},$t))   
  36. {  
  37. if (! isset($db[$as])) {$db[$as]=$mezo{$i};} else {$db[$as].=$mezo{$i};}  
  38.  }  
  39. }  
  40. print("<BR><HR><BR>");  
  41. $keplet="";  
  42. for($i=0; $i<=$as$i++)  
  43. {  
  44. if ($an[$i]!="")  
  45. {  
  46. for($ii=0; $ii<=$as$ii++)  
  47. {  
  48. if (($an[$i]==$an[$ii]) and ($i!=$ii)) {$db[$i]+=$db[$ii]; unset($an[$ii]); unset($db[$ii]);}  
  49. }  
  50. print("<BR>".$an[$i].": ".$db[$i]."<BR>");  
  51. if ($db[$i]=="1"$db[$i]="";  
  52. $okeplet.=$an[$i].$db[$i];  
  53. }  
  54. }  
  55. return $okeplet;  
  56. }  
  57. ?>  
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.
  1. <FORM METHOD="POST" ACTION="">  
  2. <INPUT TYPE="text" NAME="keplet">  
  3. <INPUT TYPE="submit">  
  4. </FORM>  
  5. <?php  
  6. $keplet = $_POST['keplet'];  
  7. print("<BR>Átalakítás előtt: ".$keplet);  
  8. print("<BR>Összegképlet: "); print_r(osszkeplet($keplet));  
  9.   
  10. function osszkeplet($mezo)  
  11. {  
  12.     $nyz = '([{<';  
  13.     $zz  = ')]}>';  
  14.     $nagybetu = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';  
  15.     $kisbetu = 'abcdefghijklmnopqrstuvwxyz';  
  16.     $szam = '0123456789';  
  17.     $pos = 0;  
  18.     $res = array();  
  19.     $length = strlen($mezo);  
  20.       
  21.     while($pos<$length)  
  22.     {  
  23.         if (strpos($nyz$mezo[$pos])===false)  
  24.         {  
  25.             // nem nyitó zárójel  
  26.             if (strpos($nagybetu$mezo[$pos])===false) $res[] = 'HIBA'// csak nagybetű lehet  
  27.             $pos++; // következő  
  28.             if (@strpos($kisbetu$mezo{$pos})===false) // nem kisbetű  
  29.             {  
  30.                 if (@strpos($szam$mezo[$pos])===false) // nem szám  
  31.                 {  
  32.                     $res[$mezo[$pos-1]] += 1;  
  33.                     continue;  
  34.                 }  
  35.                 else  
  36.                 {  
  37.                     // szám volt  
  38.                     $pos++; // következő  
  39.                     if (@strpos($szam$mezo[$pos])===false) // nem szám  
  40.                     {  
  41.                         $res[$mezo[$pos-2]] += $mezo[$pos-1];  
  42.                         continue;  
  43.                     }  
  44.                     else  
  45.                     {  
  46.                         // a következő is szám - ez a 2.  
  47.                         $res[$mezo[$pos-3].$mezo[$pos-2]] += 10*$mezo[$pos-1]+$mezo[$pos];  
  48.                         $pos++;  
  49.                         continue;  
  50.                     }  
  51.                 }  
  52.             }  
  53.             else  
  54.             {  
  55.                 // kisbetű volt  
  56.                 $pos++;  
  57.                 if (@strpos($szam$mezo[$pos])===false) // nem szám  
  58.                 {  
  59.                     $res[$mezo[$pos-2].$mezo[$pos-1]] += 1;  
  60.                     continue;  
  61.                 }  
  62.                 else  
  63.                 {  
  64.                     // szám volt  
  65.                     $pos++; // következő  
  66.                     if (@strpos($szam$mezo[$pos])===false) // nem szám  
  67.                     {  
  68.                         $res[$mezo[$pos-3].$mezo[$pos-2]] += $mezo[$pos-1];  
  69.                         continue;  
  70.                     }  
  71.                     else  
  72.                     {  
  73.                         // a következő is szám - ez a 2.  
  74.                         $res[$mezo[$pos-3].$mezo[$pos-2]] += 10*$mezo[$pos-1]+$mezo[$pos];  
  75.                         $pos++;  
  76.                         continue;  
  77.                     }  
  78.                 }  
  79.             }  
  80.         }  
  81.         else  
  82.         {  
  83.             // nyitó zárójel -> keresem a párját  
  84.             $nyp = @strpos($nyz$mezo[$pos]);  
  85.             $zaro = @strpos($mezo$zz[$nyp], $pos);  
  86.             //echo "Nyp: $nyp ; Zaro: $zaro";  
  87.             if ($zaro===false) $res[] = 'HIBA!';    // kell hogy legyen záró  
  88.             $temp = osszkeplet(substr($mezo,$pos+1,$zaro-$pos-1));  
  89.             $pos = $zaro+1;  
  90.               
  91.             // számot keresek  
  92.             if (@strpos($szam$mezo[$pos])===false) // nem szám  
  93.             {  
  94.                 foreach($temp as $e => $n)  
  95.                     $res[$e] += $n;  
  96.                 continue;  
  97.             }  
  98.             else  
  99.             {  
  100.                 // szám volt  
  101.                 $pos++; // következő  
  102.                 if (@strpos($szam$mezo[$pos])===false) // nem szám  
  103.                 {  
  104.                     foreach($temp as $e => $n)  
  105.                         $res[$e] += $n*$mezo[$pos-1];  
  106.                     continue;  
  107.                 }  
  108.                 else  
  109.                 {  
  110.                     // a következő is szám - ez a 2.  
  111.                     foreach($temp as $e => $n)  
  112.                         $res[$e] += $n*(10*$mezo[$pos-1]+$mezo[$pos]);  
  113.                     $pos++;  
  114.                     continue;  
  115.                 }  
  116.             }  
  117.   
  118.         }  
  119.     }  
  120.       
  121.     return $res;  
  122. }  
  123.   
  124. ?>  
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:
  1. print("<BR>Átalakítás előtt: ".$keplet);  
  2. $keplet=osszkeplet($keplet);  
  3. print("<BR>Összegképlet: ".$keplet[0]."<BR>");  
  4. print_r($keplet);  
  5. function osszkeplet($mezo)  
  6. {  
  7. $e[0]=""$sz=1; $a=""$db=""$zjv="";  
  8. for($i=0; $i<=strlen($mezo)-1; $i++) {  
  9.   if ($mezo{$i}=="(") {  
  10.   $zjv=strpos($mezo,")");     
  11.   if ($zjv==false) {print("Hiba! A képletből hiányzik a zárójel."); return;}  
  12.   $sz="";  
  13.   for($ii=$zjv+1; $ii<=strlen($mezo)-1; $ii++) {  
  14.   if (ereg("[0-9]",$mezo{$ii},$t)) $sz.=$mezo{$ii}; else break;  
  15.   }  
  16.   if ($sz==""$sz=1;  
  17.   }  
  18.   if ($mezo{$i}==")")  {$sz=1; continue;}  
  19.   if ((ereg("[a-z]",$mezo{$i},$t)) or (ereg("[0-9]",$mezo{$i},$t))) continue;  
  20.   if (ereg("[A-Z]",$mezo{$i},$t))   
  21.   {  
  22.   $a=$mezo{$i}; $db="";  
  23.   if (ereg("[a-z]",$mezo{$i+1},$t)) $a.=$mezo{$i+1};  
  24.   for($ii=$i+1; $ii<=strlen($mezo)-1; $ii++) {  
  25.   if (ereg("[0-9]",$mezo{$ii},$t)) $db.=$mezo{$ii}; else break;  
  26.   }  
  27.   if ($db==""$db=1;  
  28.   $e[$a]+=$db*$sz;  
  29.   }  
  30. }  
  31. foreach($e as $kulcs => $ertek)  
  32. {if ($kulcs!="0") {  
  33. if ($ertek=="1"$ertek="";  
  34. $e[0].=$kulcs.$ertek;  
  35. }}  
  36. return $e;  
  37. }  
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.