[PHP] Recursie werkt niet goed?

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Reveller
  • Registratie: Augustus 2002
  • Laatst online: 05-12-2022
Ik zie het even niet meer. De bedoeling is als volgt: ik vul een array met menu items. Vervolgens roep ik de functie buildMenu aan. Deze moet een navigatie tabel uitspugen met daarin de goede menu items opengeklapt en vetgedrukt:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$navitems[1]  = Array('parent'=>0, 'name'=>'Home',      'href'=>''); 
$navitems[2]  = Array('parent'=>0, 'name'=>'Over Ons',  'href'=>'over.php'); 
$navitems[3]  = Array('parent'=>0, 'name'=>'Producten', 'href'=>'producten.php');
$navitems[4]  = Array('parent'=>0, 'name'=>'Diensten',  'href'=>'diensten.php'); 
$navitems[5]  = Array('parent'=>4, 'name'=>'Duur',      'href'=>'contact.php');
$navitems[6]  = Array('parent'=>4, 'name'=>'Heel Duur', 'href'=>'gastenboek.php');

function buildMenu($menuItems, $curItem, $depth, $unfoldItem) 
{
  global $page;

  $first = true; 
  foreach ($menuItems as $itemKey=>$item) 
  {
    if ($item['parent'] == $curItem and ($item['parent'] == 0 or $item['parent'] == $unfoldItem))
    { 
      if ($first) 
      { 
        if ($depth > 0) echo '<tr><td>&nbsp;</td><td>';       
        echo '<table>';
        $first = false; 
      } 
         
      echo '<tr><td>[img]"bullet.gif"[/img]</td>';
      if ($page == $itemKey)
        echo '<td><b>'.$item['name'].'</b></td></tr>';
      else
        echo '<td><a href="'.$item['href'].'">'.$item['name'].'</a></td></tr>';

      buildMenu($menuItems, $itemKey, $depth + 1, $unfoldItem); 
    }        
  } 
     
  if (!$first) 
  { 
    echo'</table>'; 
    if ($depth > 0) echo '</td></tr>'; 
  } 
} 

buildMenu($navitems, 0, 0, $page);

Het probleem: als ik "over ons" (buildMenu($navitems, 0, 0, 2)) aanroep, krijg ik een keurige navigatie met "over ons" vet gedrukt. Als ik "diensten" aanklik (buildMenu($navitems, 0, 0, 4)), werkt alles ook nog: "diensten" is vet en uitgeklapt:
HTML:
1
2
3
4
5
6
Home
Over Ons
Producten
<b>Diensten</b>
  Duur
  Heel Duur

MAar als ik "Duur" aanroep (buildMenu($navitems, 0, 0, 5)), krijg ik:
HTML:
1
2
3
4
Home
Over Ons
Producten
Diensten

in plaats van
HTML:
1
2
3
4
5
6
Home
Over Ons
Producten
Diensten
  <b>Duur</b>
  Heel Duur

Hoe kan dat? Ik ben er al lang mee bezig, maar volgens mij klopt de recursie toch?

[ Voor 18% gewijzigd door Reveller op 22-10-2004 16:50 . Reden: indices ge-quote ]

"Real software engineers work from 9 to 5, because that is the way the job is described in the formal spec. Working late would feel like using an undocumented external procedure."


Acties:
  • 0 Henk 'm!

  • Bosmonster
  • Registratie: Juni 2001
  • Laatst online: 18-09 16:28

Bosmonster

*zucht*

quote allereerst je array-indices eens

parent -> 'parent'

Acties:
  • 0 Henk 'm!

  • Reveller
  • Registratie: Augustus 2002
  • Laatst online: 05-12-2022
Gedaan, maar dat verandert niets aan de zaak, toch :?

"Real software engineers work from 9 to 5, because that is the way the job is described in the formal spec. Working late would feel like using an undocumented external procedure."


Acties:
  • 0 Henk 'm!

  • WormLord
  • Registratie: September 2003
  • Laatst online: 10:10

WormLord

Devver

Ga eens na wat de code precies doet in dat geval, en vergelijk dat met wat je wil dat het doet. Let daarbij vooral op regel 15 van je voorbeeldcode.

