1

I have a dictionary of dictionaries. Within these subdictionaries, I have two keys - ui_section and section_order - that determine if the value of this subdictionary is shown in a specfic portion of the UI and if so, what order it appears. My dictionary looks like this:

MASTER_DICT = {
    'key1': {'ui_section':[1,2],'section_order':1, 'value': 'key1'},
    'key2': {'ui_section':[1],'section_order':2, 'value': 'key2'},
    'key3': {'ui_section':[1,2],'section_order':3, 'value': 'key3'},
    'key4': {'ui_section':[1],'section_order':4, 'value': 'key4'},
    'key5': {'ui_section':[1],'section_order':5, 'value': 'key5'},
    'key6': {'ui_section':[1],'section_order':6, 'value': 'key6'},
    'key7': {'ui_section':[1],'section_order':7, 'value': 'key7'},
    'key8': {'ui_section':[1],'section_order':8, 'value': 'key8'},
    'key9': {'ui_section':[1],'section_order':9, 'value': 'key9'},
}

The ui_section is a list of possible sections the key can appear in. I determine this via the following code:

def show_section_ui(master_dict, section=None):
    if section:
        ui_sections = []
        # Find the keys that are part of this section
        for k in master_dict.keys():
            try:
                if section in master_dict[k]['ui_section']:
                    ui_sections.append(master_dict[k])
            except AttributeError:
                pass
        # Order the keys by sort order
        ui_sections.sort(key=lambda x: x['section_order'])

        return ui_sections
    else:
        return None

This portion of the code works. The output below shows that order is correct for both sections 1 and 2.

>>> pprint.pprint(show_section_ui(MASTER_DICT, 1))
[{'section_order': 1, 'ui_section': [1,2], 'value': 'key1'},
 {'section_order': 2, 'ui_section': [1], 'value': 'key2'},
 {'section_order': 3, 'ui_section': [1,2], 'value': 'key3'},
 {'section_order': 4, 'ui_section': [1], 'value': 'key4'},
 {'section_order': 5, 'ui_section': [1], 'value': 'key5'},
 {'section_order': 6, 'ui_section': [1], 'value': 'key6'},
 {'section_order': 7, 'ui_section': [1], 'value': 'key7'},
 {'section_order': 8, 'ui_section': [1], 'value': 'key8'},
 {'section_order': 9, 'ui_section': [1], 'value': 'key9'}]


 >>> pprint.pprint(show_section_ui(MASTER_DICT, 2))
 [{'section_order': 1, 'ui_section': [1,2], 'value': 'key1'},
  {'section_order': 3, 'ui_section': [1,2], 'value': 'key3'}]

My problem is that the section_order needs to have sort orders per ui_section. For example, in the above outputs, in section 2, I'd like key3 to be first. My initial thought was to make section_order a list as well. But, I'm not sure how to adjust this line to properly account for the list (and to select the correct index to sort by then)

ui_sections.sort(key=lambda x: x['section_order'])

My intention was to do something like this:

MASTER_DICT = {
    'key1': {'ui_section':[1,2],'section_order':[1,2], 'value': 'key1'},
    'key2': {'ui_section':[1],'section_order':[2], 'value': 'key2'},
    'key3': {'ui_section':[1,2],'section_order':[3,1], 'value': 'key3'},
}

Getting me output like this:

 >>> pprint.pprint(show_section_ui(MASTER_DICT, 2))
 [{'section_order': [3,1], 'ui_section': [1,2], 'value': 'key3'},
 {'section_order': [1,2], 'ui_section': [1,2], 'value': 'key1'}]

How can I sort by the ui_section and the appropriate index within the key?

NewGuy
  • 3,273
  • 7
  • 43
  • 61

2 Answers2

2

I think I understood. If you want to sort a list of items according to the order of another list of items, you could do something like this. Step by step, the last line is what need.

We need itertools.count(), which is an infinite incrementing range, used to apply an index.

import itertools

these should be sorted by 'value'

>>> values = [{"name": "A", "value": 10},
              {"name": "B", "value": 8},
              {"name": "C", "value": 9}]

these are in the same input order as values, and should be sorted in the the same order

>>> to_sort = [{"name": "A", "payload": "aaa"},
               {"name": "B", "payload": "bbb"},
               {"name": "C", "payload": "ccc"}]

Zip the values with their indexes. This annotates each item to include its original order. This is a list of pairs of (object, index)

>>> zip(values, itertools.count())

