Schorsch: for-Schleife soll aus Funktion heraus abgebrochen werden

Guten Ostertag!

Ich habe eine for-Schlaufe, welche die Funktion countTextNodes aufruft. Diese ruft sich selber wiederum n-mal auf. (Rekursiv)

Irgendwann wird in der Funktion "countTextNodes" eine Bedingung erfüllt, welche diese abbrechen lässt und "return false" in die for-Schlaufe zurückgibt. Nun soll die for-Schlaufe sogleich abbrechen, und genau da liegt mein Problem, das macht sie nämlich nicht. Auch frage ich mich, ob meine Lösung nicht viel zu kompliziert ist, letztendlich brauche ich nur zu wissen, wie viele Textnode es hat, wenn man Start- und Endnode vorgibt.

Nachfolgend siehst du die Funkton "countTextNodes" welche aus der unten stehenden for-Schleife heraus aufgerufen wird.

var startNode; // Ein Node
var endNode;   // Auch ein Node
var countNode = 0; // Zähler, der mir die Textnodes zählt.

function countTextNodes(n){
    if (n.nodeType == 3)
    {
       if(n == EndNode){return false;} // Am Ziel angekommen, abbruch!
       countNode++;
    }
    else
    {
        var kids = n.childNodes;
        for(var i = 0; i < kids.length; i++) countTextNodes(kids[i]);
    }
}

for(var m = startNode; m != null; m = m.nextSibling) {
     var tmp = countTextNodes(m); // Aufruf von countTextNode()
     if(tmp == false) break; // Wenn countTextNode false zurück liefert, soll abgebrochen werden.
     // countTextNodes(m)?continue:break; Wäre genau das, was ich brauche, geht so aber nicht...
}

alert(countNode);

Ich weiss nicht, ob sich der Funktionsaufruf von countTextNodes noch irgendwie in die Bedingung der for-Schlaufe reinnehmen lässt...
Womöglich siehst du auch sofort, dass man das Ganze viel einfacher lösen könnte, bin ja gespannt!
Zur Zeit läuft die Funktion einfach durch, am endNode vorbei bis ans Ende der for-Schlaufe.