En laat dan hier even weten of je de fout gevonden hebt.

Acties:
  • 0 Henk 'm!

  • Reveller
  • Registratie: Augustus 2002
  • Laatst online: 05-12-2022
wat ontzettend dom zeg ... waarom doe ik nu zo moeilijk?

regel 15:
PHP:
1
if ($item['parent'] == $curItem) ...


-- edit -- te vroeg gejuigd ... nu staan alle nodes standaard open ... ik begrijp dat de voud ergens in regel 15 zit, maar snap niet goed wat er nu uit / anders moet?

[ Voor 42% gewijzigd door Reveller op 22-10-2004 18:39 ]

"Real software engineers work from 9 to 5, because that is the way the job is described in the formal spec. Working late would feel like using an undocumented external procedure."


Acties:
  • 0 Henk 'm!

  • Reveller
  • Registratie: Augustus 2002
  • Laatst online: 05-12-2022
WormLord - nog een hint, alsjeblieft? Ik zit nog steeds vast...

"Real software engineers work from 9 to 5, because that is the way the job is described in the formal spec. Working late would feel like using an undocumented external procedure."


Acties:
  • 0 Henk 'm!

  • Reptile209
  • Registratie: Juni 2001
  • Laatst online: 17:39

Reptile209

- gers -

Zit het niet in regel 25? Gooi eens een echo van de variabelen $page en $itemKey vlak voor je if. Want daar wordt bold aan en uit gezet. Toch?

Laat maar, dat gaat nergens over. Je moet kijken hoe en wanneer je Diensten moet openklappen, en met de huidige if in regel 15 lukt dat niet. * Reptile209 denkt er nog even over na ;)

Was ik weer :)
Volgens mij moet regel 15 als volgt luiden als laatste deel van de if-expressie:
code:
1
$item['parent'] == $menuItems[$unfoldItem]['parent'])

Da's vast geen PHP, maar ik bedoel het volgende: Je moet vergelijken of de parent van het huidige item gelijk is aan de parent van het geselecteerde item (met index $unfoldItem in je menulijst). Ofwel: je toont alle items die a) parent=0 hebben, en b) dezelfde parent hebben als het geselecteerde item.
Prog dat :).

Zoals je het nu hebt staan, worden voor "Duur" alleen de hoofditems (met parent = 0) getoond en de items met parent = 5 (en die zijn er niet).

[ Voor 89% gewijzigd door Reptile209 op 22-10-2004 22:42 ]

Zo scherp als een voetbal!


Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

(jarig!)
Imho vergeet je te kijken naar de parent van een uitgeklapte node, op het moment dat die parent anders dan 0 is.

[ Voor 6% gewijzigd door ACM op 22-10-2004 22:32 ]


Acties:
  • 0 Henk 'm!

  • Reveller
  • Registratie: Augustus 2002
  • Laatst online: 05-12-2022
@Reptile209 - jouw idee zit volgens mij in de goede richting. Het probleem is alleen als volgt: deze functie zal zichzelf eeuwig blijven herhalen, omdat er geen "rem" zit ingebouwd. Bij een "normale" recursieve functie, waarbij je bijvoorbeeld menu items ophaalt uit een database, stel je dat de functie moet stoppen als er geen menu items geretourneerd worden uit de database:

PHP:
1
2
3
4
5
6
7
8
$query = "SELECT nodes FROM tree WHERE parent_id = ".$parent;

if (db_num_rows(mysql_query($query)) != 0)
{
  // verwerk resultaten
}

// en anders stopt de functie dus

Ik zou aan elk item in $navitems een depth-property mee kunnen geven (depth=>1 etc.), en dan een cross-check uitvoeren met de maximale depth (constante) die het menu mag hebben, maar er moet toch een betere manier zijn? Ideeen, iemand?

"Real software engineers work from 9 to 5, because that is the way the job is described in the formal spec. Working late would feel like using an undocumented external procedure."


Acties:
  • 0 Henk 'm!

  • pjotrk
  • Registratie: Mei 2004
  • Laatst online: 15-07 18:43
