views:

74

answers:

3

Hi,

To generate a Table of Content, I have these data available in a Python list:

data = [
    {title: 'Section 1', level: 1, page_number: 1},
    {title: 'Section 1.1', level: 2, page_number: 2},
    {title: 'Section 1.2', level: 2, page_number: 3},
    {title: 'Section 2', level: 1, page_number: 4},
    {title: 'Section 2.1', level: 2, page_number: 5},
    {title: 'Section 3', level: 1, page_number: 6},
]

From this, I'd like to obtain this kind of nested structure, much more compatible with the use of a template engine:

toc = [
    {title: 'Section 1', page_number: 1, sub: [
        {title: 'Section 1.1', page_number: 2, sub: []},
        {title: 'Section 1.2', page_number: 3, sub: []},
    ]},
    {title: 'Section 2', page_number: 4, sub: [
        {title: 'Section 2.1', page_number: 5, sub: []},    
    ]},
    {title: 'Section 3', page_number: 6, sub: []},
]

Hints on how to achieve this? I tried with a recursive function but it's getting much tricky for my limited brain.

Any help much appreciated.

EDIT: Added fact that a section entry can have eventually no child. Sorry for the miss.

+4  A: 

Assuming chapters come in order, meaning child chapter is always after the parent, and there are no missing parent (skipped levels):

import pprint

data = [
    {'title': 'Section 1', 'level': 1, 'page_number': 1},
    {'title': 'Section 1.1', 'level': 2, 'page_number': 2},
    {'title': 'Section 1.2', 'level': 2, 'page_number': 3},
    {'title': 'Section 2', 'level': 1, 'page_number': 4},
    {'title': 'Section 2.1', 'level': 2, 'page_number': 42},
    {'title': 'Section 2.1.1', 'level': 3, 'page_number': 42},
    {'title': 'Section 3', 'level': 1, 'page_number': 42},
]

toc = []
stack = [toc]
for d in data:
    d['sub'] = []   
    while d['level'] < len(stack):
        stack.pop()
    while d['level']  > len(stack):
        stack.append(stack[-1][-1]['sub'])
    stack[-1].append(d)


pprint.pprint(toc)

Result:

[{'level': 1,
  'page_number': 1,
  'sub': [{'level': 2, 'page_number': 2, 'sub': [], 'title': 'Section 1.1'},
          {'level': 2, 'page_number': 3, 'sub': [], 'title': 'Section 1.2'}],
  'title': 'Section 1'},
 {'level': 1,
  'page_number': 4,
  'sub': [{'level': 2,
           'page_number': 42,
           'sub': [{'level': 3,
                    'page_number': 42,
                    'sub': [],
                    'title': 'Section 2.1.1'}],
           'title': 'Section 2.1'}],
  'title': 'Section 2'},
 {'level': 1, 'page_number': 42, 'sub': [], 'title': 'Section 3'}]

EDIT: changed it to have empty 'sub' items where there are no children. See the other variant in edit history.

unbeli
Ah, I should have specified in the statement that sections can have no child as well... but great stuff here, food for thought for sure
NiKo
Hey, you're fast. Okay so it works as expected now, many thanks :)
NiKo
@NiKo the difference is not that a section can have no children (that was clear), but rather if you want to have an empty 'sub' entry in that case. My first code didn't put empty 'sub's, this one does.
unbeli
A: 

You could walk through each entry in your list and build a new list from there. Whenever there is a "Section x.y", you'll add it under sub.

newData = []
curParent = None
for d in data:
  # child
  if d['title'].find('.') > 0:
    assert curParent # Make sure we have a valid parent dictionary
    curParent['sub'].append({'title': d['title'], 'page_number': d['page_number'])
  # parent
  else:
    curParent = {'title': d['title'], 'page_number': d['page_number'], 'sub': []}
    newData.append(curParent)

That should work for 2 or 3 levels, if you need many more than a different approach might be better. Also, the find('.') might not work with other titles, but either use the level field (which seems redundant in your example) or regular expressions.

tobiw
Yes, I can have many nested levels. Plus I don't really want to rely on the presence of a dot in the section title ;)
NiKo
Downvoting every answer that doesn't fit perfectly isn't very nice. It's not like I deliberately wrote something wrong, it was just another approach than the other ones.
tobiw
+2  A: 

Does this do what you want?

TITLE, LEVEL, PAGE_NUMBER, SUB = 'title', 'level', 'page_number', 'sub'
data = [
    {TITLE: 'Section 1', LEVEL: 1, PAGE_NUMBER: 1},
    {TITLE: 'Section 1.1', LEVEL: 2, PAGE_NUMBER: 2},
    {TITLE: 'Section 1.1.1', LEVEL: 3, PAGE_NUMBER: 2},
    {TITLE: 'Section 1.2', LEVEL: 2, PAGE_NUMBER: 3},
    {TITLE: 'Section 2', LEVEL: 1, PAGE_NUMBER: 4},
    {TITLE: 'Section 2.1', LEVEL: 2, PAGE_NUMBER: 5},
]

levels = [ { SUB: [] } ]
for section in data:
    section = dict(section)
    current = section[LEVEL]
    section[SUB] = []
    levels[current-1][SUB].append(section)
    del levels[current:]
    levels.append(section)

toc = levels[0][SUB]
from pprint import pprint
pprint(toc)
Duncan
very good, might be even better than mine with a stack.
unbeli
It's still a stack, just looked at slightly differently. I think the main difference is that you only save the sub list in each level where I saved the whole section. Whether that matters might be an interesting question.
Duncan