BioGeek
BioGeek

Reputation: 22827

Sorting grouped objects

I have a list of objects. Each object has two attributes: DispName and MachID. DispName can either start with theoretical or be something else.

I need to sort this list in the following way:

This is the code I have now, which works and produces the required output, but I was wondering if I could write this more pythonic, maybe making use of groupby? (My excuses for the camelCasing).

from collections import defaultdict, namedtuple
from operator import attrgetter

Mapping = namedtuple('Mapping', ['DispName', 'MachID'])

objectList = [Mapping('map 2 (MT1)', 'MT1'),
          Mapping('theoretical (MT1)', 'MT1'),
          Mapping('map 3 (MT2)', 'MT2'),
          Mapping('theoretical (MT2)', 'MT2'),
          Mapping('map 1 (MT1)', 'MT1'),
          Mapping('map 2 (MT2)', 'MT2')]

def complexSort(objectList):
    objectDict = defaultdict(list)
    sortedMappingList = []
    # group by machine ID 
    for obj in objectList:
        objectDict[obj.MachID].append(obj)
    # loop over the mappings sorted alphabetically by machine ID
    for machID in sorted(objectDict.keys()):
        mappings = objectDict[machID]
        nonTheoreticalMappings = []
        for mapping in mappings:
            if mapping.DispName.startswith('theoretical'):
                # if we encounter the theoretical mapping, add it first
                sortedMappingList.append(mapping)
            else:
                # gather the other mappings in a sublist
                nonTheoreticalMappings.append(mapping)
        # and add that sublist sorted alphabetically
        sortedMappingList.extend(sorted(nonTheoreticalMappings, 
                                     key=attrgetter('DispName')))           
    return sortedMappingList

for mapping in complexSort(objectList):
    print mapping.DispName

Produces:

theoretical (MT1)
map 1 (MT1)
map 2 (MT1)
theoretical (MT2)
map 2 (MT2)
map 3 (MT2)

Upvotes: 1

Views: 110

Answers (3)

Kevin
Kevin

Reputation: 76194

You could create a custom comparator to describe how two Mappings should be ordered with respect to one another. This is somewhat cleaner than your complexSort, since the function's only responsibility is comparing two objects, and leaves the actual sorting to Python.

from collections import namedtuple

Mapping = namedtuple('Mapping', ['DispName', 'MachID'])


def cmp_Mapping(a,b):
    #first, sort alphabetically by MachID
    if a.MachID != b.MachID:
        return cmp(a.MachID, b.MachID)
    else:
        #if MachIDs match, and one starts with "theoretical", it should go first.
        if a.DispName.startswith("theoretical") and not b.DispName.startswith("theoretical"):
            return -1
        elif b.DispName.startswith("theoretical") and not a.DispName.startswith("theoretical"):
            return 1
        #everything else is ordered alphabetically.
        else:
            return cmp(a.DispName, b.DispName)

objectList = [Mapping('map 2 (MT1)', 'MT1'),
          Mapping('theoretical (MT1)', 'MT1'),
          Mapping('map 3 (MT2)', 'MT2'),
          Mapping('theoretical (MT2)', 'MT2'),
          Mapping('map 1 (MT1)', 'MT1'),
          Mapping('map 2 (MT2)', 'MT2')]

for mapping in sorted(objectList, cmp = cmp_Mapping):
    print mapping.DispName

Result:

theoretical (MT1)
map 1 (MT1)
map 2 (MT1)
theoretical (MT2)
map 2 (MT2)
map 3 (MT2)

Upvotes: 1

unutbu
unutbu

Reputation: 879451

import collections 
import operator
import itertools as IT

Mapping = collections.namedtuple('Mapping', ['DispName', 'MachID'])

objectList = [Mapping('map 2 (MT1)', 'MT1'),
          Mapping('theoretical (MT1)', 'MT1'),
          Mapping('map 3 (MT2)', 'MT2'),
          Mapping('theoretical (MT2)', 'MT2'),
          Mapping('map 1 (MT1)', 'MT1'),
          Mapping('map 2 (MT2)', 'MT2')]

sortedMappingList = sorted(objectList,
             key=lambda mapping:
                            (mapping.MachID,
                             not mapping.DispName.startswith('theoretical'),
                             mapping.DispName))

for key, group in IT.groupby(sortedMappingList, key=operator.attrgetter('MachID')):
    for g in group:
        print(g.DispName)

yields

theoretical (MT1)
map 1 (MT1)
map 2 (MT1)
theoretical (MT2)
map 2 (MT2)
map 3 (MT2)

There is an excellent tutorial on How to sort using key functions, here.

Upvotes: 2

user2357112
user2357112

Reputation: 280500

Just use sorted with a key that produces the order you want. Since tuples are ordered lexicographically, a key that produces tuples should work pretty well.

def sort_key(thing):
    return (thing.MachID, not thing.DispName.startswith('theoretical'))

sorted(objectList, key=sort_key) # returns a list sorted the way you want

Upvotes: 3

Related Questions