FAFO on GitHub

Plus wasn’t the right operator for union. Let’s update that and then a few other operators.

Hello, my brothers and sisters and other siblings.

My friend Tim Ottinger pointed out to me that Python sets implement some of the symbolic operators, and that I had chosen differently. That won’t do, it would surely confuse people. So we’ll fix that right up.

They use | for union.

class XSet:
    def __or__(self, other):
        assert isinstance(other, self.__class__)
        return self.union(other)

    def sym_diff(self, other):
        return (self - other) | (other - self)

Sets also implement:

operation operator
intersection &
sym diff ^
subset <=
proper subset <
superset >=
proper superset >

I’ll go ahead and add those, with additions to my tests. Like this, for example:

    def test_classical_is_subset(self):
        c1 = XSet.classical_set((1, 2, 3, 4, 5))
        c2 = XSet.classical_set((2, 4))
        c3 = XSet.classical_set((1, 6))
        assert c2.is_subset(c1)
        assert c2 <= c1
        assert c2 < c1
        assert not c1 < c1
        assert not c3.is_subset(c1)
        assert c1 >= c2
        assert c1 > c2
        assert not c1 > c1

I think that I only have to implement one or two dunder methods for all of these. Which ones? A little experimentation tells me that __lt__ and __le__ suffice:

class XSet:
    def __le__(self, other):
        return self.is_subset(other)

    def __lt__(self, other):
        return self.is_subset(other) and self != other

Commit: implement subset comparisons as <, <= etc to match Python sets. implement | for union and ^ for symmetric difference. Not sure whether to advise use or not.

We have one particular need that comes to mind, which brings another to mind. The need that I foresee is the ability to get an element out of a set so that Python can deal with it. Thinking about that makes me think about getting the “cardinality” of a set, i.e. the number of elements in it.

Also thinking about getting an element from a set drives me to write this passing test:

    def test_list(self):
        x = XSet.from_tuples((("a", 1),))
        x_list = list(x)
        e,s = x_list.pop()
        assert e == 'a' and s == 1

Since sets can be iterated, Python can convert them to lists. But couldn’t we just have our own pop method, write on XSet?

Let’s test-drive that and see what we can figure out.

    def test_pop(self):
        x = XSet.from_tuples((("a", 1),))
        e,s = x.pop()
        assert e == 'a' and s == 1

We can implement that trivially but unhappily:

    def pop(self):
        return list(self).pop()

That passes. This won’t:

    def test_pop_null(self):
        e,s = XSet.null.pop()

That raises an exception, IndexError. So we implement:

    def pop(self):
        try:
            return list(self).pop()
        except IndexError:
            return None, None

And enhance that last test:

    def test_pop_null(self):
        e, s = XSet.null.pop()
        assert e is None and s is None

Commit: implement XSet.pop() to return an element unless set is empty, return None, None.

Reflection

It was just my browsing around for set information that I noticed pop and it came to mind to try it. I think that with our current definition, somewhat different from the Python standard, which would raise an exception, I like it for getting an element from a set.

I want another test, and another dunder method to go with it. I don’t think we implemented the one for intersection.

    def test_pop_empty(self):
        a = XSet.classical_set(['a'])
        b = XSet.classical_set(['b'])
        c = a & b
        assert c == XSet.null
        assert c.pop() == (None, None)

This demands:

class XSet:
    def __and__(self, other):
        return self.intersect(other)

    def intersect(self, other):
        return XSet.from_tuples((e, s) for e, s in self if other.includes(e, s))

Green. Commit: implement intersect as &.

OK, enough exercise, let’s sum up.

Summary

I had in mind starting on the idea of a set that consists, somehow, of a scope set and another set, possibly a file, that returns the elements from the target set whose element-scopes are in the other set. But I needed to fix the issue that Tim mentioned, with the operator for union, and that led to implementing others from Python’s set type.

That caused me to stumble onto pop and we’re trying that out as a way to get an element. One issue with it is that pop usually removes the popped item from a list, and our sets are immutable. Maybe we’ll come up with a better word than pop. I do like the general idea of returning either a tuple or a tuple of None, None, though. I do suppose someone could get a None into a set but if they do, let it be on their head.

Overall, I am feeling good about how much capability we’re getting with very little code, and with the ease of writing the code we need in terms of just iteration and inclusion. Rather nice, really.

Still looking for something semi-useful to build with this. If something comes to mind, please let me know: I might try it.

See you next time!