group_by()   F
last analyzed

Complexity

Conditions 11

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
c 1
b 0
f 0
dl 0
loc 39
rs 3.1764

How to fix   Complexity   

Complexity

Complex classes like group_by() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Filters for working with data structures, munging, etc..."""
2
3
from collections import OrderedDict
4
5
6
def sort_dict_vals_from_reflist(dct, reflist):
7
    """Return sorted dict vals from reference list for reference (of vals).
8
9
    Args:
10
        dct (dict): The original dictionary
11
        reflist (list): The reference list of keys to use for sorting.
12
    Returns:
13
        list: A sorted list of 2-tuples representing
14
            the dictionary (as found in `dict.items()`)
15
    """
16
    items = dct.items()
17
    items = [d for d in items if d[1] in reflist]
18
    return sorted(items, key=lambda x: reflist.index(x[1]))
19
20
21
def sort_dict_keys_from_reflist(dct, reflist, omit=False):
22
    """Return sorted dict vals from reference list for reference (of keys).
23
24
    Args:
25
        dct (dict): The original dictionary
26
        reflist (list): The reference list of keys to use for sorting.
27
    Returns:
28
        list: A sorted list of 2-tuples representing
29
            the dictionary (as found in `dict.items()`)
30
    """
31
    items = dct.items()
32
    items = [d for d in items if d[0] in reflist]
33
    return sorted(items, key=lambda x: reflist.index(x[0]))
34
35
36
def filter_list(lst, vals):
37
    """Filter a list by vals.
38
39
    Args:
40
        lst (dict): The dictionary to filter.
41
42
    Returns:
43
        string (dict): The filtered dict.
44
    """
45
    if any([not lst, not isinstance(lst, list), not isinstance(vals, list)]):
46
        return lst
47
    return list(set(lst).difference(set(vals)))
48
49
50
def filter_vals(obj, vals):
51
    """Filter a dictionary by values.
52
53
    Args:
54
        obj (dict): The dictionary to filter.
55
56
    Returns:
57
        obj (dict): The filtered dict.
58
    """
59
    if obj is None or not isinstance(vals, list):
60
        return obj
61
    newdict = {}
62
    for k, v in obj.items():
63
        if v in vals:
64
            continue
65
        newdict[k] = v
66
    return newdict
67
68
69
def filter_keys(obj, keys):
70
    """Filter a dictionary by keys.
71
72
    Args:
73
        obj (dict): The dictionary to filter.
74
75
    Returns:
76
        obj (dict): The filtered dict.
77
    """
78
    if obj is None or not isinstance(keys, list):
79
        return obj
80
    newdict = {}
81
    for k, v in obj.items():
82
        if k in keys:
83
            continue
84
        newdict[k] = v
85
    return newdict
86
87
88
def group_by(objs, groups=[], attr='name', fallback='__unlabeled'):
89
    """Group a list of objects into an ordered dict grouped by specified keys.
90
91
    Args:
92
        objs: A list of objects
93
        keys: A list of 2-tuples where the first index is the group name,
94
            and the second key is a tuple of all matches.
95
        attr: The attr to use to get fields for matching (default: 'name')
96
        fallback: A fallback label to use for unspecified groups.
97
98
    Returns:
99
        An OrderedDict of grouped items.
100
101
    >>> group_by([obj1, obj2],
102
                 groups=[('g1', ('name1', 'name2'))], attr='name')
103
    """
104
    grouped = OrderedDict()
105
    if not groups or attr is None:
106
        return {fallback: objs}
107
    # Initial population since it's not a defaultdict.
108
    for ordered_group in groups:
109
        label, _ = ordered_group
110
        grouped[label] = []
111
    seen = []
112
    for ordered_group in groups:
113
        label, matches = ordered_group
114
        for curr in objs:
115
            attr_label = getattr(curr, attr) if hasattr(curr, attr) else ''
116
            if attr_label in seen:
117
                continue
118
            if attr_label in matches:
119
                idx = matches.index(attr_label)
120
                grouped[label].insert(idx, curr)
121
                seen.append(attr_label)
122
    # Add unlabeled extras last so order is preserved.
123
    grouped[fallback] = [
124
        curr for curr in objs if getattr(curr, attr) not in seen
125
    ]
126
    return grouped
127