Skip to content

Instantly share code, notes, and snippets.

@karhunenloeve
Created August 16, 2023 11:25
Show Gist options
  • Save karhunenloeve/c64d8ee70d8da6ef6dd82b68f51158bd to your computer and use it in GitHub Desktop.
Save karhunenloeve/c64d8ee70d8da6ef6dd82b68f51158bd to your computer and use it in GitHub Desktop.
Porter stemming algorithm.
<?php
/*
Stemma-Porter-Algorithmus
Luciano Melodia
Universität Regensburg
Fakultät für Information, Medien, Sprache und Kultur.
Fach: Medieninformatik
Sentiment Analysis Projekt - Sean
Alle Rechte vorbehalten
Nach dem Vorbild von Richard Heyes (http://www.phpguru.org/) und seinem Porter-Algorithmus für die englische Sprache.
Nach dem Algorithmus von M. F. Porter für die deutsche Sprache: http://snowball.tartarus.org/algorithms/german/stemmer.html
*/
mb_internal_encoding("UTF-8");
class Porter {
protected static $konsonanten = array('q','w','r','t','z','p','s','d','f','g','h','j','k','l','x','c','v','b','n','m','ß'); // Abgleich mit Konsonanten.
protected static $vokale = array('a','e','i','o','u','y','ä','ö','ü'); // Abgleich mit Vokalen.
protected static $s_ending = array('bs', 'ds', 'fs', 'gs', 'hs', 'ks', 'ls', 'ms', 'ns', 'rs', 'ts', 'ft'); // Valide Endungen mit S der Buchsstaben: b, d, f, g, h, k, l, m, n, r, t.
protected static $st_ending = array('bst', 'dst', 'fst', 'gst', 'hst', 'kst', 'lst', 'mst', 'nst', 'tst'); // Valide Endungen mit st der Buchstaben b, d, f, g, h, k, l, m, n, t.
protected static $ausnahmen = array(); // Definition von Kurzwortausnahmen zur Verbesserung des Algorithmus.
public static $R1 = ""; // Deklarieren um Variable später funktionsübergreifend verwenden zu können.
public static $R2 = "";
public static function Stem($word){
if (strlen($word) <= 3 || in_array($word, self::$ausnahmen)) {
$word = mb_strtolower($word, 'UTF-8'); // alle Buchstaben bekommen wieder die Kleinschreibung, auch die Vokale.
return $word;
}
self::$R1 = self::R1($word); // Ersetzen des Leerstring mit jeweiligem Wert.
self::$R2 = self::R2(self::$R1); // Ersetzen von Leerstring R2 mit jeweiligem Wert.
$word = self::Schritt1($word); // Beginn des Funktionsaufrufs.
$word = self::Schritt2($word); // Beginn des Funktionsaufrufs.
$word = self::Schritt3($word); // Beginn des Funktionsaufrufs.
$word = self::Schritt4($word); // Beginn des Funktionsaufrufs.
$word = self::Schritt5($word); // Beginn des Funktionsaufrufs.
return $word;
}
public function utf8json($array) {
static $depth = 0;
$newArray = array();
$depth ++;
if($depth >= '30') {return false;}
foreach($array as $key=>$val) {
if ($key!='success') {
if(is_array($val)) {
$newArray[$key] = $this->utf8json($val);
}
else {$newArray[$key] = is_string($val) ? utf8_encode($val) : $val;}
}
else{$newArray[$key] = $val;}
}
return $newArray;
}
public static function R1($word){ // R1 ist die Ausgabe des Wortteils, der (abgesehen vom ersten Buchstaben), dort beginnt, wo ein Vokal auf einen Konsonanten folgt.
$result_vokale = count(self::$vokale); // Anzahl der Vokale als Laufzeitende wird gezählt.
$result_konsonanten = count(self::$konsonanten); // Anzahl der Konsonanten wird als Laufzeitende gezählt.
$letter = mb_substr($word, 0, 0); // Trennt den ersten Buchstaben um einen fehlerfreien Algorithmus zu liefern.
$word = mb_substr($word, 1, mb_strlen($word)); // Baut aus dem Rest den neuen String auf. (Fehlervermeidung nicht nötig, da if(strlen($word) <= 2) bereits realisiert.
for($c = 0; $c <= mb_strlen($word); $c++){ // Laufzeitvariable um wirklich die erste Konsonant + Vokal Folge zu finden.
$check_word = mb_substr($word, 0, $c);
for($j = 0; $j < $result_vokale; $j++){ // Durchforsten des Arrays für Vokale.
for($i = 0; $i < $result_konsonanten; $i++){ // Durchforsten des Arrays für Konsonanten.
$kombination = self::$konsonanten[$i].self::$vokale[$j]; // Jede erdenkliche Kombination ausprobieren.
$R1 = strpbrk($check_word, $kombination); // Wortabschnitt ab dem Kombinationszeichen. Die ersten beiden Buchstaben werden zudem gelöscht, da die Kombination von Konsonant und Vokal nicht enthalten sein soll.
if($R1 == false){ // Falls Wortabschnitt nicht vorkommt wird die Suche fortgesetzt.
if(mb_strlen($check_word) == mb_strlen($word)){ // Abfangfunktion, falls Kombination in R2 nicht enthalten.
$R1 = "";
return $R1;
}
continue;
}
else{
return mb_substr($R1, 0, mb_strlen($R1)).mb_substr($word, $c, mb_strlen($word)); // Ausgabe von R1.
}
}
}
}
}
public static function R2($R1){ // R1 ist die Ausgabe des Wortteils, der (abgesehen vom ersten Buchstaben), dort beginnt, wo ein Vokal auf einen Konsonanten folgt.
$result_vokale = count(self::$vokale); // Anzahl der Vokale als Laufzeitende wird gezählt.
$result_konsonanten = count(self::$konsonanten); // Anzahl der Konsonanten wird als Laufzeitende gezählt.
for($c = 2; $c <= mb_strlen(self::$R1); $c++){ // Laufzeitvariable um wirklich die erste Konsonant + Vokal Folge zu finden.
$check_R1 = mb_substr(self::$R1, 1, $c);
for($j = 0; $j < $result_vokale; $j++){ // Durchforsten des Arrays für Vokale.
for($i = 0; $i < $result_konsonanten; $i++){ // Durchforsten des Arrays für Konsonanten.
$kombination = self::$konsonanten[$i].self::$vokale[$j]; // Jede erdenkliche Kombination ausprobieren.
if(mb_strpos($check_R1, $kombination) === false){ // Falls Wortabschnitt nicht vorkommt wird die Suche fortgesetzt.
if(mb_strlen($check_R1) == mb_strlen(self::$R1)){ // Abfangfunktion, falls Kombination in R2 nicht enthalten.
$R2 = "";
return $R2;
}
continue; // Es wird weitergesucht, falls keine Kombination vorhanden.
}
else{
$R2 = mb_substr($check_R1, $c-1, mb_strlen($check_R1)).mb_substr(self::$R1, $c+1, mb_strlen(self::$R1)); // R2 wird gebildet, allerdings -1 wegen Stringindexierung.
return $R2; // Ausgabe von R2.
}
}
}
}
}
public static function Schritt1($word){
$new_word = str_replace("ß", "ss", $word); // Scharf-"s" wird durch Doppel-"s" ersetzt.
$new_word = mb_strtolower($new_word, 'UTF-8'); // Kleinschreibung aller Zeichen und Kodierung in UTF-8.
$str_length = mb_strlen($new_word); // Multibyte Funktion für die Stringlänge (Formatierungsmuster UTF-8).
for($j = 0; $j < $str_length; $j++){ // Schleife für die Wortteile.
for($i = 0; $i <= 8; $i++){ // Schleife für die Vokalanzahl.
if(self::$vokale[$i] == mb_substr($new_word, $j, 1)){ // Vergleich von Array-Wert der Vokale mit String Wert (Multibytefunktion erforderlich).
if(in_array(mb_substr($new_word, $j-1, 1), self::$vokale) && in_array(mb_substr($new_word, $j+1, 1), self::$vokale)){ // Abgleich von vorausgehenden und nachfolgenden Vokalen.
$new_word = mb_substr($new_word, 0, $j).mb_strtoupper(mb_substr($new_word, $j, 1), 'UTF-8').mb_substr($new_word, $j+1, $str_length); // Zusammensetzung des neuen Wortes und ToUpper Funktion für den mittleren Vokal.
}
}
}
}
self::$R1 = Porter::R1($word); // Aktualisierung von R1 und R2.
self::$R2 = Porter::R2(self::$R1);
return $new_word; // Ausgabe des neuen Wortes.
}
public static function Schritt2($word){
$Endung_1 = mb_substr(self::$R1, mb_strlen(self::$R1)-1, mb_strlen(self::$R1));
$Endung_2 = mb_substr(self::$R1, mb_strlen(self::$R1)-2, mb_strlen(self::$R1));
$Endung_3 = mb_substr(self::$R1, mb_strlen(self::$R1)-3, mb_strlen(self::$R1));
if($Endung_3 == "ies"){
return $word;
}
if("ern" != $Endung_3){ // falls die Endung mit "ern" übereinstimmt, werden in der else-Anweisung drei Buchstaben gelöscht.
if($Endung_1 == "e"){ // Falls eine einfache Endung "e" vorliegt, wird diese entfernt.
$word = substr($word, 0, -1);
if(strpos($word, "niss") !== false){ // Bei "e" und der zeitgleichen vorhergehenden Endung "niss", wird das letzte "s" gestrichen. Da "niss" im Wortinneren so gut wie nie vorkommt, kann man die Suchanfrage auf den kompletten String beziehen.
$word = substr($word, 0, -1);
}
}
else{
switch ($Endung_2){ // Fälle "em", "er", "en", "es", werden durchgegangen.
case "em":
$word = substr($word, 0, -2);
break;
case "er":
$word = substr($word, 0, -2);
break;
case "en":
$word = substr($word, 0, -2);
if(strpos($word, "niss") !== false){ // Bei "e" und der zeitgleichen vorhergehenden Endung "niss", wird das letzte "s" gestrichen. Da "niss" im Wortinneren so gut wie nie vorkommt, kann man die Suchanfrage auf den kompletten String beziehen.
$word = substr($word, 0, -1);
}
break;
case "es":
$word = substr($word, 0, -2);
if(strpos($word, "niss") !== false){ // Bei "e" und der zeitgleichen vorhergehenden Endung "niss", wird das letzte "s" gestrichen. Da "niss" im Wortinneren so gut wie nie vorkommt, kann man die Suchanfrage auf den kompletten String beziehen.
$word = substr($word, 0, -1);
}
return;
}
if(in_array($Endung_2, self::$s_ending)){
$word = substr($word, 0, -1);
}
}
}
else{
$word = substr($word, 0, -3); // Löschung dreier Buchstaben.
}
self::$R1 = Porter::R1($word); // Aktualisierung von R1 und R2.
self::$R2 = Porter::R2(self::$R1);
return $word;
}
public static function Schritt3($word){
$Endung_2 = mb_substr(self::$R1, mb_strlen(self::$R1)-2, mb_strlen(self::$R1));
$Endung_3 = mb_substr(self::$R1, mb_strlen(self::$R1)-3, mb_strlen(self::$R1));
if(strpos($Endung_3, "est") === false){ // Prüfung der drei Endungen "est", "er", "en".
if(strpos($Endung_2, "er") === false){
if(strpos($Endung_2, "en") === false){
if(in_array($Endung_3, self::$st_ending)){
if(strlen($word)>3){
$word = substr($word, 0, mb_strlen($word)-1); // Wort wird neu zusammen gesetzt und die letzten zwei Zeichen werden entfernt.
}
return $word; // gibt das Wort zurück.
}
else{
self::$R1 = Porter::R1($word); // Aktualisierung von R1 und R2.
self::$R2 = Porter::R2(self::$R1);
return $word;
}
}
else{
if(strlen($word)>3){
$word = substr($word, 0, -2);
self::$R1 = Porter::R1($word); // Aktualisierung von R1 und R2.
self::$R2 = Porter::R2(self::$R1);
}
return $word;
}
}
else{
self::$R1 = Porter::R1($word); // Aktualisierung von R1 und R2.
self::$R2 = Porter::R2(self::$R1);
return $word;
}
}
else{
$word = substr($word, 0, -3);
self::$R1 = Porter::R1($word); // Aktualisierung von R1 und R2.
self::$R2 = Porter::R2(self::$R1);
return $word;
}
}
public static function Schritt4($word){
if(strpos(self::$R2, "keit") !== false){
$word = strstr($word, "keit", true); // Endung "end" wird abgeschnitten.
return $word; // Abbruch der weiteren Suche und Ausgabe des Wortes;
}
if(strpos(self::$R2, "eend") === false || strpos(self::$R2, "eung") === false){ // falls die beiden Endungen nicht enthalten sind, wird weitergesucht.
if(strpos(self::$R2, "end") !== false){ // falls "end"- Vorhanden ist, wird die Sequenz eingeleitet.
$word = strstr($word, "end", true); // Endung "end" wird abgeschnitten.
return $word; // Abbruch der weiteren Suche und Ausgabe des Wortes;
}
if(strpos(self::$R2, "ung") !== false){ // falls "ung"- Vorhanden ist, wird die Sequenz eingeleitet.
$word = strstr($word, "ung", true); // Endung "ung" wird abgeschnitten.
return $word; // Abbruch der weiteren Suche und Ausgabe des Wortes;
}
}
if(strpos(self::$R2, "eig") === false || strpos(self::$R2, "eik") === false || strpos(self::$R2, "eisch") === false){ // falls die drei Endungen nicht enthalten sind, wird weitergesucht.
if(strpos(self::$R2, "ig") !== false){ // falls "ig"- Vorhanden ist, wird die Sequenz eingeleitet.
$word = strstr($word, "ig", true); // Endung "ig" wird abgeschnitten.
return $word; // Abbruch der weiteren Suche und Ausgabe des Wortes;
}
if(strpos(self::$R2, "ik") !== false){ // falls "ik"- Vorhanden ist, wird die Sequenz eingeleitet.
$word = strstr($word, "ik", true); // Endung "ik" wird abgeschnitten.
return $word; // Abbruch der weiteren Suche und Ausgabe des Wortes;
}
if(strpos(self::$R2, "isch") !== false){ // falls "ik"- Vorhanden ist, wird die Sequenz eingeleitet.
$word = strstr($word, "isch", true); // Endung "ik" wird abgeschnitten.
return $word; // Abbruch der weiteren Suche und Ausgabe des Wortes;
}
}
if(strpos(self::$R1, "erlich") !== false){
$word = strstr($word, "lich", true); // Endung "lich" wird abgeschnitten.
return $word; // Abbruch der weiteren Suche und Ausgabe des Wortes;
}
if(strpos(self::$R1, "enlich") !== false){
$word = strstr($word, "lich", true); // Endung "lich" wird abgeschnitten.
return $word; // Abbruch der weiteren Suche und Ausgabe des Wortes;
}
if(strpos(self::$R1, "erheit") !== false){
$word = strstr($word, "heit", true); // Endung "heit" wird abgeschnitten.
return $word; // Abbruch der weiteren Suche und Ausgabe des Wortes;
}
if(strpos(self::$R1, "enheit") !== false){
$word = strstr($word, "heit", true); // Endung "heit" wird abgeschnitten.
return $word; // Abbruch der weiteren Suche und Ausgabe des Wortes;
}
if(strpos(self::$R2, "heit") !== false){
$word = strstr($word, "heit", true); // Endung "heit" wird abgeschnitten.
return $word; // Abbruch der weiteren Suche und Ausgabe des Wortes;
}
if(strpos(self::$R2, "lich") !== false){
$word = strstr($word, "lich", true); // Endung "lich" wird abgeschnitten.
return $word; // Abbruch der weiteren Suche und Ausgabe des Wortes;
}
return $word;
}
public static function Schritt5($word){
$word = mb_strtolower($word, 'UTF-8'); // alle Buchstaben bekommen wieder die Kleinschreibung, auch die Vokale.
$letter = mb_substr($word, 0, 1); // Trennt den ersten Buchstaben um einen fehlerfreien Algorithmus zu liefern.
$word = mb_substr($word, 1, mb_strlen($word)); // Baut aus dem Rest den neuen String auf. (Fehlervermeidung nicht nötig, da if(strlen($word) <= 2) bereits realisiert.
$word = str_replace("ü", "u", $word); // Umlaut "ü" ...
$word = str_replace("ä", "a", $word); // Umlaut "ä" ...
$word = str_replace("ö", "o", $word); // Umlaut "ö" werden ersetzt.
$word = $letter.$word; // Wort wird wieder zusammen gesetzt.
return $word;
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment