• Das Erstellen neuer Accounts wurde ausgesetzt. Bei berechtigtem Interesse bitte Kontaktaufnahme über die üblichen Wege. Beste Grüße der Admin

[PHP] rekursive funktion braucht zu viel speicher

Z

zirzofer

Guest
mittels php-funktion wird ein Weg durch das Labyrinth von Beginn B nach Ende E gefunden. der richtige weg wird in einem array gespeichert.
die funktion läuft, braucht jedoch selbst bei einem so leichten fall wie hier so viel speicher, dass sie trotz 1024MB limit out of memory läuft. wie kann ich das verhindern und vor allem die funktion fit fuer groessere labyrinthe machen?


PHP:
<?php
function pfad_suchen($x_koord, $y_koord, $bewegungsrichtung = null){
	global $raum, $x_koord_max, $y_koord_max, $weg;
	if(!isset($raum[$x_koord][$y_koord])){ //Koordinaten außerhalb
		return false;
	}
	if($raum[$x_koord][$y_koord] === 'E'){ //Ausgang
		return true;
	}
	if(($raum[$x_koord][$y_koord] === '_')){ //Mauer
		return false;
	}

	if($bewegungsrichtung !== null){
		$weg[] = $bewegungsrichtung; //letzte Bewegung dem Weg zum Ausgang hinzufügen
	}

	if(pfad_suchen($x_koord, $y_koord+1, 'rechts')){
		return true;
	}
	if(pfad_suchen($x_koord+1, $y_koord, 'runter')){
		return true;
	}
	if(pfad_suchen($x_koord-1, $y_koord, 'hoch')){
		return true;
	}
	if(pfad_suchen($x_koord, $y_koord-1, 'links')){
		return true;
	}

	if(count($weg) >= 1){
		array_pop($weg);
	}

	return false;
}


$f = fopen('labyrinth.txt', 'r' );
$x_koord_max = 8;
$y_koord_max = 9;

$raum = [];
$start = [false, false];
for($i = 0; $i<=$x_koord_max; $i++){
	$zeile = trim(fgets($f));
	if($start[1] === false){
		$start = [$i, strpos($zeile, 'B')];
	}
	array_push($raum, $zeile);
}


$weg = [];
pfad_suchen($start[0], $start[1]);
echo "<pre>";
var_dump($weg);
echo "</pre>";
die();
?>

labyrinth.txt:
Code:
_________
_________
__+____B_
_______+_
___+___+_
__++___+_
___+++++_
___E_____
 
Zuletzt bearbeitet:
Ähm ja, entschuldige. da habe ich beim anpassen der funktion fürs forum einen fehler gemacht. is jetzt getestet und korrigiert (problem bleibt weiterhin identisch).
 
über 1 gb solltest du hier eigentlich nicht kommen.
eigentlich hast du ja einen graphen und suchst den kürzesten pfad zw. 2 knoten.
entweder bildest du das als matrix (was schnell groß werden kann) ab oder als baumstruktur.
dann kannst du erprobte algorithmen verwenden.
 
entweder bildest du das als matrix (was schnell groß werden kann) ab oder als baumstruktur.
das ganze läuft ja derzeit als matrix
PHP:
for($i = 0; $i<=$x_koord_max; $i++){
    $zeile = trim(fgets($f));
    if($start[1] === false){
        $start = [$i, strpos($zeile, 'B')];
    }
    array_push($raum, $zeile);
}
soll ich das system also auf eine baumstruktur umstellen?
falls ja: wie mache ich das?
 
das ganze läuft ja derzeit als matrix
als xy-matrix, die meisten graphen-algorithmen laufen aber entweder über eine knoten x knoten matrix was aber viel platz benötogt. bei dir, mit x*y knoten hättest du eine x*y * x*y matrix
https://de.wikipedia.org/wiki/Adjazenzmatrix

soll ich das system also auf eine baumstruktur umstellen?
alternativ baust du eine baumstruktur, wo du am knoten vermerkst, welche verbindungen du hast.
du must also nur speicher für tatsächlich vorhandene verbindungen haben.
https://de.wikipedia.org/wiki/Adjazenzliste

z.b. über den https://de.wikipedia.org/wiki/Dijkstra-Algorithmus kannst du dann den kürzesten weg von b zu e berechnen

