Functional coverage tree

Functional coverage tree is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Functional coverage is a measure of how much a particular function of a system has been verified as correct. It is used heavily in tracking the completeness of the verification of complex System on Chip (SoC) integrated circuits, where it can also be used to track how well the functional requirements of the system have been verified.

This task uses a sub-set of the calculations sometimes used in tracking functional coverage but uses a more familiar(?) scenario.

The head of the clean-up crews for "The Men in a very dark shade of grey when viewed at night" has been tasked with managing the cleansing of two properties after an incident involving aliens.

She arranges the task hierarchically with a manager for the crews working on each house who return with a breakdown of how they will report on progress in each house.

The overall hierarchy of (sub)tasks is as follows,

```cleaning
house1
bedrooms
bathrooms
bathroom1
bathroom2
outside lavatory
attic
kitchen
living rooms
lounge
dining room
conservatory
playroom
basement
garage
garden
house2
upstairs
bedrooms
suite 1
suite 2
bedroom 3
bedroom 4
bathroom
toilet
attics
groundfloor
kitchen
living rooms
lounge
dining room
conservatory
playroom
wet room & toilet
garage
garden
hot tub suite
basement
cellars
wine cellar
cinema```

The head of cleanup knows that her managers will report fractional completion of leaf tasks (tasks with no child tasks of their own), and she knows that she will want to modify the weight of values of completion as she sees fit.

Some time into the cleaning, and some coverage reports have come in and she thinks see needs to weight the big house2 60-40 with respect to coverage from house1 She prefers a tabular view of her data where missing weights are assumed to be 1.0 and missing coverage 0.0.

```NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |
cleaning                        |        |          |
house1                      |40      |          |
bedrooms                |        |0.25      |
bathrooms               |        |          |
bathroom1           |        |0.5       |
bathroom2           |        |          |
outside_lavatory    |        |1         |
attic                   |        |0.75      |
kitchen                 |        |0.1       |
living_rooms            |        |          |
lounge              |        |          |
dining_room         |        |          |
conservatory        |        |          |
playroom            |        |1         |
basement                |        |          |
garage                  |        |          |
garden                  |        |0.8       |
house2                      |60      |          |
upstairs                |        |          |
bedrooms            |        |          |
suite_1         |        |          |
suite_2         |        |          |
bedroom_3       |        |          |
bedroom_4       |        |          |
bathroom            |        |          |
toilet              |        |          |
attics              |        |0.6       |
groundfloor             |        |          |
kitchen             |        |          |
living_rooms        |        |          |
lounge          |        |          |
dining_room     |        |          |
conservatory    |        |          |
playroom        |        |          |
wet_room_&_toilet   |        |          |
garage              |        |          |
garden              |        |0.9       |
hot_tub_suite       |        |1         |
basement                |        |          |
cellars             |        |1         |
wine_cellar         |        |1         |
cinema              |        |0.75      |```
Calculation

The coverage of a node in the tree is calculated as the weighted average of the coverage of its children evaluated bottom-upwards in the tree.

The task is to calculate the overall coverage of the cleaning task and display the coverage at all levels of the hierarchy on this page, in a manner that visually shows the hierarchy, weights and coverage of all nodes.

Extra Credit

After calculating the coverage for all nodes, one can also calculate the additional/delta top level coverage that would occur if any (sub)task where to be fully covered from its current fractional coverage. This is done by multiplying the extra coverage that could be gained ${\displaystyle 1-coverage}$ for any node, by the product of the `powers` of its parent nodes from the top down to the node.
The power of a direct child of any parent is given by the power of the parent multiplied by the weight of the child divided by the sum of the weights of all the direct children.

The pseudo code would be:

```   method delta_calculation(this, power):
sum_of_weights = sum(node.weight for node in children)
this.delta  = (1 - this.coverage) * power
for node in self.children:
node.delta_calculation(power * node.weight / sum_of_weights)
return this.delta
```

Followed by a call to:

```   top.delta_calculation(power=1)
```

Note: to aid in getting the data into your program you might want to use an alternative, more functional description of the starting data given on the discussion page.

J

Implementation (raw data):

`raw=: 0 :0NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |cleaning                        |        |          |    house1                      |40      |          |        bedrooms                |        |0.25      |        bathrooms               |        |          |            bathroom1           |        |0.5       |            bathroom2           |        |          |            outside_lavatory    |        |1         |        attic                   |        |0.75      |        kitchen                 |        |0.1       |        living_rooms            |        |          |            lounge              |        |          |            dining_room         |        |          |            conservatory        |        |          |            playroom            |        |1         |        basement                |        |          |        garage                  |        |          |        garden                  |        |0.8       |    house2                      |60      |          |        upstairs                |        |          |            bedrooms            |        |          |                suite_1         |        |          |                suite_2         |        |          |                bedroom_3       |        |          |                bedroom_4       |        |          |            bathroom            |        |          |            toilet              |        |          |            attics              |        |0.6       |        groundfloor             |        |          |            kitchen             |        |          |            living_rooms        |        |          |                lounge          |        |          |                dining_room     |        |          |                conservatory    |        |          |                playroom        |        |          |            wet_room_&_toilet   |        |          |            garage              |        |          |            garden              |        |0.9       |            hot_tub_suite       |        |1         |        basement                |        |          |            cellars             |        |1         |            wine_cellar         |        |1         |            cinema              |        |0.75      |)`

Implementation (unpacking raw data):

`labels=: {.<;._2;._2 raw'hier wspec cspec'=:|:}.<;._2;._2 rawlevel=: (%+./) (0 i.~' '&=)"1 hierweight=: (+ 0=]) ,".wspeccoverage=:  ,".cspec`

Implementation (translation of leaf coverage to functional coverage):

`merge=: ;@(({.@[,(+}.)~)&.> [: +/\1,_1}.#@>)unrooted=: ([: merge <@(_1,\$:@}.);.1)^:(0<#)parent=:  unrooted levelparent_cover=: (] (1}.~.parent)}~ 1}. * %&(parent +//. ]) [)^:_`

`unrooted` translates indentation information to a parent tree structure. However, the limitations of recursion require we distinguish the parent node from its children, so we use _1 to denote the parent node of the recursive intermediate result unrooted trees. (This works well with using arithmetic to adjust sub-tree indices based on the lengths of preceding sub-trees.) `merge` combines a boxed sequence of these subtrees to form a single tree - we also rely on the first node of each tree being both _1 and the root node.

Thus, `parent_cover` propagates coverage to parent nodes based on the weighted average of coverage at the children.

Task example (format and show result):

`   1 1 }._1 }.":labels,each ":each hier;(,.weight);,.weight parent_cover coverageNAME_HIERARCHY                  │WEIGHT  │COVERAGE  │cleaning                        │ 1      │0.409167  │    house1                      │40      │ 0.33125  │        bedrooms                │ 1      │    0.25  │        bathrooms               │ 1      │     0.5  │            bathroom1           │ 1      │     0.5  │            bathroom2           │ 1      │       0  │            outside_lavatory    │ 1      │       1  │        attic                   │ 1      │    0.75  │        kitchen                 │ 1      │     0.1  │        living_rooms            │ 1      │    0.25  │            lounge              │ 1      │       0  │            dining_room         │ 1      │       0  │            conservatory        │ 1      │       0  │            playroom            │ 1      │       1  │        basement                │ 1      │       0  │        garage                  │ 1      │       0  │        garden                  │ 1      │     0.8  │    house2                      │60      │0.461111  │        upstairs                │ 1      │    0.15  │            bedrooms            │ 1      │       0  │                suite_1         │ 1      │       0  │                suite_2         │ 1      │       0  │                bedroom_3       │ 1      │       0  │                bedroom_4       │ 1      │       0  │            bathroom            │ 1      │       0  │            toilet              │ 1      │       0  │            attics              │ 1      │     0.6  │        groundfloor             │ 1      │0.316667  │            kitchen             │ 1      │       0  │            living_rooms        │ 1      │       0  │                lounge          │ 1      │       0  │                dining_room     │ 1      │       0  │                conservatory    │ 1      │       0  │                playroom        │ 1      │       0  │            wet_room_&_toilet   │ 1      │       0  │            garage              │ 1      │       0  │            garden              │ 1      │     0.9  │            hot_tub_suite       │ 1      │       1  │        basement                │ 1      │0.916667  │            cellars             │ 1      │       1  │            wine_cellar         │ 1      │       1  │            cinema              │ 1      │    0.75  │`

Extra credit:

`trace=: (~.@,each  (0 >. parent)&{)^:_  i.#parentpower=: */@:{&(parent (] % (i.~ ~.)@[ { +//.) weight)@> trace    power*1-weight parent_cover coverage0.590833 0.2675 0.0375 0.025 0.00833333 0.0166667 0 0.0125 0.045 0.0375 0.0125 0.0125 0.0125 0 0.05 0.05 0.01 0.323333 0.17 0.05 0.0125 0.0125 0.0125 0.0125 0.05 0.05 0.02 0.136667 0.0333333 0.0333333 0.00833333 0.00833333 0.00833333 0.00833333 0.0333333 0.0333333 0.00333333 0 0.0166667 0 0 0.0166667`

Explanation:

`trace` is, for each node, the set of nodes (or indices of nodes - since we use indices to identify nodes) leading from that node to its root.

`parent (] % (i.~ ~.)@[ { +//.) weight` is the weight of each node divided by the total weight for all nodes with the same parent.

`power` is the product of these relative weights for each member of the trace.

And, `weight parent_cover coverage` was the functional coverage for each node.

Python

Python: Using lists and tuples

It's actually some of the raw code used when researching this task.

`from itertools import zip_longest  fc2 = '''\cleaning,,    house1,40,        bedrooms,,.25        bathrooms,,            bathroom1,,.5            bathroom2,,            outside_lavatory,,1        attic,,.75        kitchen,,.1        living_rooms,,            lounge,,            dining_room,,            conservatory,,            playroom,,1        basement,,        garage,,        garden,,.8    house2,60,        upstairs,,            bedrooms,,                suite_1,,                suite_2,,                bedroom_3,,                bedroom_4,,            bathroom,,            toilet,,            attics,,.6        groundfloor,,            kitchen,,            living_rooms,,                lounge,,                dining_room,,                conservatory,,                playroom,,            wet_room_&_toilet,,            garage,,            garden,,.9            hot_tub_suite,,1        basement,,            cellars,,1            wine_cellar,,1            cinema,,.75 ''' NAME, WT, COV = 0, 1, 2 def right_type(txt):    try:        return float(txt)    except ValueError:        return txt def commas_to_list(the_list, lines, start_indent=0):    '''    Output format is a nest of lists and tuples    lists are for coverage leaves without children items in the list are name, weight, coverage    tuples are 2-tuples for nodes with children. The first element is a list representing the    name, weight, coverage of the node (some to be calculated); the second element is a list of    child elements which may be 2-tuples or lists as above.     the_list is modified in-place    lines must be a generator of successive lines of input like fc2    '''    for n, line in lines:        indent = 0        while line.startswith(' ' * (4 * indent)):            indent += 1        indent -= 1        fields = [right_type(f) for f in line.strip().split(',')]        if indent == start_indent:            the_list.append(fields)        elif indent > start_indent:            lst = [fields]            sub = commas_to_list(lst, lines, indent)            the_list[-1] = (the_list[-1], lst)            if sub not in (None, ['']) :                the_list.append(sub)        else:            return fields if fields else None    return None  def pptreefields(lst, indent=0, widths=['%-32s', '%-8g', '%-10g']):    '''    Pretty prints the format described from function commas_to_list as a table with     names in the first column suitably indented and all columns having a fixed     minimum column width.    '''    lhs = ' ' * (4 * indent)    for item in lst:        if type(item) != tuple:            name, *rest = item            print(widths[0] % (lhs + name), end='|')            for width, item in zip_longest(widths[1:len(rest)], rest, fillvalue=widths[-1]):                if type(item) == str:                    width = width[:-1] + 's'                print(width % item, end='|')            print()        else:            item, children = item            name, *rest = item            print(widths[0] % (lhs + name), end='|')            for width, item in zip_longest(widths[1:len(rest)], rest, fillvalue=widths[-1]):                if type(item) == str:                    width = width[:-1] + 's'                print(width % item, end='|')            print()            pptreefields(children, indent+1)  def default_field(node_list):    node_list[WT] = node_list[WT] if node_list[WT] else 1.0    node_list[COV] = node_list[COV] if node_list[COV] else 0.0 def depth_first(tree, visitor=default_field):    for item in tree:        if type(item) == tuple:            item, children = item            depth_first(children, visitor)        visitor(item)  def covercalc(tree):    '''    Depth first weighted average of coverage    '''    sum_covwt, sum_wt = 0, 0    for item in tree:        if type(item) == tuple:            item, children = item            item[COV] = covercalc(children)        sum_wt  += item[WT]        sum_covwt += item[COV] * item[WT]    cov = sum_covwt / sum_wt    return cov if __name__ == '__main__':            lstc = []    commas_to_list(lstc, ((n, ln) for n, ln in enumerate(fc2.split('\n'))))    #pp(lstc, width=1, indent=4, compact=1)     #print('\n\nEXPANDED DEFAULTS\n')    depth_first(lstc)    #pptreefields(['NAME_HIERARCHY WEIGHT COVERAGE'.split()] + lstc)     print('\n\nTOP COVERAGE = %f\n' % covercalc(lstc))    depth_first(lstc)    pptreefields(['NAME_HIERARCHY WEIGHT COVERAGE'.split()] + lstc)`
Output:
```TOP COVERAGE = 0.409167

NAME_HIERARCHY                  |WEIGHT  |COVERAGE  |
cleaning                        |1       |0.409167  |
house1                      |40      |0.33125   |
bedrooms                |1       |0.25      |
bathrooms               |1       |0.5       |
bathroom1           |1       |0.5       |
bathroom2           |1       |0         |
outside_lavatory    |1       |1         |
attic                   |1       |0.75      |
kitchen                 |1       |0.1       |
living_rooms            |1       |0.25      |
lounge              |1       |0         |
dining_room         |1       |0         |
conservatory        |1       |0         |
playroom            |1       |1         |
basement                |1       |0         |
garage                  |1       |0         |
garden                  |1       |0.8       |
house2                      |60      |0.461111  |
upstairs                |1       |0.15      |
bedrooms            |1       |0         |
suite_1         |1       |0         |
suite_2         |1       |0         |
bedroom_3       |1       |0         |
bedroom_4       |1       |0         |
bathroom            |1       |0         |
toilet              |1       |0         |
attics              |1       |0.6       |
groundfloor             |1       |0.316667  |
kitchen             |1       |0         |
living_rooms        |1       |0         |
lounge          |1       |0         |
dining_room     |1       |0         |
conservatory    |1       |0         |
playroom        |1       |0         |
wet_room_&_toilet   |1       |0         |
garage              |1       |0         |
garden              |1       |0.9       |
hot_tub_suite       |1       |1         |
basement                |1       |0.916667  |
cellars             |1       |1         |
wine_cellar         |1       |1         |
cinema              |1       |0.75      |```

Python: Class based and extra credit

A cleaner implementation that uses the class static variable path2node as in the previous example so you don't have to traverse the tree to work out the position to add new nodes. This relies on parent nodes appearing before their children which is the case in the order of the add_node calls.

`# -*- coding: utf-8 -*- SPACES = 4class Node:    path2node = {}     def add_node(self, pathname, wt, cov):        path2node = self.path2node        path, name = pathname.strip().rsplit('/', 1)        node = Node(name, wt, cov)        path2node[pathname] = node        path2node[path].child.append(node) # Link the tree     def __init__(self, name="", wt=1, cov=0.0, child=None):        if child is None:            child = []        self.name, self.wt, self.cov, self.child = name, wt, cov, child        self.delta = None        self.sum_wt = wt        if name == "":             # designate the top of the tree            self.path2node[name] = self      def __repr__(self, indent=0):        name, wt, cov, delta, child = (self.name, self.wt, self.cov,                                        self.delta, self.child)        lhs = ' ' * (SPACES * indent) + "Node(%r," % name        txt = '%-40s wt=%2g, cov=%-8.5g, delta=%-10s, child=[' \              % (lhs, wt, cov, ('n/a' if delta is None else '%-10.7f' % delta))        if not child:            txt += (']),\n')        else:            txt += ('\n')            for c in child:                txt += c.__repr__(indent + 1)            txt += (' ' * (SPACES * indent) + "]),\n")        return txt     def covercalc(self):        '''        Depth first weighted average of coverage        '''        child = self.child        if not child:            return self.cov        sum_covwt, sum_wt = 0, 0        for node in child:            nwt = node.wt            ncov = node.covercalc()            sum_wt += nwt            sum_covwt += ncov * nwt        cov = sum_covwt / sum_wt        self.sum_wt = sum_wt        self.cov = cov        return cov     def deltacalc(self, power=1.0):        '''        Top down distribution of weighted residuals        '''        sum_wt = self.sum_wt        self.delta = delta = (1 - self.cov) * power        for node in self.child:            node.deltacalc(power * node.wt / sum_wt)        return delta  def isclose(a, b, rel_tol=1e-9, abs_tol=1e-9):    return abs(a-b) <= max( rel_tol * max(abs(a), abs(b)), abs_tol )  if __name__ == '__main__':     top = Node()    # Add placeholder for top of tree    add_node = top.add_node     add_node('/cleaning', 1, 0)    add_node('/cleaning/house1', 40, 0)    add_node('/cleaning/house1/bedrooms', 1, 0.25)    add_node('/cleaning/house1/bathrooms', 1, 0)    add_node('/cleaning/house1/bathrooms/bathroom1', 1, 0.5)    add_node('/cleaning/house1/bathrooms/bathroom2', 1, 0)    add_node('/cleaning/house1/bathrooms/outside_lavatory', 1, 1)    add_node('/cleaning/house1/attic', 1, 0.75)    add_node('/cleaning/house1/kitchen', 1, 0.1)    add_node('/cleaning/house1/living_rooms', 1, 0)    add_node('/cleaning/house1/living_rooms/lounge', 1, 0)    add_node('/cleaning/house1/living_rooms/dining_room', 1, 0)    add_node('/cleaning/house1/living_rooms/conservatory', 1, 0)    add_node('/cleaning/house1/living_rooms/playroom', 1, 1)    add_node('/cleaning/house1/basement', 1, 0)    add_node('/cleaning/house1/garage', 1, 0)    add_node('/cleaning/house1/garden', 1, 0.8)    add_node('/cleaning/house2', 60, 0)    add_node('/cleaning/house2/upstairs', 1, 0)    add_node('/cleaning/house2/upstairs/bedrooms', 1, 0)    add_node('/cleaning/house2/upstairs/bedrooms/suite_1', 1, 0)    add_node('/cleaning/house2/upstairs/bedrooms/suite_2', 1, 0)    add_node('/cleaning/house2/upstairs/bedrooms/bedroom_3', 1, 0)    add_node('/cleaning/house2/upstairs/bedrooms/bedroom_4', 1, 0)    add_node('/cleaning/house2/upstairs/bathroom', 1, 0)    add_node('/cleaning/house2/upstairs/toilet', 1, 0)    add_node('/cleaning/house2/upstairs/attics', 1, 0.6)    add_node('/cleaning/house2/groundfloor', 1, 0)    add_node('/cleaning/house2/groundfloor/kitchen', 1, 0)    add_node('/cleaning/house2/groundfloor/living_rooms', 1, 0)    add_node('/cleaning/house2/groundfloor/living_rooms/lounge', 1, 0)    add_node('/cleaning/house2/groundfloor/living_rooms/dining_room', 1, 0)    add_node('/cleaning/house2/groundfloor/living_rooms/conservatory', 1, 0)    add_node('/cleaning/house2/groundfloor/living_rooms/playroom', 1, 0)    add_node('/cleaning/house2/groundfloor/wet_room_&_toilet', 1, 0)    add_node('/cleaning/house2/groundfloor/garage', 1, 0)    add_node('/cleaning/house2/groundfloor/garden', 1, 0.9)    add_node('/cleaning/house2/groundfloor/hot_tub_suite', 1, 1)    add_node('/cleaning/house2/basement', 1, 0)    add_node('/cleaning/house2/basement/cellars', 1, 1)    add_node('/cleaning/house2/basement/wine_cellar', 1, 1)    add_node('/cleaning/house2/basement/cinema', 1, 0.75)     top = top.child[0]  # Remove artificial top    cover = top.covercalc()    delta = top.deltacalc()    print('TOP COVERAGE = %g\n' % cover)    print(top)    assert isclose((delta + cover), 1.0), "Top level delta + coverage should " \                                          "equal 1 instead of (%f + %f)" % (delta, cover) `
Output:

The deltas where checked by, for example, changing the coverage of the cinema in house2 to be 1.0 instead of 0.75 and observing an additional 0.0166667 increase in the top level coverage at node 'cleaning'.

```TOP COVERAGE = 0.409167

Node('cleaning',                         wt= 1, cov=0.40917 , delta=0.5908333 , child=[
Node('house1',                       wt=40, cov=0.33125 , delta=0.2675000 , child=[
Node('bedrooms',                 wt= 1, cov=0.25    , delta=0.0375000 , child=[]),
Node('bathrooms',                wt= 1, cov=0.5     , delta=0.0250000 , child=[
Node('bathroom1',            wt= 1, cov=0.5     , delta=0.0083333 , child=[]),
Node('bathroom2',            wt= 1, cov=0       , delta=0.0166667 , child=[]),
Node('outside_lavatory',     wt= 1, cov=1       , delta=0.0000000 , child=[]),
]),
Node('attic',                    wt= 1, cov=0.75    , delta=0.0125000 , child=[]),
Node('kitchen',                  wt= 1, cov=0.1     , delta=0.0450000 , child=[]),
Node('living_rooms',             wt= 1, cov=0.25    , delta=0.0375000 , child=[
Node('lounge',               wt= 1, cov=0       , delta=0.0125000 , child=[]),
Node('dining_room',          wt= 1, cov=0       , delta=0.0125000 , child=[]),
Node('conservatory',         wt= 1, cov=0       , delta=0.0125000 , child=[]),
Node('playroom',             wt= 1, cov=1       , delta=0.0000000 , child=[]),
]),
Node('basement',                 wt= 1, cov=0       , delta=0.0500000 , child=[]),
Node('garage',                   wt= 1, cov=0       , delta=0.0500000 , child=[]),
Node('garden',                   wt= 1, cov=0.8     , delta=0.0100000 , child=[]),
]),
Node('house2',                       wt=60, cov=0.46111 , delta=0.3233333 , child=[
Node('upstairs',                 wt= 1, cov=0.15    , delta=0.1700000 , child=[
Node('bedrooms',             wt= 1, cov=0       , delta=0.0500000 , child=[
Node('suite_1',          wt= 1, cov=0       , delta=0.0125000 , child=[]),
Node('suite_2',          wt= 1, cov=0       , delta=0.0125000 , child=[]),
Node('bedroom_3',        wt= 1, cov=0       , delta=0.0125000 , child=[]),
Node('bedroom_4',        wt= 1, cov=0       , delta=0.0125000 , child=[]),
]),
Node('bathroom',             wt= 1, cov=0       , delta=0.0500000 , child=[]),
Node('toilet',               wt= 1, cov=0       , delta=0.0500000 , child=[]),
Node('attics',               wt= 1, cov=0.6     , delta=0.0200000 , child=[]),
]),
Node('groundfloor',              wt= 1, cov=0.31667 , delta=0.1366667 , child=[
Node('kitchen',              wt= 1, cov=0       , delta=0.0333333 , child=[]),
Node('living_rooms',         wt= 1, cov=0       , delta=0.0333333 , child=[
Node('lounge',           wt= 1, cov=0       , delta=0.0083333 , child=[]),
Node('dining_room',      wt= 1, cov=0       , delta=0.0083333 , child=[]),
Node('conservatory',     wt= 1, cov=0       , delta=0.0083333 , child=[]),
Node('playroom',         wt= 1, cov=0       , delta=0.0083333 , child=[]),
]),
Node('wet_room_&_toilet',    wt= 1, cov=0       , delta=0.0333333 , child=[]),
Node('garage',               wt= 1, cov=0       , delta=0.0333333 , child=[]),
Node('garden',               wt= 1, cov=0.9     , delta=0.0033333 , child=[]),
Node('hot_tub_suite',        wt= 1, cov=1       , delta=0.0000000 , child=[]),
]),
Node('basement',                 wt= 1, cov=0.91667 , delta=0.0166667 , child=[
Node('cellars',              wt= 1, cov=1       , delta=0.0000000 , child=[]),
Node('wine_cellar',          wt= 1, cov=1       , delta=0.0000000 , child=[]),
Node('cinema',               wt= 1, cov=0.75    , delta=0.0166667 , child=[]),
]),
]),
]),```

