[PHP/MySQL] Subtotalen berekenen in nested array

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • radem205
  • Registratie: Juni 2002
  • Laatst online: 02-02-2022
Beste tweakers,

Voor het maken van een budgetoverzicht met verschillende niveau's (boomstructuur), wil ik vanuit het diepste niveau in een array de subtotalen berekenen voor de bovenliggende niveau's.

Hiervoor maak ik gebruik van een Adjacency List, waarbij ik de gegevens inclusief parent id heb opgeslagen in een database.

Een voorbeeld van deze boomstructuur met bijbehorende array is:

Hoofdpost
--Subpost 1
------Subpost 2 (bedrag: 50)
------Subpost 3 (bedrag: 10)
------Subpost 4 (bedrag: 10)
--Subpost 5
------Subpost 6
----------Subpost 7
--------------Subpost 8 (bedrag: 10)
--------------Subpost 9 (bedrag: 20)
----------Subpost 10 (bedrag 10)

De bijbehorende array:

code:
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
Array
(
    [0] => Array
        (
            [titel] => Hoofdpost
            [bedrag] => 
            [children] => Array
                (
                    [0] => Array
                        (
                            [titel] => Subpost1
                            [bedrag] => 
                            [children] => Array
                                (
                                    [0] => Array
                                        (
                                            [titel] => Subpost2
                                            [bedrag] => 50
                                        )

                                    [1] => Array
                                        (
                                            [titel] => Subpost3
                                            [bedrag] => 10
                                        )

                                    [2] => Array
                                        (
                                            [titel] => Subpost4
                                            [bedrag] => 20
                                        )

                                )

                        )

                    [1] => Array
                        (
                            [titel] => Subpost5
                            [bedrag] => 
                            [children] => Array
                                (
                                    [0] => Array
                                        (
                                            [titel] => Subpost6
                                            [bedrag] => 
                                            [children] => Array
                                                (
                                                    [0] => Array
                                                        (
                                                            [titel] => Subpost7
                                                            [bedrag] => 
                                                            [children] => Array
                                                                (
                                                                    [titel] => Subpost8
                                                                    [bedrag] => 10
                                                                )

                                                            [0] => Array
                                                                (
                                                                    [titel] => Subpost9
                                                                    [bedrag] => 20
                                                                )

                                                        )

                                                    [1] => Array
                                                        (
                                                            [titel] => Subpost10
                                                            [bedrag] => 10
                                                        )

                                                )

                                        )

                                )

                        )

                )

        )

)


Wat ik dus wil bereiken is dat ie op alle niveau's de subtotalen berekend. Dus als volgt:

Hoofdpost (bedrag: 110)
--Subpost 1 (bedrag: 70)
------Subpost 2 (bedrag: 50)
------Subpost 3 (bedrag: 10)
------Subpost 4 (bedrag: 10)
--Subpost 5 (bedrag: 40)
------Subpost 6 (bedrag: 40)
----------Subpost 7 (bedrag: 30)
--------------Subpost 8 (bedrag: 10)
--------------Subpost 9 (bedrag: 20)
----------Subpost 10 (bedrag 10)

Weet iemand hoe ik bovenstaande kan bereiken? Ik heb namelijk geen idee hoe je in omgekeerde volgorde een nested array kan doorlopen om zodoende de bovenliggende totalen te bereken.

Of zijn er betere methodes om een boomstructuur te krijgen waarin de subtotalen zijn berekend?

Acties:
  • 0 Henk 'm!

  • DataGhost
  • Registratie: Augustus 2003
  • Laatst online: 18:34

DataGhost

iPL dev

Dit kan je recursief berekenen. Je roept je functie aan op Hoofdpost en die gaat vervolgens alle bedragen van subposten optellen, wat vervolgens alle bedragen van die subposten op gaat tellen, wat vervolgens... en als dat allemaal klaar is wordt het vanzelf 'achteruit' ingevuld.

Acties:
  • 0 Henk 'm!

  • radem205
  • Registratie: Juni 2002
  • Laatst online: 02-02-2022
Ik heb nu onderstaande functie:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getChildrenSum($array)
{
    if(is_array($array) AND count($array) > 0) {
        
        foreach($array AS $key => $item) {
            
            if($item['bedrag'])
                $sum += $item['bedrag'];
            if(is_array($item['children']) AND count($item['children']) > 0)
                $sum += getChildrenSum($item['children']);
            
        }
        
        return $sum;
        
    }
}


Met bovenstaande code laat ie nu het volledige totaal zijn van alle niveau's.
Alleen snap ik nu niet hoe je de $sum per niveau toegevoegd krijgt aan de bestaande array.

[ Voor 6% gewijzigd door radem205 op 09-02-2017 09:04 ]


Acties:
  • 0 Henk 'm!

  • DataGhost
  • Registratie: Augustus 2003
  • Laatst online: 18:34

DataGhost

iPL dev

Je wilt waarschijnlijk het argument van je functie by-reference maken in plaats van by-value, en dan moet het laatste stukje toch wel op z'n plaats gaan vallen denk ik zo :)

Acties:
  • 0 Henk 'm!

  • radem205
  • Registratie: Juni 2002
  • Laatst online: 02-02-2022
Ik had al het vermoeden dat ik by-reference moet gebruiken, maar toch kom ik er nog niet uit.

Ik heb nu dit:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getChildrenSum(array &$items)
{
   
    if(is_array($items) AND count($items) > 0) {
        
        foreach($items AS $key => $item) {
            
            if(is_array($item['children']) AND count($item['children']) > 0)
                $items[$key]['bedrag'] += getChildrenSum($item['children']);
            
        }
        
    } 
        
    return $items;
    
}


Maar ik krijg een foutmelding: Fatal error: Unsupported operand types.

Wat doe ik fout?

Acties:
  • 0 Henk 'm!

  • DataGhost
  • Registratie: Augustus 2003
  • Laatst online: 18:34

DataGhost

iPL dev

Je hebt zo te zien wel meer veranderd dan alleen byref maken van je argument :+

Acties:
  • 0 Henk 'm!

  • radem205
  • Registratie: Juni 2002
  • Laatst online: 02-02-2022
DataGhost schreef op donderdag 9 februari 2017 @ 09:34:
Je hebt zo te zien wel meer veranderd dan alleen byref maken van je argument :+
Daar heb je gelijk in. Het probleem zit 'm volgens mij in dat de $sum niet teruggeplaatst wordt in de array. Maar ik weet even niet hoe ik dit ga doen.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getChildrenSum(&$array)
{
    if(is_array($array) AND count($array) > 0) {
        
        foreach($array AS $key => $item) {
            
            if($item['bedrag'])
                $sum += $item['bedrag'];
            if(is_array($item['children']) AND count($item['children']) > 0)
                $sum += getChildrenSum($item['children']);
            
        }
        
        return $sum;
        
    }
}


Dit retourneert nog steeds het totaal (110).

Acties:
  • 0 Henk 'm!

  • DataGhost
  • Registratie: Augustus 2003
  • Laatst online: 18:34

DataGhost

iPL dev

Ja dat klopt, deze retourneert wél het totaal ;). Het zat dus ook in een ander deel van je code dan waar je zat te kijken (regel 9 van je tweede snippet gok ik, en daar zit niet de fout). Een diff maakt direct duidelijk waar je fout zit.

[ Voor 5% gewijzigd door DataGhost op 09-02-2017 09:44 ]


Acties:
  • 0 Henk 'm!

  • radem205
  • Registratie: Juni 2002
  • Laatst online: 02-02-2022
Hmm, ik blijf maar tegen het probleem aanlopen dat ik niet weet hoe ik de waarde van $sum kan toevoegen aan de array.

Ik heb nu dit:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function getChildrenSum(&$items)
{
    
    if(is_array($items) AND count($items) > 0) {
        
        $sum = 0;
        foreach($items AS $key => $item) {
            
            if(is_array($item['children']) AND count($item['children']) > 0)
                $sum += getChildrenSum($item['children']);
            else
                $sum += $item['bedrag'];
            
        } 
        
        
        echo $sum;
        return $sum;
        
    }  
}


De echo $sum geeft nu mooi de subtotalen per subarray weer, maar ik krijg de waarde niet goed geplaatst in de oorspronkelijke array.

Acties:
  • 0 Henk 'm!

  • Daos
  • Registratie: Oktober 2004
  • Niet online
Je hele recursie vind ik al onhandig. Ik zou het zo doen:

PHP:
1
2
3
4
5
6
7
8
9
function insertTotal(&$item)
{
        $sum = 0;
        foreach($item['children'] as &$innerItem) {
        // TODO: Implement
        } 
        
        $item['bedrag'] = $sum;  
}

Acties:
  • 0 Henk 'm!

  • DataGhost
  • Registratie: Augustus 2003
  • Laatst online: 18:34

DataGhost

iPL dev

radem205 schreef op donderdag 9 februari 2017 @ 10:36:
Hmm, ik blijf maar tegen het probleem aanlopen dat ik niet weet hoe ik de waarde van $sum kan toevoegen aan de array.

Ik heb nu dit:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function getChildrenSum(&$items)
{
    
    if(is_array($items) AND count($items) > 0) {
        
        $sum = 0;
        foreach($items AS $key => $item) {
            
            if(is_array($item['children']) AND count($item['children']) > 0)
                $sum += getChildrenSum($item['children']);
            else
                $sum += $item['bedrag'];
            
        } 
        
        
        echo $sum;
        return $sum;
        
    }  
}


De echo $sum geeft nu mooi de subtotalen per subarray weer, maar ik krijg de waarde niet goed geplaatst in de oorspronkelijke array.
Dat krijg je wel, kijk nog eens naar je code in radem205 in "\[PHP/MySQL] Subtotalen berekenen in nested array". Deze is goed, behalve dat je een array bij de som probeert op te tellen op regel 9. Waardoor dat komt zit elders in dat stukje code, dat is iets wat je per ongeluk veranderd hebt, daar probeerde ik je subtiel naartoe te sturen maar het lijkt erop dat ik iets té subtiel was.
Daos schreef op donderdag 9 februari 2017 @ 10:39:
Je hele recursie vind ik al onhandig. Ik zou het zo doen:

PHP:
1
2
3
4
5
6
7
8
9
function insertTotal(&$item)
{
        $sum = 0;
        foreach($item['children'] as &$innerItem) {
        // TODO: Implement
        } 
        
        $item['bedrag'] = $sum;  
}
En hoe denk je hiermee een 1000-diep genest subtotaal te kunnen berekenen? Dit is juist schoolvoorbeeld van iets waarvoor je recursie gebruikt.

Acties:
  • 0 Henk 'm!

  • Daos
  • Registratie: Oktober 2004
  • Niet online
DataGhost schreef op donderdag 9 februari 2017 @ 10:43:
En hoe denk je hiermee een 1000-diep genest subtotaal te kunnen berekenen? Dit is juist schoolvoorbeeld van iets waarvoor je recursie gebruikt.
Met recursie :P Het ging mij om de parameter: 1 item ipv een array.

Acties:
  • 0 Henk 'm!

  • radem205
  • Registratie: Juni 2002
  • Laatst online: 02-02-2022
DataGhost schreef op donderdag 9 februari 2017 @ 10:43:
[...]

Dat krijg je wel, kijk nog eens naar je code in radem205 in "\[PHP/MySQL] Subtotalen berekenen in nested array". Deze is goed, behalve dat je een array bij de som probeert op te tellen op regel 9. Waardoor dat komt zit elders in dat stukje code, dat is iets wat je per ongeluk veranderd hebt, daar probeerde ik je subtiel naartoe te sturen maar het lijkt erop dat ik iets té subtiel was.
Ik heb het gevoel dat ik in de buurt van de oplossing kom. Met onderstaande code zie ik de subtotalen, alleen bij Subpost5 zie ik het totaal van de Hoofdpost. Weet niet hoe dit kan.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getChildrenSum(array &$items)
{
    if(is_array($items) AND count($items) > 0) {
        $sum = 0;
        foreach($items AS $key => $item) {
            
            if(is_array($item['children']) AND count($item['children']) > 0) {
                $sum += getChildrenSum($items[$key]['children']);
                $items[$key]['bedrag'] = $sum;
            } else 
                $sum += $item['bedrag'];
               
        }
        
    } 
    
    return $sum;
}


code:
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
Array
(
    [0] => Array
        (
            [titel] => Hoofdpost
            [bedrag] => 120
            [children] => Array
                (
                    [0] => Array
                        (
                            [titel] => Subpost1
                            [bedrag] => 80
                            [children] => Array
                                (
                                    [0] => Array
                                        (
                                            [titel] => Subpost2
                                            [bedrag] => 50
                                        )

                                    [1] => Array
                                        (
                                            [titel] => Subpost3
                                            [bedrag] => 10
                                        )

                                    [2] => Array
                                        (
                                            [titel] => Subpost4
                                            [bedrag] => 20
                                        )

                                )

                        )

                    [1] => Array
                        (
                            [titel] => Subpost5
                            [bedrag] => 120
                            [children] => Array
                                (
                                    [0] => Array
                                        (
                                            [titel] => Subpost6
                                            [bedrag] => 40
                                            [children] => Array
                                                (
                                                    [0] => Array
                                                        (
                                                            [titel] => Subpost7
                                                            [bedrag] => 30
                                                            [children] => Array
                                                                (
                                                                    [0] => Array
                                                                        (
                                                                            [titel] => Subpost8
                                                                            [bedrag] => 10
                                                                        )

                                                                    [1] => Array
                                                                        (
                                                                            [titel] => Subpost9
                                                                            [bedrag] => 20
                                                                        )

                                                                )

                                                        )

                                                    [1] => Array
                                                        (
                                                            [titel] => Subpost10
                                                            [bedrag] => 10
                                                        )

                                                )

                                        )

                                )

                        )

                )

        )

)