Wanneer je item 6 probeert uit te klappen moet je zoals ACM al aangaf niet alleen alleen naar de directe parent kijken. Het script wil nu alleen item 6 gaan uitklappen, maar hiertoe komt hij niet omdat nodenummer 4 nieteens is uitgeklapt.

Wat je dus moet controleren is of er geen bovenliggende items zijn bij de uit te klappen items. Vervolgens zou je dan in de buildMenu function de eerste keer (op depth 0) de $unfoldItems array moeten aanvullen met alle boverliggende nodes.

Het totale script wordt dan iets als:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?
// neppaginanummer
$page = 6;

$navitems[1]  = Array('parent'=>0, 'name'=>'Home',      'href'=>''); 
$navitems[2]  = Array('parent'=>0, 'name'=>'Over Ons',  'href'=>'over.php'); 
$navitems[3]  = Array('parent'=>0, 'name'=>'Producten', 'href'=>'producten.php'); 
$navitems[4]  = Array('parent'=>0, 'name'=>'Diensten',  'href'=>'diensten.php'); 
$navitems[5]  = Array('parent'=>4, 'name'=>'Duur',      'href'=>'contact.php'); 
$navitems[6]  = Array('parent'=>4, 'name'=>'Heel Duur', 'href'=>'gastenboek.php');

// wat extra voorbeeld items
$navitems[7]  = Array('parent'=>2, 'name'=>'extra1',    'href'=>''); 
$navitems[8]  = Array('parent'=>7, 'name'=>'extra2',    'href'=>''); 
$navitems[9]  = Array('parent'=>8, 'name'=>'extra3',    'href'=>''); 

// functie voor opvragen van alle voorgaande parents
function getAnchestors($tree, $treeItem)
{
    $anchestors = array();
    
    // begin met de parent van het uit te vouwen item
    $parent = $tree[$treeItem]['parent'];

    // ga door totdat het 1e nivo berijkt is
    while ($parent != 0)
    {
        // voeg parent toe als anchestors
        $anchestors[] = $parent;
        
        // stap een nivo hoger
        $parent = $tree[$parent]['parent'];
    }
    
    return $anchestors;
}

function buildMenu($menuItems, $curItem, $depth, $unfoldItems) 
{ 
    global $page;

    // voer alleen de eerste keer uit
    if ($depth == 0)
    {
        // controlle of $unfoldItems een array is
        if (!is_array($unfoldItems))
        {
            $unfoldItems = array($unfoldItems);
        }

        // zoek bij elk uit te vouwen item de parents
        foreach ($unfoldItems as $unfoldItem) 
        { 
            // haal parents op
            $newUnfoldItems = getAnchestors($menuItems, $unfoldItem);
            
            // bewaar alleen de parents die nog niet in $menuItems zaten
            $newUnfoldItems = array_diff ($newUnfoldItems, $menuItems);
            
            //voeg de nieuwe items toe aan de $unfoldItem array
            $unfoldItems = array_merge ($unfoldItems, $newUnfoldItems);
        }
    }
    
    $first = true; 
    foreach ($menuItems as $itemKey=>$item) 
    { 
        // controleer of $item een subItem is van het huidige item en of hij 
        // voorkomt in de $unfoldItems array (of zelf een root node is)
        if ($item['parent']==$curItem and ($item['parent'] == 0 or in_array($item['parent'], $unfoldItems) ) )
        { 
            if ( $first ) 
            { 
                if ( $depth > 0 ) 
                { 
                    echo '<tr><td>&nbsp;</td><td>';      
                } 
          
                echo '<table border="1">'; 
                $first = false; 
            }

            echo '<tr><td>[img]"bullet.gif"[/img]</td>'; 
            
            if ($page == $itemKey) 
                echo '<td><b>'.$item['name'].'</b></td></tr>'; 
            else 
                echo '<td><a href="'.$item['href'].'">'.$item['name'].'</a></td></tr>'; 

            buildMenu($menuItems, $itemKey, $depth + 1, $unfoldItems); 
        }       
    } 
    
    if ( !$first ) 
    { 
        echo'</table>'; 
      
        if ( $depth > 0 ) 
        { 
            echo '</td></tr>'; 
        }
    } 
} 

// door $unfoldItems in een array mee te geven kunnen ook meerdere items worden uitgeklapt
buildMenu($navitems, 0, 0, array(9,6)); 

// of gewoon nog steeds:
buildMenu($navitems, 0, 0, $page); 
?>

[ Voor 17% gewijzigd door pjotrk op 23-10-2004 01:03 ]


Acties:
  • 0 Henk 'm!

  • Reveller
  • Registratie: Augustus 2002
  • Laatst online: 05-12-2022
@pjotrk - dank je, maar is dit geen overkill? Ik weet op voorhand al dat de boom nooit dieper wordt dan 2 lagen: items en subitems. Het enige wat ik wil is de kinderen van een item uitklappen als deze kinderen heeft en als ik een kind klik, ervoor zorgen dat dat kind vet wordt en uitgeklapt blijft. Jouw code werkt niet, maar met enige aanpassing waarschijnlijk wel. Je hebt me een goede duw gegeven, maar nogmaals: wel in de goede richting? Volgens mij moet de oorspronkelijke functie toch eenvoudig aan te passen zijn dat hij doet wat ik wil? Ik kom er nog steeds niet uit; mijn browser bevriest omdat die oorspronkelijke functie geen limiet kent voor het aantal loops dat ie moet maken... :/

"Real software engineers work from 9 to 5, because that is the way the job is described in the formal spec. Working late would feel like using an undocumented external procedure."


Acties:
  • 0 Henk 'm!

  • riezebosch
  • Registratie: Oktober 2001
  • Laatst online: 18-09 10:39
Als je boom nooit dieper wordt dan twee lagen, waarom doe je het dan niet zonder recursiviteit maar met een geneste while- of for-loop?

code:
1
2
3
4
5
6
7
8
9
10
11
while (currParrent != parrents.lastParrent)
{
   currSub = currParrent.firstSub;

   while (currSub != currParrent.lastSub)
   {
      currSub = currParrent.nextSub;
   }

   currParrent = parrent.nexParrent;
}

Canon EOS 400D + 18-55mm F3.5-5.6 + 50mm F1.8 II + 24-105 F4L + 430EX Speedlite + Crumpler Pretty Boy Back Pack


Acties:
  • 0 Henk 'm!

  • WormLord
  • Registratie: September 2003
  • Laatst online: 10:10

WormLord

Devver

