XGroup
I’m continuing directly on from the preceding article, to create my XGroup implementation. It may turn out that a break was needed. We’ll see.
Hello again, friends!
The plan is to build on the existing group-oriented tests to build up XGroup, a new XImplementation subclass, representing a grouped set of records.
Before I proceed, I want to be sure that I understand what my second test is actually producing. It goes like this:
def test_two_keys(self):
peeps = self.build_peeps()
scopes = SetBuilder()\
.put("department", "department")\
.put("job", "job")\
.set()
group_dictionary = defaultdict(list)
for person, scope in peeps:
keys = person.re_scope(scopes)
group_dictionary[keys].append((person, scope))
it_serfs = self.find(group_dictionary, 'it', 'serf')
assert len(it_serfs) == 2
for peep, s in it_serfs:
assert peep['job'] == 'serf'
I’m going to pretty-print the group_dictionary
.
print()
for key_set, records in group_dictionary.items():
d = key_set['department']
j = key_set['job']
print(f'dept {d} job {j}')
for e,s in records:
print(f" {s}: {e['department']}, {e['job']}, {e['pay']}")
That prints what I expect:
dept it job serf
1: it, serf, 1000
2: it, serf, 1100
dept it job sdet
3: it, sdet, 10000
4: it, sdet, 11000
dept sales job closer
5: sales, closer, 1000
6: sales, closer, 1100
dept sales job prospector
7: sales, prospector, 10000
8: sales, prospector, 11000
OK, to work.
I’ll duplicate most of that test and then continue toward the XGroup by intention.
def test_build_x_group(self):
peeps = self.build_peeps()
scopes = SetBuilder()\
.put("department", "department")\
.put("job", "job")\
.set()
group_dictionary = defaultdict(list)
for person, scope in peeps:
keys = person.re_scope(scopes)
group_dictionary[keys].append((person, scope))
x_group = XGroup(group_dictionary)
grouped_set = XSet(x_group)
We’ll put some assertions below that, of course, but this is enough to demand the XGroup class. Here’s my first cut, with PyCharm assisting by listing the required methods for me:
class XGroup(XImplementation):
def __init__(self, group_dictionary):
self._dict = group_dictionary
def __iter__(self):
for group_keys, records in self._dict.items():
yield group_keys
def __hash__(self):
return hash(self._dict)
def __len__(self):
return len(self._dict)
def __repr__(self):
return 'XGroup()'
My test passes, but it isn’t checking anything yet. We can begin checking. So far, there’s not much but I think we can do this:
...
grouped_set = XSet(x_group)
it = iter(grouped_set)
g1 = next(it)
print(g1)
assert g1['department'] == 'it'
That’s enough to tell me that I forgot to make a set out of those tuples.
After some messing about …
I simplify the test and finally realize what’s confusing me:
...
x_group = XGroup(group_dictionary)
it = iter(x_group)
g1 = next(it)
assert g1['department'] == 'it'
g2 = next(it)
assert g2['department'] == 'it'
g3 = next(it)
assert g3['department'] == 'sales'
My XGroup __iter__
needs to return element and scope, and it isn’t. What even should the scope of the group sets be? I think we’ll go for null for now.
class XGroup(XImplementation):
def __iter__(self):
for group_keys, records in self._dict.items():
print("iter", group_keys)
yield group_keys, XSet.null
Now the test needs some fixing:
x_group = XGroup(group_dictionary)
it = iter(x_group)
g1, _s = next(it)
assert g1['department'] == 'it'
g2, _s = next(it)
assert g2['department'] == 'it'
g3, _s = next(it)
assert g3['department'] == 'sales'
Right. Now let’s see about putting the XSet back in there.
def test_build_x_group(self):
peeps = self.build_peeps()
scopes = SetBuilder()\
.put("department", "department")\
.put("job", "job")\
.set()
group_dictionary = defaultdict(list)
for person, scope in peeps:
keys = person.re_scope(scopes)
group_dictionary[keys].append((person, scope))
x_group = XGroup(group_dictionary)
group_set = XSet(x_group)
xset_it = iter(group_set)
x1, s = next(xset_it)
assert x1['department'] == 'it'
assert x1['job'] == 'serf'
x2, s = next(xset_it)
assert x2['department'] == 'it'
assert x2['job'] == 'sdet'
x3, s = next(xset_it)
assert x3['department'] == 'sales' and x3['job'] == 'closer'
That is passing and I am tired. We’ll commit: initial XGroup produces keys component.
Summary
We’re well on the way to our new grouping capability. As seems to happen so often, we are getting some very powerful capability in a very few lines. That’s a credit to the basic ideas, which are those of Dave Childs, with a bit of residual credit to Python and maybe a fragment or two for me.
That commit message suggests to me what we should name the components of our new set. The group keys should have scope something like ‘group’ and the record set, still to be produced, should be named something like ‘records’. Or maybe ‘keys’ and ‘group’. Some conventional name, anyway.
Or … we could allow the group operation to provide two names, one for the keys component and one for the records component, and we could default to the names we choose but allow the programmer to create new names if they care to.
This has been good progress. A little confusion over how to create the iterator but otherwise everything just ticked along.
I am pleased. We’ll move further next time, but these small steps have been productive. I feel rather good about how this is going. I just wish I had someone with me who could appreciate it as much as I do. I hope my readers are at least mildly interested … both of you!
See you next time!