- - - Aktualisiert - - -

gerade noch gefunden:
http://www.inf.fh-flensburg.de/lang/algorithmen/graph/breadth-first-tree.htm

- - - Aktualisiert - - -

wobei deine xy-matrix ja schon eine baumstruktur ist, nur mit zuviel informationen
 
wenn du ein array erstellst, mit objekten welche ein array enthalten mit den idx der knoten zu denen sie verschalten sind
[
{x: ???, y: ???, links: [idx1, idx2, ...]}
]
hättest du schon mal eine komfortable datenbasis mit wenig speicherplatz. knoten die nicht verschalten sind (-) bzw. knoten die keine verbindung haben (ein einzelnes + ohne nachbar - z.b. 3,3 bei dir) dürftest du ignorieren können (letzteres nur, wenn start und ende verschieden sein müssen.)
mit https://de.wikipedia.org/wiki/Dijkstra-Algorithmus#Algorithmus_in_Pseudocode liegst du dann schon mal nicht ganz falsch.

du könntest auch auf deiner x,y matrix arbeiten, aber dann musst du für knoten x,y immer knoten x-1;y, x+1;y, x;y-1 und x;y+1 betrachten um kanten zu finden.
 
wenn du ein array erstellst, mit objekten welche ein array enthalten mit den idx der knoten zu denen sie verschalten sind
[
{x: ???, y: ???, links: [idx1, idx2, ...]}
]
hier wäre ich für ein kurzes beispielhaftes skriptstück dankbar. was genau soll so ein "idx" enthalten? wie bekomme ich die werte?
 
hier wäre ich für ein kurzes beispielhaftes skriptstück dankbar. was genau soll so ein "idx" enthalten? wie bekomme ich die werte?

irgendein verweis aud die verbundenen knoten, z.b. der index in das array, oder gleich einen referenz auf den verbundenen knoten
bei genauerer überlegung geht das aber nicht in einem durchgang, du müsstest also 2 mal über deine matrix iterieren. einmal um die knoten zu erstellen, und das 2. mal um die links zu erzeugen. hmm...

- - - Aktualisiert - - -

hier wäre ich für ein kurzes beispielhaftes skriptstück dankbar.

Code:
var g=[]
du gehst über x
  du gehst über y
    wenn auf x,y±1 oder x±1,y kein "-" steht erzeugst du in g einen arrayeintrag mit [x=>x, y0>y, links=>[]]

idx = 0;
du gehst nochmal über x
  du gehst nochmal über y
    wenn auf x,y±1 oder x±1,y kein "-" 
      du suchst die einträge mit x,y±1 und x±1,y welche kein "-" haben in g
      und speicherst sie in g[++idx].links
 
Ich habe mich jetzt ein wenig in die Materie eingelesen, aber ich verstehe es einfach nicht.
habe den pseudocode jetzt mal umgesetzt, dabei allerdings nicht wirklich verstanden, worum es geht, und darum ist auch - offensichtlich - totaler Blödsinn herausgekommen:

PHP:
$f = fopen('labyrinth.txt', 'r' );

$abmessungen = explode(' ', fgets($f));
$x_koord_max = 8;
$y_koord_max = 9; 

$raum = [];
$start = [false, false];
$raum = [];
$start = [false, false];
for($i = 0; $i<=$x_koord_max; $i++){
    $zeile = trim(fgets($f));
    if($start[1] === false){
        $start = [$i, strpos($zeile, 'B')];
    }
    $raum[] = $zeile;
}


$g = [];
for($x = 0; $x<$x_koord_max; $x++){ //"gehe über x"?
	for($y = 0; $y<$y_koord_max; $y++){ //"gehe über y"?
		if($raum[$x][$y+1] !== '_' || $raum[$x][$y-1] !== '_' || $raum[$x+1][$y] !== '_' || $raum[$x-1][$y] !== '_'){
			$g[] = [
				'x' => $x,
				'y0' => $y,
				'links' => []
			];
        }
	}
}