Racket

To save on paper, the coverage table needs to be saved to a file (in this case `data/functional-coverage.txt`).

`#lang racket/base(require racket/list racket/string racket/match racket/format racket/file) (struct Coverage (name weight coverage weighted-coverage children) #:transparent #:mutable) ;; -| read/parse |------------------------------------------------------------------------------------(define (build-hierarchies parsed-lines)  (define inr    (match-lambda      ['() (values null null)]      [`((,head-indent . ,C) ,tail-lines ...)       (define child? (match-lambda [(cons i _) #:when (> i head-indent) #t] [_ #f]))       (define-values (chlds rels) (splitf-at tail-lines child?))       (define-values (rels-tree rels-rem) (inr rels))       (values (cons (struct-copy Coverage C (children (build-hierarchies chlds))) rels-tree)               rels-rem)]))  (define-values (hierarchies remaining-lines) (inr parsed-lines))  hierarchies) (define report-line->indent.c/e-line  (match-lambda    [(regexp #px"^( *)([^ ]*) *\\| *([^ ]*) *\\| *([^ ]*) *\\|\$"             (list _                   (app string-length indent-length)                   name                   (or (and (not "") (app string->number wght)) (app (λ (x) 1) wght))                   (or (and (not "") (app string->number cvrg)) (app (λ (x) 0) cvrg))))     (cons indent-length (Coverage name wght cvrg 0 #f))])) (define (report->indent.c/e-list rprt)  (map report-line->indent.c/e-line (drop (string-split rprt "\n") 1))) ;; -| evaluate |--------------------------------------------------------------------------------------(define find-wght-cvrg  (match-lambda    [(and e (Coverage _ w c _ '())) (struct-copy Coverage e (weighted-coverage (* w c)))]    [(and e (Coverage _ _ _ _ `(,(app find-wght-cvrg (and cdn+ (Coverage _ c-ws _ c-w/cs _))) ...)))     (define chld-wghtd-avg (for/sum ((w (in-list c-ws)) (w/c (in-list c-w/cs))) (* w w/c)))     (struct-copy Coverage e (weighted-coverage (/ chld-wghtd-avg (apply + c-ws))) (children cdn+))])) ;; -| printing |--------------------------------------------------------------------------------------(define max-description-length  (match-lambda    [(Coverage (app string-length name-length) _ _ _               (list (app max-description-length children-lengths) ...))     (apply max name-length (map add1 children-lengths))])) (define (~a/right w x)  (~a x #:width w #:align 'right)) (define (~a/decimal n dec-dgts)  (~a/right (+ dec-dgts 3) (if (zero? n) "" (real->decimal-string n dec-dgts)))) (define (print-coverage-tree tree)  (define mdl (max-description-length tree))  (printf "| ~a |WEIGT| COVER |WGHTD CVRG|~%" (~a "NAME" #:width mdl #:align 'center))  (let inr ((depth 0) (tree tree))    (unless (null? tree)      (match tree        [(Coverage name w c w/c chlds)         (printf "| ~a | ~a | ~a | ~a |~%"                 (~a (string-append (make-string depth #\space) name) #:width mdl)                 (~a/right 3 w) (~a/decimal c 2) (~a/decimal w/c 5))         (for ((c chlds)) (inr (add1 depth) c))])))) ;; ---------------------------------------------------------------------------------------------------(module+ main;; data/functional-coverage.txt contains a verbatim copy of;; the table in the task's description(for-each (compose print-coverage-tree find-wght-cvrg) (build-hierarchies (report->indent.c/e-list (file->string "data/functional-coverage.txt")))))`
Output:
```|         NAME         |WEIGT| COVER |WGHTD CVRG|
| cleaning             |   1 |       |  0.40917 |
|  house1              |  40 |       |  0.33125 |
|   bedrooms           |   1 |  0.25 |  0.25000 |
|   bathrooms          |   1 |       |  0.50000 |
|    bathroom1         |   1 |  0.50 |  0.50000 |
|    bathroom2         |   1 |       |          |
|    outside_lavatory  |   1 |  1.00 |  1.00000 |
|   attic              |   1 |  0.75 |  0.75000 |
|   kitchen            |   1 |  0.10 |  0.10000 |
|   living_rooms       |   1 |       |  0.25000 |
|    lounge            |   1 |       |          |
|    dining_room       |   1 |       |          |
|    conservatory      |   1 |       |          |
|    playroom          |   1 |  1.00 |  1.00000 |
|   basement           |   1 |       |          |
|   garage             |   1 |       |          |
|   garden             |   1 |  0.80 |  0.80000 |
|  house2              |  60 |       |  0.46111 |
|   upstairs           |   1 |       |  0.15000 |
|    bedrooms          |   1 |       |          |
|     suite_1          |   1 |       |          |
|     suite_2          |   1 |       |          |
|     bedroom_3        |   1 |       |          |
|     bedroom_4        |   1 |       |          |
|    bathroom          |   1 |       |          |
|    toilet            |   1 |       |          |
|    attics            |   1 |  0.60 |  0.60000 |
|   groundfloor        |   1 |       |  0.31667 |
|    kitchen           |   1 |       |          |
|    living_rooms      |   1 |       |          |
|     lounge           |   1 |       |          |
|     dining_room      |   1 |       |          |
|     conservatory     |   1 |       |          |
|     playroom         |   1 |       |          |
|    wet_room_&_toilet |   1 |       |          |
|    garage            |   1 |       |          |
|    garden            |   1 |  0.90 |  0.90000 |
|    hot_tub_suite     |   1 |  1.00 |  1.00000 |
|   basement           |   1 |       |  0.91667 |
|    cellars           |   1 |  1.00 |  1.00000 |
|    wine_cellar       |   1 |  1.00 |  1.00000 |
|    cinema            |   1 |  0.75 |  0.75000 |```