4
\$\begingroup\$

Given a list of nested menu items,

items = [
    '3D/Axis',
    '3D/CameraTracker',

    'Color/Invert',
    'Color/Log2Lin',

    'Color/Math/Add',
    'Color/Math/Multiply',

    'Other/Group',
    'Other/NoOp',

    'Views/JoinViews',
    'Views/ShuffleViews',

    'Views/Stereo/Anaglyph',
    'Views/Stereo/ReConverge',
]

..and a dummy function to be triggered by each item:

def msg(path):
    """Dummy function used in menu
    """
    return path.lower()

Programatically create a hierarchal collection of dictionaries, which is equivalent to manually typing this:

menu = {
    '3D': {'Axis':                     lambda: msg('3D/Axis'),
           'CameraTracker':            lambda: msg("3D/CameraTracker")},

    'Color': {'Invert':                lambda: msg('Color/Invert'),
              'Log2Lin':               lambda: msg('Color/Log2Lin'),

              'Math': {'Add':          lambda: msg('Color/Add'),
                       'Multiply':     lambda: msg('Color/Multiply')}},

    'Other': {'Group':                 lambda: msg('Other/Group'),
              'NoOp':                  lambda: msg('Other/NoOp')},

    'Views': {'JoinViews':             lambda: msg('Views/JoinViews'),
              'ShuffleViews':          lambda: msg('Views/ShuffleViews'),
              'Stereo': {'Anaglyph':   lambda: msg('Views/Stereo/Anaglyph'),
                         'ReConverge': lambda: msg('Views/Stereo/ReConverge')}}},

..which could be tested as follows:

assert menu['3D']['Axis']() == '3d/axis'
assert menu['Color']['Invert']() == 'color/invert'
assert menu['Color']['Math']['Add']() == 'color/math/add'
assert menu['Views']['Stereo']['Anaglyph']() == 'views/stereo/anaglyph'
\$\endgroup\$
  • 2
    \$\begingroup\$ Code golf questions should go to Code Golf, it was decided that they don't belong here anymore (AFAIK). \$\endgroup\$ – Jeff Mercado Jan 1 '12 at 7:51
  • \$\begingroup\$ @dbr If you'd like this migrated, go ahead and flag for it. \$\endgroup\$ – Tim Post Jan 2 '12 at 4:08
  • 2
    \$\begingroup\$ ...except we usually don't take gratuitously language-specific questions here :-/ \$\endgroup\$ – J B Jan 2 '12 at 13:03
2
\$\begingroup\$

A simple recursion will do the work

def build_nested_helper(path, text, container):
    segs = path.split('/')
    head = segs[0]
    tail = segs[1:]
    if not tail:
        container[head] = lambda: msg(text)
    else:
        if head not in container:
            container[head] = {}
        build_nested_helper('/'.join(tail), text, container[head])

def build_nested(paths):
    container = {}
    for path in paths:
        build_nested_helper(path, path, container)
    return container

menu = build_nested(items)

squashed code: 159 chars

def f(p,t,c):
 s=p.split('/');a=s[0];b=s[1:]
 if b:
  if a not in c:c[a]={}
  f('/'.join(b),t,c[a])
 else:c[a]=lambda:msg(t)
menu={}
for i in items:f(i,i,menu)
\$\endgroup\$
  • 3
    \$\begingroup\$ squash s=p.split('/');a=s[0];b=s[1:] to a,b=p.split('/',1) \$\endgroup\$ – eumiro Jan 1 '12 at 10:54
  • \$\begingroup\$ @eumiro nice one! didn't know the second argument before. \$\endgroup\$ – qiao Jan 1 '12 at 13:01
0
\$\begingroup\$

As a starting point,

def populate_dict(item, existing_dict, fullpath = None):
    if len(item) == 1:
        existing_dict[item[0]] = lambda p=item[0]: msg(fullpath + "/" + item[0])
    else:
        head, tail = item[0], item[1:]
        existing_dict.setdefault(head, {})
        populate_dict(
            tail,
            existing_dict[head],
            fullpath = "/".join([x for x in (fullpath, head) if x is not None]))

menu = {}
for i in items:
    populate_dict(i.split("/"), menu)

..which can be squashed down to:

def p(i,e,fp=""):
 if len(i)<2:e[i[0]]=lambda p=i[0]:msg(fp+"/"+i[0])
 else:p(i[1:], e.setdefault(i[0], {}),"/".join([fp, i[0]]).lstrip("/"))
menu = {}
for i in items:p(i.split("/"),menu)
\$\endgroup\$
0
\$\begingroup\$
#!/usr/bin/env python3

from itertools import groupby
from pprint import pprint

items = [
    '3D/Axis',
    '3D/CameraTracker',

    'Color/Invert',
    'Color/Log2Lin',

    'Color/Math/Add',
    'Color/Math/Multiply',

    'Other/Group',
    'Other/NoOp',

    'Views/JoinViews',
    'Views/ShuffleViews',

    'Views/Stereo/Anaglyph',
    'Views/Stereo/ReConverge',
]

def fun(group, items, path):
    sep = lambda i:i.split('/', 1)
    head = [i for i in items if len(sep(i))==2]
    tail = [i for i in items if len(sep(i))==1]
    gv = groupby(sorted(head), lambda i:sep(i)[0])
    return group, dict([(i, path+i) for i in tail] + [fun(g, [sep(i)[1] for i in v], path+g+'/') for g,v in gv])

menu = dict([fun('menu', items, '')])['menu']
pprint(menu)

{'3D': {'Axis': '3D/Axis', 'CameraTracker': '3D/CameraTracker'},
 'Color': {'Invert': 'Color/Invert',
           'Log2Lin': 'Color/Log2Lin',
           'Math': {'Add': 'Color/Math/Add',
                    'Multiply': 'Color/Math/Multiply'}},
 'Other': {'Group': 'Other/Group', 'NoOp': 'Other/NoOp'},
 'Views': {'JoinViews': 'Views/JoinViews',
           'ShuffleViews': 'Views/ShuffleViews',
           'Stereo': {'Anaglyph': 'Views/Stereo/Anaglyph',
                      'ReConverge': 'Views/Stereo/ReConverge'}}}

fun() takes 6 lines
Just change path+i to (path+i).lower() to get what you want.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.