$idx = 0;
$iii_len = count($g);
for($x = 0; $x<$x_koord_max; $x++){ //"gehe über x"?
	for($y = 0; $y<$y_koord_max; $y++){ //"gehe über y"?
		if($raum[$x][$y+1] !== '_' || $raum[$x][$y-1] !== '_' || $raum[$x+1][$y] !== '_' || $raum[$x-1][$y] !== '_'){
			for($iii = 0; $iii<$iii_len; $iii++){ //du suchst die einträge mit x,y±1 und x±1,y welche kein "-" haben in g?
				if($raum[$g[$iii]['x']][$g[$iii]['y0']+1] !== '_'
					|| $raum[$g[$iii]['x']][$g[$iii]['y0']-1] !== '_'
					|| $raum[$g[$iii]['x']+1][$g[$iii]['y0']] !== '_'
					|| $raum[$g[$iii]['x']-1][$g[$iii]['y0']] !== '_'){
					$g[++$idx]['links'] = $raum[$g[$iii]];//schaut völlig falsch aus
				}
			}
        }
	}
}

echo "<pre>";
var_dump($g);
echo "</pre>";
die();

das das skript hunderte Undefined offset und Illegal offset type wirft, ist auch kein wudner (wegen dem -/+ 1), aber ich verstehe ehrlich gesagt nicht, was mittels deinem vorschlag erreicht werden kann und wie der erfolgreich umzusetzen ist, tsseh (ich vertraue absolut darauf, dass der zur lösung führt, aber ich verstehe ihn leider nicht).
 
ich hab jetzt keine lust das in php zu machen, geht ja aber auch nur ums prinzip.
hier hab ich mal den dijkstra algorithmus geklaut, das ist eine um die pfadberechnung erweiterte variante von hier. deswegen erstmal in js. damit sollte das verständniss aber erstmal kommen.
machen wir erst mal den einfacheren fall mit einer matrix:

Code:
<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <script>
      var l = [
        "_________",
        "_________",
        "__+____B_",
        "_______+_",
        "___+___+_",
        "__++___+_",
        "___+++++_",
        "___E_____"
      ];
      
      var L = l.map(function(e, i)
      {
        return e.split("");
      });
      
      var start = -1;
      var end = -1;
      var g = [];
      L.forEach(function(e, y)
      {
        e.forEach(function(e, x)
        {
          g[x * L.length + y] = [];
          if (L[y][x] != "_")
          {
            if (L[y][x] == "B")
            {
              start = x * L.length + y;
            }
            else if (L[y][x] == "E")
            {
              end = x * L.length + y;
            }
            if (x > 0 && L[y][x - 1] != "_")
            {
              g[x * L.length + y][(x - 1) * L.length + y] = 1;
            }
            if (x < (L[y].length - 1) && L[y][x + 1] != "_")
            {
              g[x * L.length + y][(x + 1) * L.length + y] = 1;
            }
            if (y > 0 && L[y - 1][x] != "_")
            {
              g[x * L.length + y][x * L.length + y - 1] = 1;
            }
            if (y < (L.length - 1) && L[y + 1][x] != "_")
            {
              g[x * L.length + y][x * L.length + y + 1] = 1;
            }
          }
        });
      });
      
      if (start >= 0 && end >= 0)
      {
        var d = dijkstra(g);
        d.run(start, end, function()
        {
          var p = d.path(end);
          console.log("daten: ", g);
          console.log("path: ", p.map(function(e)
          {
            return {n: e, y: e % L.length, x: (e - (e % L.length)) / L.length};
          }));
        });
      }
      
      function dijkstra(g)
      {
        var d = {};
        var predecessor = [];
        d.run = function (src, dst, onEnd)
        {
          predecessor = [];
          var distance = [];
          var unvisited = [];
          var current = src;
  
          g.forEach(function (e, n)
          {
            unvisited.push(n);
            distance.push(n == src ? 0 : Infinity);
            predecessor.push(-1);
          });
  
          function tick()
          {
            if (unvisited.length == 0 || distance[current] == Infinity)
            {
              return onEnd();
            }
            
            unvisited.sort(function(a, b)
            {
              return distance[b] - distance[a];
            });

            current = unvisited.pop()
            
            g[current].forEach(function (e, l)
            {
              if (g[current][l])
              {
                if (unvisited.includes(l))
                {
                  var dist = distance[current] + 1; // alle kanten sind mit 1 gewichtet
                  if (dist < distance[l])
                  {
                    distance[l] = dist;
                    predecessor[l] = current;
                  }
                }
              }
            });
            
            if (current == dst)
            {
              return onEnd();
            }
            
            setTimeout(tick);
          }
  
          setTimeout(tick);
        };
        
        d.path = function (dst)
        {
          var path = [dst];
          while ((dst = predecessor[dst]) >= 0)
          {
            path.unshift(dst);
          }
          return path;
        };
        
        return d;
      }
      
    </script>
  </head>
  <body>
  </body>
