xref: /aosp_15_r20/external/fonttools/Lib/fontTools/misc/treeTools.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes"""Generic tools for working with trees."""
2*e1fe3e4aSElliott Hughes
3*e1fe3e4aSElliott Hughesfrom math import ceil, log
4*e1fe3e4aSElliott Hughes
5*e1fe3e4aSElliott Hughes
6*e1fe3e4aSElliott Hughesdef build_n_ary_tree(leaves, n):
7*e1fe3e4aSElliott Hughes    """Build N-ary tree from sequence of leaf nodes.
8*e1fe3e4aSElliott Hughes
9*e1fe3e4aSElliott Hughes    Return a list of lists where each non-leaf node is a list containing
10*e1fe3e4aSElliott Hughes    max n nodes.
11*e1fe3e4aSElliott Hughes    """
12*e1fe3e4aSElliott Hughes    if not leaves:
13*e1fe3e4aSElliott Hughes        return []
14*e1fe3e4aSElliott Hughes
15*e1fe3e4aSElliott Hughes    assert n > 1
16*e1fe3e4aSElliott Hughes
17*e1fe3e4aSElliott Hughes    depth = ceil(log(len(leaves), n))
18*e1fe3e4aSElliott Hughes
19*e1fe3e4aSElliott Hughes    if depth <= 1:
20*e1fe3e4aSElliott Hughes        return list(leaves)
21*e1fe3e4aSElliott Hughes
22*e1fe3e4aSElliott Hughes    # Fully populate complete subtrees of root until we have enough leaves left
23*e1fe3e4aSElliott Hughes    root = []
24*e1fe3e4aSElliott Hughes    unassigned = None
25*e1fe3e4aSElliott Hughes    full_step = n ** (depth - 1)
26*e1fe3e4aSElliott Hughes    for i in range(0, len(leaves), full_step):
27*e1fe3e4aSElliott Hughes        subtree = leaves[i : i + full_step]
28*e1fe3e4aSElliott Hughes        if len(subtree) < full_step:
29*e1fe3e4aSElliott Hughes            unassigned = subtree
30*e1fe3e4aSElliott Hughes            break
31*e1fe3e4aSElliott Hughes        while len(subtree) > n:
32*e1fe3e4aSElliott Hughes            subtree = [subtree[k : k + n] for k in range(0, len(subtree), n)]
33*e1fe3e4aSElliott Hughes        root.append(subtree)
34*e1fe3e4aSElliott Hughes
35*e1fe3e4aSElliott Hughes    if unassigned:
36*e1fe3e4aSElliott Hughes        # Recurse to fill the last subtree, which is the only partially populated one
37*e1fe3e4aSElliott Hughes        subtree = build_n_ary_tree(unassigned, n)
38*e1fe3e4aSElliott Hughes        if len(subtree) <= n - len(root):
39*e1fe3e4aSElliott Hughes            # replace last subtree with its children if they can still fit
40*e1fe3e4aSElliott Hughes            root.extend(subtree)
41*e1fe3e4aSElliott Hughes        else:
42*e1fe3e4aSElliott Hughes            root.append(subtree)
43*e1fe3e4aSElliott Hughes        assert len(root) <= n
44*e1fe3e4aSElliott Hughes
45*e1fe3e4aSElliott Hughes    return root
46