Rename in the Flat sets
Since the Flat sets know their field names once and for all, we could use a better rename that doesn’t copy the data. Simple enough, rather nice.
We really only have a rename that works at the top level of a set:
class XSet:
def rename(self, re_scoping_set: Self):
# renames this set, not its contents sets
old_names = self.scope_set()
replaced_names = re_scoping_set.element_set()
update = replaced_names | re_scoping_set
renames = old_names ^ update
return self.re_scope(renames)
The re_scoping_set
will have the form { oldnew } for each scope (name) we wish to change.
Let’s do a test for rename_contents
, that renames one level down. Here’s the test for a single record:
def test_rename(self):
new_names = XSet.from_tuples((('first', 'first_name'), ('last', 'last_name')))
person = XSet.from_tuples((('ron', 'first'), ('jeffries', 'last'), ('serf', 'job')))
renamed = person.rename(new_names)
assert renamed.includes('jeffries', 'last_name')
assert renamed.includes('ron', 'first_name')
assert renamed.includes('serf', 'job')
We want one for a set of records, like this:
def test_rename_contents(self):
new_names = XSet.from_tuples((('first', 'first_name'), ('last', 'last_name')))
ron = XSet.from_tuples((('ron', 'first'), ('jeffries', 'last'), ('serf', 'job')))
chet = XSet.from_tuples((('chet', 'first'), ('hendrickson', 'last'), ('coder', 'job')))
personnel = XSet.n_tuple((chet, ron))
renamed = personnel.rename_contents(new_names)
for employee, _ignored in renamed:
assert employee.includes('jeffries', 'last_name') or employee.includes('hendrickson', 'last_name')
assert employee.includes('ron', 'first_name') or employee.includes('chet', 'first_name')
assert employee.includes('serf', 'job') or employee.includes('coder', 'job')
This is a bit hacked together but should do the job. The method should be easy enough:
def rename_contents(self, re_scoping_set: Self):
new_tuples = ((e.rename(re_scoping_set), s) for e, s in self)
return XSet.from_tuples(new_tuples)
Now if we were to call that method on our flat file set, it would create 1000 new records, all nicely renamed. That would be bad.
We are green. Commit: implement rename_contents
.
I suppose that testing this, we can just write the test and let it run. It won’t take long to run.
def test_rename(self):
new_names = XSet.from_tuples((('first', 'first_name'), ('last', 'last_name')))
path = '~/Desktop/job_db'
fields = XFlat.fields(('last', 12, 'first', 12, 'job', 12, 'pay', 8))
ff = XFlatFile(path, fields)
ee = XSet(ff)
renamed = ee.rename_contents(new_names)
impl = renamed.implementation
assert isinstance(impl, XFlatFile)
This fails, since the implementation is an XFrozen, since we copied all the records. An interesting side effect is that the repr
for XFrozen prints the entire contents. I think we’ll need to change that.
Now we make rename_contents
invite the implementation to take part in the festivities.
def rename_contents(self, re_scoping_set: Self):
try:
return self.implementation.rename_contents(re_scoping_set)
except AttributeError:
new_tuples = ((e.rename(re_scoping_set), s) for e, s in self)
return XSet.from_tuples(new_tuples)
- Full Disclosure
- I forgot the
return
after the try.
Everything continues as before, since no implementation handles rename_contents
. So in XFlatFile:
def rename_contents(self, re_scoping_set):
return XSet(self)
This doesn’t do the job, but now we have a place to stand. Now we “just” need to munge the symbol table. That might be easier if it had a more reasonable shape. What it is, is a list of triples, field, start, length, if I’m not mistaken.
def rename_contents(self, re_scoping_set):
new_names = []
for name, start, len in self.fields:
changed_name = name
for old, new in re_scoping_set:
if name == old:
changed_name = new
new_names.append((changed_name, start, len))
new_impl = self.__class__(self.file_path, new_names, self.scope_set)
return XSet(new_impl)
Our test is green. Let’s fetch a record just to be sure.
def test_rename(self):
new_names = XSet.from_tuples((('first', 'first_name'), ('last', 'last_name')))
path = '~/Desktop/job_db'
fields = XFlat.fields(('last', 12, 'first', 12, 'job', 12, 'pay', 8))
ff = XFlatFile(path, fields)
ee = XSet(ff)
renamed = ee.rename_contents(new_names)
impl = renamed.implementation
assert isinstance(impl, XFlatFile)
for person, scope in renamed:
assert person.includes('jeffries', 'last_name')
break
You might think we could pop a record, but with a flat file set, that would be bad. We should implement pop as well, if we’re going to stick with it.
We are green. Commit: implement quick rename_contents for XFlatFile.
Let’s take a look at pop
. I think it needs work:
def pop(self):
try:
return list(self).pop()
except IndexError:
return None, None
Who ever thought that was a good idea? Let’s try this:
def pop(self):
it = iter(self)
return next(it, (None, None))
Much better. Commit: more reasonable pop op.
Summary
Just a couple of small, but very useful things. In both cases, we were traversing whole sets when we did not need to.
I note that the rename in the flat file is kind of messy. We can surely improve it a bit with pure code, but I think we’d do well to at least consider using a symbol set and a set-oriented rename. I’ll leave that for another day.
I am kind of thinking that we need a map
function for XSets, to transform them with less code. We’ll see.
See you next time!