</html>

der L.forEach - block erzeugt eine matrix mit allen x*y knoten. hier also eine 8*9 x 8*9 matrix.
die 72 zeilen sind deine knoten und in den spalten stehen die verbindungen zu anderen knoten.

das ganze lässt sich dann reduzieren auf

Code:
<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <script>
      var l = [
        "_________",
        "_________",
        "__+____B_",
        "_______+_",
        "___+___+_",
        "__++___+_",
        "___+++++_",
        "___E_____"
      ];
      
      var L = l.map(function(e, i)
      {
        return e.split("");
      });
      
      var start = null;
      var end = null;
      var g = [];
      L.forEach(function(e, y)
      {
        e.forEach(function(e, x)
        {
          if (L[y][x] != "_")
          {
            var l = [];
            if (x > 0 && L[y][x - 1] != "_")
            {
              l.push({x: x - 1, y: y});
            }
            if (x < (L[y].length - 1) && L[y][x + 1] != "_")
            {
               l.push({x: x + 1, y: y});
            }
            if (y > 0 && L[y - 1][x] != "_")
            {
              l.push({x: x, y: y - 1});
            }
            if (y < (L.length - 1) && L[y + 1][x] != "_")
            {
              l.push({x: x, y: y + 1});
            }
            if (l.length)
            {
              g.push({x: x, y: y, i: g.length, l: l});
              if (L[y][x] == "B")
              {
                start = g[g.length - 1];
              }
              else if (L[y][x] == "E")
              {
                end = g[g.length - 1];
              }
            }
          }
        });
      });
      
      if (start && end)
      {
        var d = dijkstra(g);
        d.run(start, end, function()
        {
          var p = d.path(end);
          console.log("daten: ", g);
          console.log("path: ", p);
        });
      }
      
      function dijkstra(g)
      {
        var d = {};
        var predecessor = [];
        d.run = function (src, dst, onEnd)
        {
          predecessor = [];
          var distance = [];
          var unvisited = [];
          var current = src;
  
          g.forEach(function (e, n)
          {
            unvisited.push(e);
            distance[e.i] = e == src ? 0 : Infinity;
            predecessor[e.i] = null;
          });
  
          function tick()
          {
            if (unvisited.length == 0 || distance[current.i] == Infinity)
            {
              return onEnd();
            }
            
            unvisited.sort(function(a, b)
            {
              return distance[b.i] - distance[a.i];
            });

            current = unvisited.pop()
            
            current.l.forEach(function (l)
            {
              if (unvisited.find(function (n)
              {
                if (n.x == l.x && n.y == l.y)
                {
                  l.i = n.i;
                  return true;
                }
                return false;
              }))
              {
                var dist = distance[current.i] + 1; // alle kanten sind mit 1 gewichtet
                if (dist < distance[l.i])
                {
                  distance[l.i] = dist;
                  predecessor[l.i] = current;
                }
              }
            });
            
            if (current == dst)
            {
              return onEnd();
            }
            
            setTimeout(tick);
          }
  
          setTimeout(tick);
        };
        
        d.path = function (dst)
        {
          var path = [dst];
          while ((dst = predecessor[dst.i]))
          {
            path.unshift(dst);
          }
          return path;
        };
        
        return d;
      }
      
    </script>
  </head>
  <body>
  </body>
</html>

wieder erzeugt L.forEach deine knoten und verschaltungen. die links sind im knoten in der eigenschaft links wieder als objekt gespeichert. einfach als x und y position auf der der partnerknoten liegt. der dijkstra algorithmus sucht dann in g die passenden knoten und speichert im link-objekt zusätzlich den index.
besser wäre es, diesen schritt schon vorher zu machen. oder noch besser man speichert kein link-objekt mit x, y und index, sondern nur den index.