[ Voor 5% gewijzigd door radem205 op 09-02-2017 11:23 ]


Acties:
  • 0 Henk 'm!

  • Daos
  • Registratie: Oktober 2004
  • Niet online
Kijk dan naar mijn opzetje. Base-case toevoegen aan het begin van de functie + 2 regels in de body van de foreach (+ misschien een if om te kijken of children wel bestaat) en je bent klaar.

Acties:
  • 0 Henk 'm!

  • DataGhost
  • Registratie: Augustus 2003
  • Laatst online: 18:34

DataGhost

iPL dev

Dat komt vooral doordat je heel slordig omgaat met $items, $item, by-value en by-reference. Je mixt deze alle vier stevig door elkaar en gooit daar nog een vleugje array, eigenlijk-niet-array en array-als-object overheen. De code die je schrijft lijkt weinig doordacht, misschien is het handig eerst eens op papier stap voor stap uit te schrijven wat je moet doen. Hint: je foreach is by-value.

Acties:
  • 0 Henk 'm!

  • CH4OS
  • Registratie: April 2002
  • Niet online

CH4OS

It's a kind of magic

radem205 schreef op donderdag 9 februari 2017 @ 11:13:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getChildrenSum(array &$items)
{
    if(is_array($items) AND count($items) > 0) {
        $sum = 0;
        foreach($items AS $key => $item) {
            
            if(is_array($item['children']) AND count($item['children']) > 0) {
                $sum += getChildrenSum($items[$key]['children']);
                $items[$key]['bedrag'] = $sum;
            } else 
                $sum += $item['bedrag'];
               
        }
        
    } 
    
    return $sum;
}
Als je al typehint dat het argument in je functie een array moet zijn, hoef je in je functie niet nog eens te checken of het echt een array is hoor. ;) Ook hoef je dan het aantal items in de array niet te tellen. :)

[ Voor 3% gewijzigd door CH4OS op 09-02-2017 11:33 ]


Acties:
  • 0 Henk 'm!

  • radem205
  • Registratie: Juni 2002
  • Laatst online: 02-02-2022
Hehe, eindelijk lijkt ie te werken met onderstaande code.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getChildrenSum(array &$item)
{
        $sum = 0;

        if(is_array($item['children']) AND count($item['children']) > 0) {
            foreach($item['children'] AS &$innerItem) {
                getChildrenSum($innerItem);
                $sum += $innerItem['bedrag'];
                
            } 
            $item['bedrag'] = $sum;
        } 
      
        return $sum;
}

getChildrenSum($items[0]);


Bedankt voor jullie hulp!

Acties:
  • 0 Henk 'm!

  • CH4OS
  • Registratie: April 2002
  • Niet online

CH4OS

It's a kind of magic

Ben dan toch nog wel even benieuwd waarom je de if-statement in je functie hebt, terwijl je ook typehint op de variabele dat het een array moet zijn.

De count is vrij onzinnig om nog op te checken en de is_array is dubbelop.


Moet lezen... 7(8)7

[ Voor 43% gewijzigd door CH4OS op 09-02-2017 12:01 ]


Acties:
  • 0 Henk 'm!

  • radem205
  • Registratie: Juni 2002
  • Laatst online: 02-02-2022
CH40S schreef op donderdag 9 februari 2017 @ 11:59:
Ben dan toch nog wel even benieuwd waarom je de if-statement in je functie hebt, terwijl je ook typehint op de variabele dat het een array moet zijn.

Ergo; je if statement evalueert nu altijd naar true.
Als ik het goed heb, weet je in de code nog niet of de key children een array is en items bevat.

Acties:
  • 0 Henk 'm!

  • CH4OS
  • Registratie: April 2002
  • Niet online

CH4OS

It's a kind of magic

radem205 schreef op donderdag 9 februari 2017 @ 12:00:
[...]


Als ik het goed heb, weet je in de code nog niet of de key children een array is en items bevat.
Ah, ik was wat voorbarig idd. Ik zie het verschil nu. Mea culpa.
Pagina: 1