Monday, 24 November 2014

Weekend code puzzle: my answer (Python version)

G'day:
This is a companion exercise to my earlier articles:
I'll be using exactly the same logic as in those two, just working within Python's constraints. I freely admit to not knowing Python from a bar of soap - indeed this is the third bit of Python I have ever written - so I would not vouch for this being anything other than a comparison to the other two pieces of code, and not a demonstration of what a Python dev might consider "good code". This is not a Python tutorial.


As a comparison for my - mostly CFML-oriented - base readership, here's the earlier CFML version again:

// getSubseries.cfm

function getSubseries(series, threshold){
    var working = []
    return series.reduce(function(reduction, current){
        working.append(current)
        while (working.sum() > threshold){
            working.deleteAt(1)
        }
        var workingIsBetterForLength    = working.len() > reduction.len()
        var workingIsBetterForTotal        = working.len() == reduction.len() && working.sum() > reduction.sum()
        return (workingIsBetterForLength || workingIsBetterForTotal) ? duplicate(working) : reduction
    }, [])
}

I just have a wrapper for a call to reduce(), which does all the work.

Here's the Python version (/logical equivalent) I came up with:

# getSubseries.py

def getSubseries(series,threshold):
    import functools
    working = []
     def reduceCallback(reduction, current):
        working.append(current)
        while sum(working) > threshold:
            working.pop(0)

        workingIsBetterForLength= len(working) > len(reduction)
        workingIsBetterForTotal = len(working) == len(reduction) and sum(working) > sum(reduction)

        return list(working) if workingIsBetterForLength or workingIsBetterForTotal else reduction
    return functools.reduce(reduceCallback, series, [])

Some notes:

  • I need to import the functools library for the reduce() function. In Python 2, there was a built-in reduce() function, but in Python 3 it's been removed, instead pushed out into a library. It's a pity it's not a method of the List class.
  • As far as I can tell there is no way to do an inline function expression in Python. There are lambda expressions, but they're pretty useless compared to what CFML (etc) can do inline. This means I need to declare a function, then use that as a callback. This seems really clumsy to me.
  • The best way to duplicate the working list is to construct a new one with the List constructor. This makes sense.
  • Python doesn't have the short-hand ternary operator (?:), but does have this long-winded way of doing it. I guess the idea is it's slightly more readable for a person who has never encountered the construct before, but I dunno whether this is actually true. I also don't know a language should be specifically catering to first-time readers.
  • As Ryan pointed out in a comment to y/day's article, Python does have methods for len() etc, but the implementation is a bit shit (that's my assessment, not his). Python allows for implementation of a __len__() method, which the len() function will call. This is getting things arse-backwards if you ask me.
  • I don't think the "meaningful whitespace" does actually improve code readability. In fact I think it reduces it. This is perhaps my untrained eye looking for block-like constructs where I'm not going to find them.
All in all I found Python to be OK, but - really - on the basis of this exercise, I find it inferior to CFML: it's just quite clumsy in a lot of areas that CFML seems elegant by comparison. That said, obviously this is not really delving too far into Python, and I am presuming it's got stuff which really makes one go "wow", which explains its popularity. At the moment it seems to me like it's a case of the Emperor's New Clothes though. I shall make a point of looking into it some more, as I kinda assume there is more to it, and it is a good language.

That was a bit short, but I need to crack on with my day job now.

--
Adam