für dein kleines 9*8 labyrinth sollten beide varianten von den daten her realisierbar sein, perspektivisch ist der 2. weg besser.
allerdings dürfte ab einer gewissen labyrinthgröße die laufzeit eine rolle spielen. in js ist das hier über setTimeouts gelöst. der dijkstra rechnet also immer nur die verbindungen eines knotens durch und lässt dann den browser wieder rann. das problem musst du auch in php lösen.

- - - Aktualisiert - - -

hier auch der 2. weg ohne suche im dijkstra sondern vorher
Code:
<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <script>
      var l = [
        "_________",
        "_________",
        "__+____B_",
        "_______+_",
        "___+___+_",
        "__++___+_",
        "___+++++_",
        "___E_____"
      ];
      
      var L = l.map(function(e, i)
      {
        return e.split("");
      });
      
      var start = null;
      var end = null;
      var g = [];
      L.forEach(function(e, y)
      {
        e.forEach(function(e, x)
        {
          if (L[y][x] != "_")
          {
            var l = [];
            if (x > 0 && L[y][x - 1] != "_" 
             || x < (L[y].length - 1) && L[y][x + 1] != "_"
             || y > 0 && L[y - 1][x] != "_"
             || y < (L.length - 1) && L[y + 1][x] != "_")
            {
              g.push({x: x, y: y, i: g.length, l: []});
              if (L[y][x] == "B")
              {
                start = g[g.length - 1];
              }
              else if (L[y][x] == "E")
              {
                end = g[g.length - 1];
              }
            }
          }
        });
      });
      var idx = -1;
      L.forEach(function(e, y)
      {
        e.forEach(function(e, x)
        {
          if (L[y][x] != "_")
          {
            var n = null;
            if (x > 0 && L[y][x - 1] != "_")
            {
              n = n ? n : g[++idx];
              console.assert(n.x == x && n.y == y);
              n.l.push(g.find(function(e)
              {
                return e.x == (x - 1) && e.y == y;
              }));
            }
            if (x < (L[y].length - 1) && L[y][x + 1] != "_")
            {
              n = n ? n : g[++idx];
              console.assert(n.x == x && n.y == y);
              n.l.push(g.find(function(e)
              {
                return e.x == (x + 1) && e.y == y;
              }));
            }
            if (y > 0 && L[y - 1][x] != "_")
            {
              n = n ? n : g[++idx];
              console.assert(n.x == x && n.y == y);
              n.l.push(g.find(function(e)
              {
                return e.x == x && e.y == (y - 1);
              }));
            }
            if (y < (L.length - 1) && L[y + 1][x] != "_")
            {
              n = n ? n : g[++idx];
              console.assert(n.x == x && n.y == y);
              n.l.push(g.find(function(e)
              {
                return e.x == x && e.y == (y + 1);
              }));
            }
          }
        });
      });

      if (start && end)
      {
        var d = dijkstra(g);
        d.run(start, end, function()
        {
          var p = d.path(end);
          console.log("daten: ", g);
          console.log("path: ", p);
        });
      }
      
      function dijkstra(g)
      {
        var d = {};
        var predecessor = [];
        d.run = function (src, dst, onEnd)
        {
          predecessor = [];
          var distance = [];
          var unvisited = [];
          var current = src;
  
          g.forEach(function (e, n)
          {
            unvisited.push(e);
            distance[e.i] = e == src ? 0 : Infinity;
            predecessor[e.i] = null;
          });
  
          function tick()
          {
            if (unvisited.length == 0 || distance[current.i] == Infinity)
            {
              return onEnd();
            }
            
            unvisited.sort(function(a, b)
            {
              return distance[b.i] - distance[a.i];
            });

            current = unvisited.pop()
            
            current.l.forEach(function (l)
            {
              if (unvisited.includes(l))
              {
                var dist = distance[current.i] + 1; // alle kanten sind mit 1 gewichtet
                if (dist < distance[l.i])
                {
                  distance[l.i] = dist;
                  predecessor[l.i] = current;
                }
              }
            });
            
            if (current == dst)
            {
              return onEnd();
            }
            
            setTimeout(tick);
          }
  
          setTimeout(tick);
        };
        
        d.path = function (dst)
        {
          var path = [dst];
          while ((dst = predecessor[dst.i]))
          {
            path.unshift(dst);
          }
          return path;
        };
        
        return d;
      }
      
    </script>
  </head>
  <body>
  </body>