[({'name': 'A', 'value': 10}, 0),
 ({'name': 'B', 'value': 8}, 1),
 ({'name': 'C', 'value': 9}, 2)]

Now sort by the key 'value'. The x[0] is to get the first item of the pair (the object).

>>> sorted(zip(values, itertools.count()), key=lambda x: x[0]["value"])
[({'name': 'B', 'value': 8}, 1),
 ({'name': 'C', 'value': 9}, 2),
 ({'name': 'A', 'value': 10}, 0)]

Now retrieve the indexes from the pairs to return the new order of the original indexes.

>>> map(lambda x: x[1],
        sorted(zip(values, itertools.count()),
               key=lambda x: x[0]["value"]))
[1, 2, 0]

Now use those indexes to map over the to_sort list and retrieve the items at those indexes.

>>> map(lambda i: to_sort[i],
        map(lambda x: x[1],
            sorted(zip(values, itertools.count()),
                   key=lambda x: x[0]["value"])))

[{'name': 'B', 'payload': 'bbb'}, 
 {'name': 'C', 'payload': 'ccc'},
 {'name': 'A', 'payload': 'aaa'}]

I hope that answers your question. It means changing MASTER_DICT to be a list, but I think that's the best representation for it anyway.

Joe
  • 46,419
  • 33
  • 155
  • 245
1

I don't have a nice one line code change for you, but you can replace the line you have:

ui_sections.sort(key=lambda x: x['section_order'])

With this:

sort_orders = []
for s in ui_sections:
    ndx = s['ui_section'].index(section)
    # This next line makes the assumption that you ALWAYS have a section_order
    # for every ui_section listed. If not, you'll get an IndexError
    sort_orders.append(s['section_order'][ndx])

    # Magic happens here        
    sorted_sections = [x for y, x in sorted(zip(sort_orders,ui_sections))]
    return sorted_sections

Output:

>>> pprint.pprint(show_section_ui(MASTER_DICT, 2))
[{'section_order': [3, 1], 'ui_section': [1, 2], 'value': 'key3'},
 {'section_order': [1, 2], 'ui_section': [1, 2], 'value': 'key1'}]

>>> pprint.pprint(show_section_ui(MASTER_DICT, 1))
[{'section_order': [1, 2], 'ui_section': [1, 2], 'value': 'key1'},
 {'section_order': [2], 'ui_section': [1], 'value': 'key2'},
 {'section_order': [3, 1], 'ui_section': [1, 2], 'value': 'key3'},
 {'section_order': [4], 'ui_section': [1], 'value': 'key4'},
 {'section_order': [5], 'ui_section': [1], 'value': 'key5'},
 {'section_order': [6], 'ui_section': [1], 'value': 'key6'},
 {'section_order': [7], 'ui_section': [1], 'value': 'key7'},
 {'section_order': [8], 'ui_section': [1], 'value': 'key8'},
 {'section_order': [9], 'ui_section': [1], 'value': 'key9'}]

Adding key8 to the second ui_section at position 3, and key7 at position 4:

[{'section_order': [3, 1], 'ui_section': [1, 2], 'value': 'key3'},
 {'section_order': [1, 2], 'ui_section': [1, 2], 'value': 'key1'},
 {'section_order': [8, 3], 'ui_section': [1, 2], 'value': 'key8'},
 {'section_order': [7, 4], 'ui_section': [1, 2], 'value': 'key7'}]

This is utilizing this answer. First, though, it finds the index that the section is listed in the ui_section:

ndx = s['ui_section'].index(section)

The value at this location is then added to the sort_orders list. Note that the code provided does not error check that this is valid (ie. if you don't have a value for the second position), and will throw an IndexError if it is not.

sort_orders.append(s['section_order'][ndx])

Next it zips the two lists together so that you have a list of tuples containing the sort order and the section dictionary.

[(3, {'ui_section': [1, 2], 'section_order': [8, 3], 'value': 'key8'}), 
 (1, {'ui_section': [1, 2], 'section_order': [3, 1], 'value': 'key3'}), 
 (2, {'ui_section': [1, 2], 'section_order': [1, 2], 'value': 'key1'}), 
 (4, {'ui_section': [1, 2], 'section_order': [7, 4], 'value': 'key7'})
]

Then we sort that based on the first position in the tuple. Then we unzip it and pull back the sorted information. All of that occurs in this line:

sorted_sections = [x for y, x in sorted(zip(sort_orders,ui_sections))] 
Community
  • 1
  • 1
Andy
  • 49,085
  • 60
  • 166
  • 233