Schöne Eiersuche
Schorsch

  1. Guten Ostertag!

    Dir auch :)

    var startNode; // Ein Node
    var endNode;   // Auch ein Node

    Hier ist von endNode die Rede.

    var countNode = 0; // Zähler, der mir die Textnodes zählt.

    function countTextNodes(n){
        if (n.nodeType == 3)
        {
           if(n == EndNode){return false;} // Am Ziel angekommen, abbruch!

    Hier nicht! Die Bedingung kann nie erfüllt werden.

    countNode++;
        }
        else
        {
            var kids = n.childNodes;
            for(var i = 0; i < kids.length; i++) countTextNodes(kids[i]);
        }
    }

    for(var m = startNode; m != null; m = m.nextSibling) {

    IMHO ist der Vergleich != NULL irrelevant, den for() sollte abbrechen, wenn m==endNode
      for(var m = srartNode; m != endNode; m = m.nextSibiling)

    var tmp = countTextNodes(m); // Aufruf von countTextNode()
         if(tmp == false) break; // Wenn countTextNode false zurück liefert, soll abgebrochen werden.
         // countTextNodes(m)?continue:break; Wäre genau das, was ich brauche, geht so aber nicht...
    }

    alert(countNode);

    Gruß aus Berlin!
    eddi

    1. Salut
      Erstmals vielen Dank!

      for(var m = startNode; m != null; m = m.nextSibling) {
      IMHO ist der Vergleich != NULL irrelevant, den for() sollte abbrechen, wenn m==endNode
        for(var m = srartNode; m != endNode; m = m.nextSibiling)

      Leider scheint das bei mir nicht zu klappen. Könnte es sein, dass es daran liegt, dass sich der wert m ausserhalb der for-Schleife (in der rekursiven Funktion countTextNodes() ) mehrmals ändert und zum Zeitpunkt, wo die for-Schlaufe beginnt, der WErt von m nie m!=endNode ist?
      Zur Zeit rasselt die for-Schlaufe einfach bis ins Nirvana...

      Hat jemand eine Idee?

      Gruss Schorsch

      1. Re:

        Leider scheint das bei mir nicht zu klappen. Könnte es sein, dass es daran liegt, dass sich der wert m ausserhalb der for-Schleife (in der rekursiven Funktion countTextNodes() ) mehrmals ändert und zum Zeitpunkt, wo die for-Schlaufe beginnt, der WErt von m nie m!=endNode ist?

        Könnte sein, wenn der tatsächlich gepostete Code nur ein Ausschnitt ist. Anhand des vorliegenden Code führt aber var n jeweils in eine Sackgasse. Das hatte ich vorhin auch übersehen (PHP ist da ja traumhaft einfach). Durch Parameterübergaben läßt sich das aber auch wieder gradebiegen. Diesen Fall hatte ich auch mal bei einer Navigatorleiste in Form eines "Startmenüs". Hier ein Ausschnitt:

        a=new Array();
        a["0"]="Waren?Chat?Hilfe?Online Shop?";
        a["0a0"]="Artikel?Warengruppen?Vorbestellte Waren?Warenbestellung? ?Warenbestand?";
        a["0a0a2"]="Kundenregister?Warenregister?";
        a["0a0a1"]="Warenstamm? ?";
        function bui(v,j,w,k)
         {
         m='<table id="'+v+'" cellpadding="0" cellspacing="0">';
         x=a[v];
         for(i=j;x.length>0;i++)
          {
          if(x.substr(0,x.indexOf("?"))!=" ")
           {
           if(a[v+"a"+i]) m+='<tr onMouseover="sty(this);pru(''+v+'a'+i+'')" onMouseout="sty(this);pru(''+v+'a'+i+'')"><td><nobr>'+x.substr(0,x.indexOf("?"))+'</nobr></td><td>>'+bui(v+"a"+i,i,x,v)+'</td></tr>';
           else
            {
            m+='<tr onMouseover="sty(this)" onMouseout="sty(this)"';
            m+=' onClick="cli(this,'')"><td class="cur"><nobr>'+x.substr(0,x.indexOf("?"))+'</nobr></td><td></td></tr>';
            }
           }
          else   m+='<tr><td colspan="2"><hr></td></tr>';
          x=x.substr(x.indexOf("?")+1,x.length);
          }
         x=w
         i=j
         v=k
         m+='</table>';
         return(m);
         }
        function auf() {m=bui(0,0,"",0);document.getElementById("menu").innerHTML=m;}

        Gruß aus Berlin!
        eddi

        1. Salut und merci,

          Könnte sein, wenn der tatsächlich gepostete Code nur ein Ausschnitt ist.

          Ne, mein Code kann man mit den drei Variabeln (counter, start- und endnode ) als Einheit betrachten.

          Anhand des vorliegenden Code führt aber var n jeweils in eine Sackgasse.

          ? Wie meinst du das genau? Dieses Problem hatte ich nicht festgestellt. In der Funktion countTextNodes() läuft n ja genau solange, bis ein Textnode  kommt...

          Durch Parameterübergaben läßt sich das aber auch wieder gradebiegen.

          Das ist genau die Frage. Wie schaffe ich es, dass der wert n, während sich die Funktion countTextNodes() selber aufruft, bei einem ganz bestimmten Wert diesen an die for-Schleife zurückgibt und diese dadurch zum Abbrechen zwingt!
          Leider half mir dein Beispiel nicht weiter, trotzdem danke!

          Gruss
          Schorsch

          1. Salut und merci,

            Könnte sein, wenn der tatsächlich gepostete Code nur ein Ausschnitt ist.

            Ne, mein Code kann man mit den drei Variabeln (counter, start- und endnode ) als Einheit betrachten.

            Den Satz verstehe ich nicht; tut mir leid.

            Anhand des vorliegenden Code führt aber var n jeweils in eine Sackgasse.

            ? Wie meinst du das genau? Dieses Problem hatte ich nicht festgestellt. In der Funktion countTextNodes() läuft n ja genau solange, bis ein Textnode  kommt...

            Mag sein, daß ich hier einen Denkfehler mache. Nach meiner Vorstellung wird var n mit jedem rekursiven Aufruf von countTextNodes() als Parameter übergeben, gleichfalls wird n dadurch neu definiert ->>> n=kids[i].
            Wenn countTextNodes() das erste mal aufgerufen wird, ist n=startNode.
            Wenn n nicht von nodeType 3 ist, werden n.childNodes in einer Schleife durchlaufen, die jedes mal countTextNodes() aufruft, wodurch jedesmal n und kids neu definiert werden.

            startNode
              n
              |
              |-n.chidNodes[0]
              |  |
              |  |-n.n.childNodes[0]
              |  |
              |  |-n.n.childNodes[1]
              |
              |-n.childNodes[1]
              |
            endNode

            Diesen Baum vollzieht Deine Funktion. Da n!=n.childNodes gilt, ist n.n.childNodes natürlich Müll.

            for(var i = 0; i < kids.length; i++)
               {
               countTextNodes(kids[i]);
               }

            Diese for-Schliefe bricht ab, wenn es das array childNodes komplett durchlaufen hat (in diesem Fall ist es bei n.n.childNodes[1] so weit). Nun zu der verschachtelten Funktion countTextNodes() und diesem oben dargestellten Baum:
            Da die Funktion jedesmal den Wert von n mit n.childNodes ersetzt, werden alle Instanzen abgebrochen (i < kids.length ist erreicht) wenn der erste "Arm" an Baum durchlaufen wird und n.childNodes[1] wird dabei nie mitgezählt werden.

            Durch Parameterübergaben läßt sich das aber auch wieder gradebiegen.

            Das ist genau die Frage. Wie schaffe ich es, dass der wert n, während sich die Funktion countTextNodes() selber aufruft, bei einem ganz bestimmten Wert diesen an die for-Schleife zurückgibt und diese dadurch zum Abbrechen zwingt!

            Das halte ich nach wie vor für den falschen Ansatz, nunmehr wohlwissend der Tatsache, daß die Funktion countTextNodes() voraussetzt, das endNode immer ein Kindknoten direkt oder indirekt von startNode sein muß, oder für das gilt: endNode.parentNode == startNode.parentNode
            Das ist also auch eine Fehlerquelle.

            Leider half mir dein Beispiel nicht weiter, trotzdem danke!

            Schade - es ist eine Möglichkeit.

            Im eigentlichen ist ein Dokumentenbaum nichts anderes als ein polydimensionales Array, welches mit childNodes und parentNode sich wunderbar aufschlüsseln läßt. Diese beiden Methoden bewegen einen quasi-Datenzeiger (in Deiner Funktion var n).

            Deine Funktion muß lediglich so gestaltet werden, daß wenn sie in einem "Arm" des Baumes endet, dort nicht verendet; sondern so lange rekursiv nach dem parentNode fragt, bis entweder gilt:

            n.parentNode.parentNode.parentNode.[.... usw ....] == startNode
             oder
             n.parentNode.parentNode.parentNode.[.... usw ....].nextSibiling != NULL

            Gruß aus Berlin!
            eddi

  2. Moin!

    Ich habe eine for-Schlaufe, welche die Funktion countTextNodes aufruft. Diese ruft sich selber wiederum n-mal auf. (Rekursiv)

    Dein Problem fällt in die Kategorie "rekursives Programmieren richtig gemacht". :)

    Dein bisheriger Ansatz hat sehr viele Kritikpunkte, die ich mal im einzelnen erläutern will.

    Erstens: Du hast eine Funktion countTextNodes(). Allein von der Erwartung des Funktionsnamens her MUSS diese Funktion als Rückgabewert eine Zahl haben, nämlich die Anzahl der Textnodes.

    Das hat sie bei dir bisher nicht, sondern sie liefert _manchmal_ false zurück und ruft sich ansonsten rekursiv immer tiefer auf, ohne eine wirkliche Abbruchbedingung.

    Also ist der Kritikpunkt 1: Programmiere deine Funktion so, dass sie entweder so heißt wie das, was sie tut (in diesem Fall wäre der Name "indefinitelyRecurseIntoNodesAndMaybeBreak()" nicht verkehrt), oder dass sie so tut, wie sie heißt. Ich würde die zweite Methode vorziehen.

    Ok. Ausgangslage deines Problems: Du hast einen Startnode und einen Endnode, und du willst die Anzahl der Textnodes "dazwischen" herauskriegen.

    Ich vermute mal, dass es hierbei unweigerlich eine Baumstruktur gibt, für die sich rekursive Funktionen gut eignen.

    Deine Funktion muß folglich rekursiv folgendes machen:
    1. Schaue, ob es auf der aktuellen Ebene Nodes gibt, die tiefer verfolgt werden müssen. Rufe die Funktion dafür selbst auf, und addiere deren Ergebnis.
    2. Zähle außerdem die Textnodes der aktuellen Ebene, und addiere sie ebenfalls.
    3. Gib das Ergebnis zurück.

    Grob geschnitzt also so:
    function countNodes(node)
    {
      var summe = 0;
      for (i=0; i< node.childNodes.length; i++)
      {
        summe += countNodes(node.childNodes[i]);
      }
      if (node.nodeType == 3)
      {
        summe += 1;
      }
      return summe;
    }

    (Keine Garantie, dass alles richtig ist!)

    Diese Funktion wird einmal für einen Startknoten aufgerufen und gibt dir die Summe aller darunter befindlichen Kindknoten zurück - rekursiv, und ohne globale Variablen (was sehr wichtig ist beim Funktionsprogrammieren allgemein, und beim rekursiven Programmieren im speziellen noch mehr).

    So, und jetzt zu deinem speziellen Wunsch, bei Erreichen eines bestimmten Nodes alle weitere Summierung abzubrechen.

    Gemäß der Struktur kommt die Funktion also in der Unterstruktur des obersten Elternknotens irgendwann _vielleicht_ an den Knoten, der alles abbrechen soll. Also muß logischerweise dieser Tatbestand zurückgemeldet werden.

    _Eigentlich_ wäre für diese Programmierung tatsächlich eine globale Variable als Flag "Endnode wurde gefunden" ganz praktisch. Andererseits sind globale Variablen durchaus böse - aber sie vereinfachen hier doch einiges.

    Also wird erweitert:

    var found = false; // Flag, ob Endnode gefunden wurde

    function countNodes(node)
    {
      var summe = 0;
      for (i=0; i< node.childNodes.length; i++)
      {
        summe += countNodes(node.childNodes[i]);
      }
      if (!found)
      {
        if (node.nodeType == 3)
        {
          summe += 1;
        }
      }
      return summe;
    }

    Es ist eigentlich simpel: Wenn der Endnode noch nicht gefunden wurde (found == false ist), wird in der rekursiven Funktion ein gefundener Node gezählt. Wenn er gefunden wurde, wird (kommt noch) found = true gesetzt, und es werden gefundene Nodes nicht mehr addiert.

    Bleibt nur noch die Frage: Wie wird found = true? Dazu muß man in countNodes natürlich wissen, welcher Endnode überhaupt gesucht wird - das übergibt man schlauerweise auch als Parameter.

    var found = false; // Flag, ob Endnode gefunden wurde

    function countNodes(node, endnode)
    {
      var summe = 0;

    if (node == endnode)
      {
        found = true;
      }

    for (i=0; i< node.childNodes.length; i++)
      {
        summe += countNodes(node.childNodes[i], endnode);
      }
      if (!found)
      {
        if (node.nodeType == 3)
        {
          summe += 1;
        }
      }
      return summe;
    }

    Und der Aufruf wäre dann recht simpel:

    alert(countNodes(startnode, endnode));

    Anmerkung: Da ich mich mit rekursiver Programmierung besser auskenne, als mit den Javascript-DOM-Methoden, bitte ich zu bedenken, dass alle Skripte noch viele mögliche Fehler enthalten können, die einen Einsatz immer noch vereiteln können. :)

    - Sven Rautenberg

    1. Guten Tag Sven, guten Tag Eddie

      Vielen Dank für Eure Beiträge, habe sie eingehend studiert.
      Ich hoffe, Eure Anregungen verstanden und einigermassen umgesetzt zu haben.

      Erstens: Du hast eine Funktion countTextNodes(). Allein von der Erwartung des Funktionsnamens her MUSS diese Funktion als Rückgabewert eine Zahl haben, nämlich die Anzahl der Textnodes.

      Erfüllt

      1. Schaue, ob es auf der aktuellen Ebene Nodes gibt, die tiefer verfolgt werden müssen. Rufe die Funktion dafür selbst auf, und addiere deren Ergebnis.

      Erfüllt, charlie() (siehe unten)  sei dank!

      1. Zähle außerdem die Textnodes der aktuellen Ebene, und addiere sie ebenfalls.

      Habe eine for-Schlaufe gemacht, die das macht und jeden Node der "aktuellen Ebene" charlie() übergibt.

      1. Gib das Ergebnis zurück.

      Erfüllt

      ... und ohne globale Variablen

      Erfüllt

      ... bei Erreichen eines bestimmten Nodes alle weitere Summierung abzubrechen.

      Erfüllt

      ... der Tatsache, daß die Funktion countTextNodes() voraussetzt, das endNode immer ein Kindknoten direkt oder indirekt von startNode sein muß,... Das ist also auch eine Fehlerquelle.

      Behoben, Start- und Endnode können irgendwo liegen...

      Deine Funktion muß lediglich so gestaltet werden, daß wenn sie in einem "Arm" des Baumes endet, dort nicht verendet...

      Erfüllt,  mit for(m; m != null; m = m.nextSibling)

      Und so sieht die Funktion aus:

      // Kernstück ist die for-Schlaufe am Schluss der Funktion
      // welche die rekursive Funktion charlie() aufruft

      function countTextNodes(startNode, endNode)
      {
       var counter=0; // Conter, der mir die Anzahl Textnodes zählt
       if(startNode == endNode)return 1; // Wenn der Startnode derselbe wie Endnode, logisch nur 1 Node

      // Wenn Startnode ein Textnode ist, dann funzt nextSibling nicht,
       // also setze ich den Startnode auf die Eltern des Startnodes, um von
       // dort aus weiterzusibbeln...
       m = (startNode.nextSibling == null)?startNode.parentNode:startNode;

      // Diese Funktion läuft in die _Tiefe_
       // Rekursive Funktion, welche mir von n aus sämtliche Unteräste durchläuft
       // und dabei die Textnodes zählt.
       // Wenn es ein Textnode ist, muss die Funktion nicht mehr sich selber aufrufen
       // Da Textnodes nie kinder haben.
       function charlie(n, endNode)
       {
        if (n.nodeType == 3) // Wenn ein Textnode
        {
         counter++; // Diesen zählen

      // Durch das verschieben des Startnodes auf dessen Eltern
         // (m = (startNode.nextSibling == null)?startNode.parentNode:startNode;)
         // Gibt es Fälle, wo bereits frühere Texnodes vor dem Startnode
         // gezählt werden. Dies wird mit folgender Zeile verhindert
         if(n == startNode)counter = 1;

      // Abbruchbedingung, Endnode erreicht
         if(n == endNode){return false;} // false zurückgeben
        }

      // Wenn kein Texnode, dann schauen, ob der Node noch kinder hat
        // Wenn ja, durch Rekursion Kinder und Kindeskinder von ihm druchlaufen
        else
        {
         for(var i = 0; i < n.childNodes.length; i++)
         {
          // Wenn return false, abbrechen, false zurückgeben
          if(charlie(n.childNodes[i], endNode) == false){return false;}
         }
        }
       }

      // Diese Schlaufe läuft in die _Breite_
       // Diese Schlaufe geht von einem knoten zum nächstfolgenen
       // (nicht Kinderknoten!) und schickt diesen zu charlie()
       // der die Kinder des Knotens abgrast.
       for(m; m != null; m = m.nextSibling)
        {
           if(charlie(m, endNode) == false){break;}   // Wird false zurückgegeben abbruch
        }

      return counter; // counter zurückgeben
      }

      alert(countTextNodes(startNode, endNode));

      Diese Funktion liefert mir bis jetzt bei allen Tests das richtige Resultat.
      Was kann man noch weiter verbessern?
      Gibt noch "kritische Momente" in meinem Vorschlag?
      Bewege ich mich in der Kategorie

      "rekursives Programmieren richtig gemacht" ?

      Gruss und Merci
      Schorsch