</html>

- - - Aktualisiert - - -

Code:
<?php 
  $f = fopen('labyrinth.txt', 'r' );
  
  $raum = array();
  while ($line = fgets($f))
  {
    $l = str_split(trim($line)); 
    $raum[] = $l;
  }
  
  $g = array();
  foreach ($raum as $y => $line)
  {
    foreach ($line as $x => $e)
    {
      if($e != '_' )
      {
        if ($x > 0 && $raum[$y][$x - 1] != "_" 
         || $x < (count($raum[$y]) - 1) && $raum[$y][$x + 1] != "_"
         || $y > 0 && $raum[$y - 1][$x] != "_"
         || $y < (count($raum) - 1) && $raum[$y + 1][$x] != "_")
        {
          $g[] = array('x' => $x, 'y' => $y, 'i' => count($g), 'l' => array());
        }
      }
    }
  }
  $idx = -1;
  foreach ($raum as $y => $line)
  {
    foreach ($line as $x => $e)
    {
      if($e != '_' )
      {
        $i = -1;
        if ($x > 0 && $raum[$y][$x - 1] != "_")
        {
          $i = $i < 0 ? ++$idx : $i;
          addLink($i, getNode($x - 1, $y, $g), $g);
        }
        if ($x < (count($raum[$y]) - 1) && $raum[$y][$x + 1] != "_")
        {
          $i = $i < 0 ? ++$idx : $i;
          addLink($i, getNode($x + 1, $y, $g), $g);
        }
        if ($y > 0 && $raum[$y - 1][$x] != "_")
        {
          $i = $i < 0 ? ++$idx : $i;
          addLink($i, getNode($x, $y - 1, $g), $g);
        }
        if ($y < (count($raum) - 1) && $raum[$y + 1][$x] != "_")
        {
          $i = $i < 0 ? ++$idx : $i;
          addLink($i, getNode($x, $y + 1, $g), $g);
        }
      }
    }
  }
  
  $path = dijkstra($g, 0, 12);
  
  foreach($path as $node)
  {
    echo("(x: " . $g[$node]['x'] . " y: " . $g[$node]['y'] . ") -> ");
  }
  
  function getNode($x, $y, &$g)
  {
    foreach ($g as $idx => $node)
    {
       if ($node['x'] == $x && $node['y'] == $y )
       {
        return $idx;
       }
    }
    return -1;
  }
  function addLink($idx, $link, &$g)
  {
    $g[$idx]['l'][] = $link;
  }
  function dijkstra(&$g, $start, $end)
  {
    $predecessor = array();
    $distance = array();
    $unvisited = array();
    
    $cmp = function ($a, $b) use (&$distance)
    {
      return $distance[$b] - $distance[$a];
    };
    
    foreach ($g as $idx => $node)
    {
      $unvisited[] = $idx;
      $distance[] = $idx == $start ? 0 : PHP_INT_MAX;
      $predecessor[] = -1;
    }
    
    $current = $start;
    
    while (count($unvisited) && $distance[$current] != PHP_INT_MAX)
    {
      usort($unvisited, $cmp);
      $current = array_pop($unvisited);
      
      foreach($g[$current]['l'] as $link)
      {
        if (in_array($link, $unvisited))
        {
          $dist = $distance[$current] + 1; // alle kanten sind mit 1 gewichtet
          if ($dist < $distance[$link])
          {
            $distance[$link] = $dist;
            $predecessor[$link] = $current;
          }
        }
      };
      
      if ($current == $end)
      {
        $path = array($end);
        while (($current = $predecessor[$current]) >= 0)
        {
          array_unshift($path, $current);
        }
        return $path;
      }
    }
    return array();
  }
?>
 
Vielen vielen Dank! ich habe mich jetzt ein wenig in den code eingearbeitet und frage mich vor allem, woher genau an der stelle $path = dijkstra($g, 0, 12); die 12 kommt bzw. was die zu bedeuten hat.
 
0 und 12 ist der start und endeknoten als index in g
0 ist der knoten mit x=7,y=2 und uns auf index 12 liegt der mit x=3, y=7
das hab ich einfach mal so reingeschrieben die musst du noch bestimmen.
 
Zurück
Oben