Ik zou het eerste deel van je functie veranderen in:
PHP:
1
2
3
4
5
6
7
8
9
function buildMenu($menuItems, $curItem, $depth, $activeItem) 
{ 
  global $page; 

  $unfoldItem = $menuItems[$activeItem]["parent"];
  if (0 == $unfoldItem)
  {
    $unfoldItem = $activeItem;
  }


Het item dat je uitklapt moet namelijk het parent item zijn van het actieve item, tenzij het actieve item geen parent heeft.

Ik heb de code trouwens niet getest.

Acties:
  • 0 Henk 'm!

  • pjotrk
  • Registratie: Mei 2004
  • Laatst online: 15-07 18:43
Reveller schreef op 23 oktober 2004 @ 14:20:
@pjotrk - dank je, maar is dit geen overkill? Ik weet op voorhand al dat de boom nooit dieper wordt dan 2 lagen: items en subitems. Het enige wat ik wil is de kinderen van een item uitklappen als deze kinderen heeft en als ik een kind klik, ervoor zorgen dat dat kind vet wordt en uitgeklapt blijft. Jouw code werkt niet, maar met enige aanpassing waarschijnlijk wel. Je hebt me een goede duw gegeven, maar nogmaals: wel in de goede richting? Volgens mij moet de oorspronkelijke functie toch eenvoudig aan te passen zijn dat hij doet wat ik wil? Ik kom er nog steeds niet uit; mijn browser bevriest omdat die oorspronkelijke functie geen limiet kent voor het aantal loops dat ie moet maken... :/
Ik heb hem net nog maar even getest, maar hier doet ie het in ieder geval wel gewoon, misschien dat ik nog wat over het hoofd zie.

Of het overkill is weet ik niet, ikzelf maak het met dit soort kleine dingetjes vaak zo uitgebreidt mogelijk zodat ik het er later niet meer hoef toe te voegen, en het kon idd nog wel korter :p, als je zeker weet dat je nooit meer dan 2 nivo's hebt is de oplossing van wormlord inderdaad goed.

De global variable kan dan wel weg (globals vermijd ik sowieso liever eigenlijk). En de if statements kunnen nog wel wat eenvoudiger.
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?
$navitems[1]  = Array('parent'=>0, 'name'=>'Home',      'href'=>''); 
$navitems[2]  = Array('parent'=>0, 'name'=>'Over Ons',  'href'=>'over.php'); 
$navitems[3]  = Array('parent'=>0, 'name'=>'Producten', 'href'=>'producten.php'); 
$navitems[4]  = Array('parent'=>0, 'name'=>'Diensten',  'href'=>'diensten.php'); 
$navitems[5]  = Array('parent'=>4, 'name'=>'Duur',      'href'=>'contact.php'); 
$navitems[6]  = Array('parent'=>4, 'name'=>'Heel Duur', 'href'=>'gastenboek.php'); 
$navitems[7]  = Array('parent'=>6, 'name'=>'Heel Duur', 'href'=>'gastenboek.php'); 

function buildMenu($menuItems, $activeItem, $curItem=0, $depth = 0) 
{ 
    $unfoldItem = $menuItems[$activeItem]['parent']; 
    if ( $unfoldItem == 0 ) 
        $unfoldItem = $activeItem;

    $first = true;
    foreach ( $menuItems as $itemKey=>$item ) 
    { 
        if ( $item['parent'] == $curItem )
        {
            if ( $first )
            {   
                if ( $depth > 0 ) echo '<tr><td>&nbsp;</td><td>';
                echo '<table>';
                $first = false;
            }
   
            echo '<tr><td>[img]"bullet.gif"[/img]</td>';
            if ( $activeItem == $itemKey )
                echo '<td><b>'.$item['name'].'</b></td></tr>'; 
            else 
                echo '<td><a href="'.$item['href'].'">'.$item['name'].'</a></td></tr>';             

            if ( $itemKey == $unfoldItem ) 
                buildMenu($menuItems, $activeItem, $itemKey, 1);
        }
    } 
     
    if ( !$first )
    {
        echo '</table>'; 
        if ( $depth > 0 )  echo '</td></tr>'; 
    }
}

buildMenu($navitems, $page); 
?>


dit kan je ook nog eenvoudig geschikt maken voor oneindig diep als je:

PHP:
1
2
3
4
5
<?
    $unfoldItem = $menuItems[$activeItem]['parent']; 
    if ( $unfoldItem == 0 ) 
        $unfoldItem = $activeItem;
?>

vervang door
PHP:
1
2
3
4
5
<?
    $unfoldItems = array($activeItem); 
    while ( $unfoldItems[count($unfoldItems)-1] != 0 )
        $unfoldItems[] = $menuItems[$unfoldItems[count($unfoldItems)-1]]['parent'];
?>

en
PHP:
1
2
3
4
<?
            if ( $itemKey == $unfoldItem ) 
                buildMenu($menuItems, $activeItem, $itemKey, 1);
?>

door
PHP:
1
2
3
4
<?
            if ( in_array($itemKey, $unfoldItems) )
                buildMenu($menuItems, $activeItem, $itemKey, $depth + 1 ); 
?>

[ Voor 15% gewijzigd door pjotrk op 23-10-2004 23:23 ]


Acties:
  • 0 Henk 'm!

  • Reveller
  • Registratie: Augustus 2002
  • Laatst online: 05-12-2022
@WormLord, pjotrk - dank jullie wel :) Ik heb uiteindelijk gekozen om het toch maar voor te bereiden op oneindig diep. Je weet maar nooit. Ik ga wel nog eens even de code van pjotrk doornemen om te kijken WAAROM het nou werkt :+

"Real software engineers work from 9 to 5, because that is the way the job is described in the formal spec. Working late would feel like using an undocumented external procedure."

Pagina: 1