What I’ve been up to in Programming: Python

Selenium for Automated Pool Signup

Spent the last week debugging that script. Turns out the key to getting it to run in cron is to add export DISPLAY=:0 && before your command. That’s because Chrome will not launch without a display to send Chrome to.

Python Morsels

The most recent Python Morsels exercise was to figure out if a number was a perfect square. Trey began his problem statement this way: “This week I want you to write a function that might seem simple at first, but there’s a number of ways to solve it.” It definitely took some out of the box thinking for me to figure out how I was going to solve the base case. The math.sqrt() function returns a float so that it can give answers for non-perfect squares. So I kept thinking and I realized that any perfect, non-complex square root must be an integer. So I came up with the conditional to return. (And after all this Pythonic learning, I’ve learned not to evaluate for truth and then return a variable. Just return the evaluation)

import math


def is_perfect_square(number):
    return math.sqrt(number) == int(math.sqrt(number))


if __name__ == "__main__":
    print(is_perfect_square(9))

For the first bonus, Trey just wanted us to return False if the number was negative. The solution was trivial. Anyone who’s done any amount of functional programming could figure it out.

import math


def is_perfect_square(number):
    if number < 0:
        return False
    else:
        return math.sqrt(number) == int(math.sqrt(number))


if __name__ == "__main__":
    print(is_perfect_square(9))
    print(is_perfect_square(-9))

The second bonus was to handle extremely large numbers. Trey mentioned in his intro that the second two bonuses would be hard. I think I can probably reason my way to a solution for bonus 3 (complex numbers). I’m quite a bit rusty on complex numbers, but I think I can get my mind to remember. (Despite having an electrical engineering degree, I’ve rarely had to use it. Ended up becoming a programmer and then a boss in a programming shop) For this one, however, I had no idea why Python couldn’t deal with large numbers. After all isn’t Python the current darling of the scientific community now? So I clicked on the first link related to this hint. The Decimal answer didn’t work. I wasn’t ready to do gmpy2 yet. So I clicked on the second link. Looked like Decimal was probably most likely the way he wanted me to go. That, of course, created some issues with 0.0 not being equal to 0, so I had to change things. I spent about 15-20 minutes trying to figure out if there was a Decimal way to get the integer part separated from the part after the decimal. Tragically, no. I did discover math.modf which will do that for you. However, it was not operating under the same context as the Decimal, so it was rounding too small that to work. (What I was trying to do there is that any perfect square should have nothing to the right of the decimal point.) Eventually, I had the thought that almost the same as the base and bonus 1, just “phrased” slightly differently. If I was previously seeing if they were both the same number, then I could move it over to this side of the equation and ask if sqrt(a)-int(sqrt(a))=0. I finally had a working solution once I raised the context high enough to get enough decimal places for that to work.

import decimal
import math


def is_perfect_square(number):
    if number < 0:
        return False
    else:
        with decimal.localcontext() as c:
            c.prec = 100
            return decimal.Decimal(number).sqrt()-int(decimal.Decimal(number).sqrt()) == 0


if __name__ == "__main__":
    print(is_perfect_square(9))
    print(is_perfect_square(-9))

Bonus 3 (handling complex numbers) was incredibly easy, especially after bonus 2. All I had to do was a quick read of the cmath library and see that a number N that is complex has N.real and N.imag to access the numbers in each part of the complex number. Then I just do what I did above to see if they are integers by making sure nothing’s right of the decimal place. Piece of cake! Also, I already knew about kwargs from other Trey problems plus reading up on it and it FINALLY made sense after all these years of not making sense to me.

import decimal
import cmath


def is_perfect_square(number, **kwargs):
    if kwargs.get("complex"):
        return (cmath.sqrt(number).real - int(cmath.sqrt(number).real) == 0) and (cmath.sqrt(number).imag - int(cmath.sqrt(number).imag) == 0)
    else:
        if number < 0:
            return False
        else:
            with decimal.localcontext() as c:
                c.prec = 100
                return decimal.Decimal(number).sqrt()-int(decimal.Decimal(number).sqrt()) == 0


if __name__ == "__main__":
    print(is_perfect_square(9))
    print(is_perfect_square(-9))

A little dust for the next few days (or weeks)

I just moved this blog to slightly different hosting infrastructure. Because of that I had some funky issues with character encoding yesterday. I believe that’s fixed now, but there may be other things that take me time to notice and fix up. Also, I still have a couple other things to set up so that I can FINALLY have https working for this blog. As part of this infrastructure change, I discovered and fixed at least one long-running issue that was causing problems with that. So if things are offline for a bit over the next few days or weeks – that’s why.

Last Few Days in Programming: Lots of Python

Been quite busy with Python, keeping me away from other pursuits, like video games. (Although the kids have been requesting Spelunky 2 whenever it’s time to hang out with them)

Extra Life Donation Tracker (eldonationtracker)

For my Extra Life Donation Tracker I pushed out a new release, v5.2.2. A user of my program (man, I never get tired of how awesome that is!!) had wholly anonymous donors which was causing an issue I thought I’d handled. But it turns out that the folks that run the Donor Drive API are a little inconsistent in how they handle that in the donor endpoint vs the donations endpoint. So I pushed that fix out and now things should be dandy for game day (about 2 weeks away!!)

Automating some Boring Stuff

In these COVID-19 times I have a problem – the YMCA where I’m a member has instituted signups for swimming. But you have to sign up EXACTLY 48 hours before you want to swim. Since I’m swimming every other day, that means that sign up time is when I’m swimming. For a while I would just wait until after my swim to sign up. But it’s a VERY popular time. So I started taking my phone to the pool to sign up. There are many negatives to this:

  1. It takes ~5 minutes or so with my phone and LTE connection (out of a 45 minute session which is already shorter than I’d normally spend in there)
  2. It uses data
  3. I risk dropping my phone into the pool or into a pool of water around the pool
  4. It means my phone is right there in my gym bag where someone could steal it (although that would give me a great excuse to buy a new one…)

So, while I was swimming today (best source of thoughts other than the shower), I realized I could probably use Selenium to automate this. I’ve never used it before, but I’d heard a lot about it. I knew that Al Sweigart talked about it in his book, Automate the Boring Stuff with Python. I bought a copy of the first edition, but I wanted to make sure I was up to date on the latest stuff so I went to that link I just shared where he has it available to read for free. He’s using the model Corey Doctorow used to use where it’s there for free, but you can also buy it and help him and the publisher. Also, he has a class on Udemy that covers the same topics. Anyway, I spent all morning (literally) digging around in my browser’s inspector mode to get all the data I needed to use it to automate the sign up. I believe I’ve got it working (I’d already signed up for my next swim session, so I had to pretend to sign up for another time, but you can only sign up for one time per day). I set up a cron job and what I’m going to do is let it sign me up and I’ll double-check (safety valve in case it doesn’t work). I’m not ready to share this code at this point – mostly because I’d prefer if it could keep working. However, it was a great experience in debugging and in how web scraping is just as annoying now as when I first learned about it somewhere around 15 years ago with O’Reilly books with titles like “Google Hacking” and “Flickr Hacking”.

raspigaragealert

As I mentioned in Switching up the hardware for the Garage IOT, I recently moved my Raspberry Pi-powered garage alert software from a Raspberry Pi 1B to a Raspberry Pi Zero W. The Raspberry Pi 1B is now in the office providing temperature and humidity data – quantifying just HOW HOT it is in here. This led me to have a renewed interest in the program. So I went ahead and created another config file in order to make it more generally usable to folks who aren’t me. Then I also created documentation. The documentation still needs a bit more work, but it could help others. Also, since it’s Hacktoberfest, someone made a PR for my code!! If this isn’t the first PR someone’s made against my code in a project in which they were co-authors, it’s at least one of the first. So that’s exciting!

Python Morsels

Finally, for this time period there was the most recent Python Morsels exercise. I fell a little behind with some other projects (and Spelunky 2) so my most recent assignment was to “create a ProxyDict class which offers an immutable dictionary-like interface that wraps around a given dictionary.” The first bonus was to add support for len, items, values, and get. The second bonus has to implement iteration and a nice repr string. The final bonus had to support equality.

At first I was a bit lost. I tried a naive solution where I just passed the keys of the dictionary I received in the __init__ method, but I got stuck on figuring out __getitem__. So then I thought I needed to use abstract base classes. I’d seen them in some book I read in the past few months. But I couldn’t remember what they were called. So I clicked on Trey’s first hint, which showed that I was right and reminded me of the term “abstract base class”. This was not a “gimme” for there was not a Dictionary in collections.abc. So after looking at the table in https://docs.python.org/3.8/library/collections.abc.html#module-collections.abc for a while, I thought Collection would give me a lot of what I wanted. But it was still missing a bit, so I looked at Mapping, which was probably the best thing to use because it was immutable and inherited from Collection. Unlike other problems in Python Morsels, this is a very, very esoteric part of Python, but it was interesting to learn how to implement an ABC; particularly the fact that it will let you know which dunder methods you’re missing when you try to create a class. Turns out that by doing this, I got bonuses 1 and 3 (and part of 2) for free! This was my code:

from collections.abc import Mapping


class ProxyDict(Mapping):

    def __init__(self, dictionary):
        self.proxy_dictionary = dictionary
        super().__init__()

    def __getitem__(self, item):
        return self.proxy_dictionary[item]

    def __iter__(self):
        return (key for key in self.proxy_dictionary.keys())

    def __len__(self):
        return len(self.proxy_dictionary)

As for the __repr__ method – I’m a pro at those at this point. I kept thinking there must be some way to cheat and use the one from the dictionary I was proxying, but I didn’t know how. So this was my code:

from collections.abc import Mapping


class ProxyDict(Mapping):

    def __init__(self, dictionary):
        self.proxy_dictionary = dictionary
        super().__init__()

    def __getitem__(self, item):
        return self.proxy_dictionary[item]

    def __iter__(self):
        return (key for key in self.proxy_dictionary.keys())

    def __len__(self):
        return len(self.proxy_dictionary)

    def __repr__(self):
        center_list = []
        for key, value in self.proxy_dictionary.items():
            if isinstance(key, int):
                center_list.append(f"{key}: '{value}'")
            else:
                center_list.append(f"'{key}': '{value}'")
        center = (', '.join(center_list))
        return "ProxyDict({"+center+"})"

I don’t think I have the prettiest syntax for my repr method. I was trying to be elegant and use a list comprehension. That looked like:

center_list = [f"{key}: '{value}'" for key, value in self.proxy_dictionary.items()]
center = (', '.join(center_list))

But without using a lamba or something, I couldn’t figure out how to implement the if/else logic in the list comprehension.

What I learned from Trey’s Solution

First of all, when I said this was an esoteric thing, I wasn’t kidding. There’s actually already a way to do this without any work:

from types import MappingProxyType as ProxyDict

Thanks mostly to Trey’s problem sets I knew that I wanted to use yield or that I probably wanted to do a generator. So I thought my solution was pretty good. But it turns out there are two simpler ways I could have done it. Since I’m proxying a dict, which already has an iter method, I could have done:

def __iter__(self):
   yield from self.proxy_dictionary

or I could have done:

    def __iter__(self):
        return iter(self.proxy_dictionary)

I actually think the first one is more readable. For the repr I kept thinking there must be some easier way to do this. Because the dictionary already has a repr. But I thought that would result in something like ProxyDict(dict(stuff)); apparently not. Because this is Trey’s solution:

def __repr__(self):
        return f"ProxyDict({repr(self.proxy_dictionary)})"

Although, that locks in the class name and causes issues if someone wants to do the same thing with our class. So the better way is:

def __repr__(self):
        return f"{type(self).__name__}({self.proxy_dictionary!r})"

the !r is the same as repr(self.proxy_dictionary).

Well, time to go check out that Pull Request on raspigaragealert!

First 24 Hours with Podcast Republic

It took me a while to get the hang of the many, many options within Podcast Republic so that I could get it to work the way I wanted – automatically downloading episodes on WiFi. Unlike Doggcatcher, but more like other modern pod catchers, you can stream a podcast instead of always having to download it. The option is nice, but it does introduce complexity. As usual, it was annoying at first to have the pod catcher think it needed to download every single episode from the podcasts you subscribed to.

Podcast Republic - recent episodes
Yup, 8783 episodes

Thankfully, unlike Doggcatcher it didn’t try to download them all. So I was able to figure out how to tell Podcast Republic I’d already listened to most of those episodes before it filled up my phone. I also ended up with newer thumbnails for some of the podcasts, like Star Talk:

Unlike Doggcatcher, it does grab the episode thumbnails. I’d heard some podcasts refer to those, but never saw them on Doggcatcher. Here’s what it looks like when a podcast has it:

Podcast Republic - episode thumbnails for Planet Money
Episode thumbnails for Planet Money

What I’m excited about is the ability to make multiple playlists. For example:

Podcast Republic - playlists
Podcast Republic – playlists

Here I’ve created a news playlist and applied it to news podcasts. I like to listen to news when it first comes out so I could jump to that playlist first each morning. Then I could go back to the normal playlist. I’m also happy with how easy it is to organize playlists vs in Doggcatcher. This makes it easier for me to set things up ahead of time and then not have to keep opening my phone to select the next episode I want to listen to. For example, I tend to want to listen to really long podcasts on exercise days because I’m able to hear it all in one go. (In fact, I created a running playlist, too) So I can shuffle those to another part of the default unplayed playlist if I want to get to them later. Here are some of the tags I’ve created. I haven’t done anything with them yet, but I could see it being useful if I’m trying to find a particular podcast:

Podcast Republic tags
Podcast Republic tags

So for now I’m quite glad Dan recommended Podcast Republic as a Doggcatcher replacement. I’ll leave you with two screenshots of the UIs I’m going to be interacting with the most:

Podcast Republic - Playing a Podcast
Podcast Republic – Playing a Podcast
Podcast Republic - what is up next
Podcast Republic – what is up next

Evaluating moving from Doggcatcher to Podcast Republic

I’ve been using Doggcatcher for YEARS – ever since I first got a smartphone something like 8 or so years ago. I started using Doggcatcher on Dan’s recommendation. One of the best features it’s had is the ability to speed up podcasts without chipmunking the voice. (I think that came a year or so after I started using it). Recently I’ve been a bit annoyed at Doggcatcher, particularly with podcasts from the EarWolf network (although there may be other networks with the same behavior). Every time Doggcatcher checks for updates, all the episodes from EarWolf will disappear and redownload. Until it is done, I can’t listen to the episode.
Neil deGrasse Tyson’s podcast is also annoying in that if a new episode comes out before I’ve finished the previous one, it’ll overwrite it so that I now have two copies of the same file. This makes it more stressful than it needs to be when I’m trying to choose the next podcast to listen to. So I started asking folks for recommendations. Dan recommended Podcast Republic to me. I don’t know if it’ll fix things for me because Dan was using it because Doggcatcher wasn’t working well for him for authenticated feeds, but I’m hopeful.

It does have some features that I didn’t know I wanted: being able to sync across devices – would have helped when I changed phones as well as being able to listen on web and sync (not something I’d use a LOT, but might use a bit). So I’m going to try it out and let you know what I think.

Brave on Windows Part 1

I’ve been using Vivaldi on Windows for about four months now. As I keep saying, my browser needs on Windows aren’t too huge. Mostly I access youtube, the Stardew Valley Farm uploaded, and Google Docs. But I want to keep checking out new browsers on Windows first precisely since they are so important on my Linux computer. I don’t want to mess up a good thing there.

So let’s start off with Brave’s new user tour:

First Page of the Welcome Tour

Interestingly it doesn’t see Vivaldi as a Browser to import from:

Select a browser to import from, but not Vivaldi

Now onto the important part of what makes Brave, Brave:

Brave’s Tracking and Add Blocking

Intelligently they tell you how to turn it off if it’s breaking sites, rather than let users think the browser’s rendering is broken. We’ll see how well it works for the sites I visit – probably just fine on Windows.

Choose your default search engine

Now, this I REALLY like. I guess since everyone else either owns search (Google and Microsoft) or gets paid by the search engine (Mozilla getting Google payments) I never see this. But I think this is the type of transparency that browsers should be providing! Not surprising since one of the Brave founders came from Mozilla.

Brave Rewards?

Rewards is a weird name for this, since I’m not getting paid or any items. But I do like the idea – you earn tokens that equate to money that gets paid out to the websites you want to support. Here’s a little more about it:

About Brave Rewards

I’m not going to sign up now because I don’t really visit enough sites on this computer and I just want to get on with it. Here’s the page I get after that:

Brave default start page

Now, it may look suspicious to you that it claims to have already blocked some trackers when I’ve only gone through their welcome page. I, too, was suspicious at first. But then I remembered when I imported settings from Chrome, it took me to some Adobe page. So new tabs always look like this. I opened a new tab without doing anything else:

New tabs just look like this

Looks like they make money from Cryptocurrencies? However, true to what you’d expect, unlike Vivaldi it doesn’t pre-populate your new tab with a bunch of sponsored sites. In fact, my speed dial still looks exactly the same on Vivaldi. Here’s how the blog looks on Brave:

This blog on Brave

I like the fonts it chooses to render with. It claims to have stopped trackers on my site. I don’t know of any, so I’m going to guess that the “Share This” has some of that embedded as do embedded YouTube videos. Let’s take a quick look at two sites I use that have ads. First Ars Technica:

It claims to have stopped 13 ads

But I guess these things aren’t ads:

And a quick look at reddit:

Reddit on Brave

There’s definitely an ad missing in that square. Supposedly also 13 ads and trackers. Again.

Brave doesn’t seem to have nearly as many widgets as Vivaldi, but that’s not surprising; Vivaldi, like Opera before it is known for being a power user’s browser. I don’t know if this ends up being pro or con for Brave in the long run. It’s a nice clear browser that more or less seems to look and feel like a regular browser – just with supposedly less tracking and ads. To get the same experience as Vivaldi would probably involve lots of potentially dangerous extensions. We’ll see how it handles my day-to-day on Windows.

number of trackers and ads blocked

Switching up the hardware for the Garage IOT

Back in May, I set up my Raspberry Pi B as my garage door monitor. Unfortunately it stopped working, I haven’t investigated yet, but I wouldn’t be surprised if it got hit with the infamous SD card corruption that was a big problem with the early Raspberry Pi boards. (I think I read it’s much less of a problem with the Raspberry Pi 4) So I decided to go ahead and switch it with a Raspberry Pi Zero W, especially since you can get it with headers from Adafruit for only $14. As a bonus, it’s got a better processor (same as the Raspberry Pi 3, I think) and built-in WiFi. It’s also got a smaller footprint, but that doesn’t matter to me for where it’s mounted. So now I’m back to having a Raspberry Pi B without a job to do (assuming the hardware is fine and it just ended up in an unbootable state. I’ve also now got a usb WiFi module for it, so maybe that’ll help me think of something for it to do. I think the Raspberry Pi rover project I got in a Humble Bundle uses a 1st gen Raspberry Pi, but I’d been thinking of using a 4th gen Pi in order to maybe do some more fun stuff with it like maybe some openCV based Computer Vision and/or machine learning.

All Journey and No Destination: Friday and Fast Times at Ridgemont High

By complete coincidence I ended up watching Fast Times at Ridgemont High and Friday (each for the first time) back to back this week. I watched Fast Times because it was being covered by Paul Scheer and Amy Nicholson were covering it on Unspooled, their film podcast. As for Friday, well, that’s a slightly more convoluted story. Five Iron Frenzy, one of my most consistently favorite bands, was doing a Kickstarter for their new album. As part of promotion for the campaign, Reese Roper appeared on Mike Herrera’s podcast, The Mike Herrera Podcast. Herrera is the lead singer and songwriter for MxPx, a band I’ve been listening to off-and-on since 1996ish. The Roper episode led me to lookup MxPx’s latest release, MxPx. There’s a song on there called Friday Tonight that had some lyrics that didn’t make sense to me:

Friday tonight: lyrics: how is it every single time/ I’m in the kitchen/ You’re in the kitchen / In the God-damned refrigerator/ I’ve always loved that line / I say it every time

So I went to genius.com’s page for the song and found out it was this scene from the movie Friday:

Fridge scene from Friday

So I decided to check out the movie. It was an interesting couple of movies to watch back-to-back for the first time.

In the first season of the Unspooled podcast they covered the movies on the AFI Top 100 list. For this season they are looking at movies that perhaps should have a place on the list (although the stated fate of the season 2 list is to be sent into space) and are exploring the movies by category. The first category is high school movies. I’d never seen Fast Times at Ridgemont High because it came out when I was too young and, for some reason, I never happened to catch it on Comedy Central, TNT, or any of the other cable channels that used to just show TV edits of movies before they started having shows in their own right. 

I’m not entirely sure what I was expecting, but from the trailer and various bits of the movie that had become part of the culture/memes/etc, I was expecting a zany film. Or at least a film that operated on the level of reality of Ferris Bueller, which came out four years later. Or maybe something like Grease, but without the music. Instead we got a movie where, when we reached the scene with Spucoli taking a joy ride in the football player’s car, I turned to my wife and asked, “What’s the point of this movie? I’m not getting a plot.” Instead it’s almost a series of vignettes that takes us through an entire school year at Ridgemont High. I learned afterwards (while listening to the podcast) that this is because it was based on a non-fiction book written by a Rolling Stone writer who studied the senior class at a high school. (Incidentally, Mean Girls was also based on a book, but that one ended up having a much more conventional plot) Plot-wise this movie seems to be at least one of the seeds that leads to most of the movies from Kevin Smith’s View-Askew-niverse – particularly Clerks and Mallrats. It also wasn’t nearly as comedic as I thought it would be. There are funny moments, but it’s more of a drama with funny moments – like real life.

Mostly we follow Stacy Hamilton (Jennifer Jason Leigh) who puts in an amazing performance as a 15-year-old who falls for the trope of having an unexplained need to lose her virginity; a trope that persisted until the 1990s when we finally started taking AIDS and other STDs seriously. What Ii mean by unexplained need is that Stacy seems not to want sex simply because of her teenage hormones, but more because it seems to be expected in her peer group if she doesn’t want people to consider her a baby. I even remember a Fresh Prince of Belair episode where Carlton is very embarrassed to be a virgin. By contrast, by the time I was in High School in the last 90s there wasn’t really any pressure to graduate without one’s virginity. It was more of a personal choice that people made – at least among my non-church peers. They’d been scaring us about STDs and the almost 100% chance of teenage pregnancy for so long that I was shocked when, as a married man, we didn’t get a baby on the first try. Anyway, her arc ends up being the most realistic movie depiction I’ve ever seen of the disappointment of teenage sex from the girl’s point of view. (The podcast clarified this was one of the director’s messages) First attempt is the famous dugout scene. Second attempt, she gets thwarted in a humiliating way. Third attempt, the dude is a one-minute man. By contrast, movie sex is usually from a male perspective. I also loved the way she handled talking to Damone once she got pregnant, not taking his attempt to shift blame onto her.

Stacy Hamilton lets Damone know she’s pregnant

“No, take that back.” Man, that was really great writing of a strong character. A different writer might have made her cave there, but Stacy isn’t playing victim, she’s just trying to get Damone to be fair by paying for his part in it.

That’s the clearest arc in the movie. Jeff Spicoli (Sean Penn) is merely comic relief. Linda Barrett (Phoebe Cates) is simply there to give bad advice to Stacy. Mark Ratner (Brian Backer) seems like he’s going to be the main character, but he’s mostly just a foil to Damone (Robert Romanus) and a second attempt at sex for Stacy. And Brad Hamilton (Judge Reinhold) is almost certainly the basis of Kevin Smith’s long-suffering Dante Hicks (Clerks and Clerks 2). Despite a good work ethic, capitalism just beats him down over and over throughout the film. None of of the usual plots are in evidence – no one is in danger of not graduating (maybe Spicoli is, but he’s merely a comedic element, not a real character), no one is trying to get into the big party, no male or female is in a “she cleans up nicely” trope, even Ratner isn’t trying very hard to get with Stacy. Yet, somehow this movie really hits for me. Perhaps it’s the more documentary-ish story telling due to it being based on a book. In the hands of our director (the same director of Clueless), the characters and situations aren’t heightened. As someone who worked in high school (selling shoes, as a lifeguard, in a movie theatre, and a bank teller), that aspect of the story really worked for me compared to the newer movies where the kids just have cash without needing to do anything for it. 

A few odds and ends before moving on to Friday:

I have to give kudos to the to the set designer for selecting oversized chairs in the restaurant during Ratner and Stacy’s date. They look ridiculously oversized, emphasizing that they are kids playing at being adults. 

My wife and I are fond of remarking on something we’ve noticed in movies from the late 70s and through the 80s (and I’m pretty sure I’ve mentioned it on the blog at some point). Movies from that time period will inevitably have precocious kids using profanity (the “worse” the word, the “funnier” it seems) and you will see lots of gratuitous breasts. Fast Times at Ridgemont High is no exception. (Judge Reinhold’s fantasy is completely without consequence to the plot). During the Unspooled episode about the movie, the director mentioned that during the 80s, the amount of breast shots required in a movie was a requirement for securing financing for the movie. So it’s not just something we’ve noticed, it’s an actual thing that was going on. (Frankly, on seeing how things were handled in Fast Times at Ridgemont High with bare breasts, I’m surprised we don’t end up seeing Cameron naked in Ferris Bueller)

Speaking of nudity, the director mentioned that during a screening, in the scene where Damone and Stacy have sex – she originally wanted to show full frontal nudity of Damone becuase there was already a bunch of full frontal female nudity in rated R movies at the time. She was told no because the male anatomy is automatically an aggressive organ while a female is passive, so it would have been rated X. Of course, the sad part, thanks to Hollywood being so silly that we have the term Hollywood-ugly to describe someone that the characters consider ugly but who is beautiful by normal standards, during a preview screening someone yelled out “fat chick” at Stacy’s naked body. I’m going to link to the image (rather than posting it in this post) in order to keep this post safe for work. (SO This LINK IS NOT SAFE FOR WORK) Yeah, I noticed that I was surprised Hollywood let a woman look like that in a movie, but she is definitely NOT fat.

One last thing – does Stacy’s boyfriend in Chicago exist? I thought he didn’t until she started crying at the end of the movie because he wasn’t coming to graduation. My wife thought he was real. Paul Scheer was sure he was fake and Amy Nicholson thought he was real, but was maybe convinced by the end of the podcast that he wasn’t.

While Fast Times at Ridgemont High takes place over a school year, Friday takes place over the course of one day. My wife had seen it enough times to be able to quote lines as they were happening. I never saw it because it was rated R and my parents were very strict about seeing movies rated higher than our ages. And later I was into very different movies, so I never thought about it until MxPx brought it back to my attention.

Interestingly, even though both of these movies ostensibly are without traditional plot structures, this movie just didn’t quite do it for me as well as Fast Times at Ridgemont High did. Perhaps this is because Friday only takes place over the course of one day, so there isn’t even a character progression. Yet Ferris Bueller also takes place over a single day. I think the big difference is that Bueller and friends are out on an illicit adventure (and, near the end, the need to avoid getting caught) while Ice Cube and the rest of the cast are simply sitting outside. Perhaps a more successful movie for me would have involved Ice Cube and Chris Tucker sitting outside for a normal day only to end up dragged on some sort of quest or to have things go insanely wrong. Instead, there are only two desires our main characters have. Chris Tucker wants to get Ice Cube high for the first time. This is accomplished midway through the movie and doesn’t have any consequences. He doesn’t do anything or cause anything to happen from being high – it doesn’t even mess up Ice Cube’s chances with the girl across the street. And that’s his desire, but it’s not as though he is a nerd who’s never had a girl – he CURRENTLY has a girlfriend. (Although, for all her protesting at Ice Cube interacting with other women, my wife noticed that she has a guy in her bed when she called Ice Cube on the phone). Instead we get an SNL skit-like day where the same folks keep stopping by over and over. 

Notice the guy behind her?

Why isn’t anyone working or in school? I was at a loss to figure out what age anyone was supposed to be, partially because Hollywood tends to cast way older (something they’ve started to fix), afterall, except for Jennifer Jason Leigh, no one in Fast Times at Ridgemont High looks like they belong in High School. Well, one potential plotline could have been the fact that Ice Cube lost his job because there is video footage of him stealing. Ice Cube says the guy in the video isn’t him. A few characters say different things about the robbery, but by the time the movie is done, I have no idea whether or not it was him. A different movie could have had him proving that it wasn’t him or trying to get another job and either succeeding or failing in comedic ways. But this paragraph is where I state something I’ve been thinking of as I’ve worked on this essay a little at a time over the past week. Maybe all of this makes sense if you grew up in a neighborhood like the one in the movie? Maybe there are some people for whom the plot – with some folks just sitting on the porch and others stopping by over and over makes sense. But for me it just fell flat when combined with the lack of a traditional plot motivation for any of the characters. 

It also seemed to take a wild swing at the end when it went from a mostly goofy movie to DEADLY serious when Zeus gives Felicia a black eye and then hits the girl Ice Cube would like to get with. It’s suddenly about whether shooting a gun is worth it. And while we did literally have Chekhov’s Gun, it was some real tonal whiplash. Then again, I remember some Fresh Prince of Bel-Air episodes doing that, too. So maybe it’s just an expected trope.

A couple stray thoughts:

I finally got to see the origin of the meme “Bye Felicia”. However, the character of Felicia didn’t make sense to me. Throughout the first ¾ of the movie she appears at Ice Cube’s house asking to borrow things that don’t make sense to borrow – like a microwave. She looks and acts like she’s probably a homeless addict. Yet, near the end you find out that she’s the sister of the girl Ice Cube has been after. So, does this mean she’s just mentally ill? And if she is, does that make all the jokes at her expense worse? (Although my question does imply it’s OK to laugh at an addict. But we do have a male character who’s a homeless addict who is 100% just played for laughs)

Why is Bernie Mac a shady preacher both in this movie and a shady judge in Booty Call? Was it part of his standup at the time or is he just really good at that role?

In the end, I think it’s interesting that I watched both of these cultural touchstone movies back-to-back without any foreknowledge of the plot and they both happened to be movies without traditional plot structures. Fast Times at Ridgemont High turned out to be really enjoyable while Friday turned out to be a dud for me. The next episode of Unspooled is going to be Dazed and Confused, but I don’t know if it’ll merit a blog post on its own. Time will tell.

Last few weeks in Programming: Python, Ruby

You may notice it’s been a while since I last wrote about programming. Well, a lot of the programming I was doing with the kids was due to having more time on my hands thanks to COVID. With Scarlett back in school full-time, there’s a lot less time for that as we focus on making sure she gets the most out of her education as well as teaching the twins to make up for not being in preschool this year. This has left me with reduced time for all my projects and entertainment (games, TV, and reading). Up until now that has meant the programming was put off to the side – kind of. Because I’ve been loving my exercises from the Python Morsels website and I’ll be talking about that when I get to the Python section. But first up, Ruby!

Ruby

At some point I think I blogged about reading Ruby Wizardry: An Introduction to Programming for Kids. I got the book during one of the many (MANY) programming Humble Bundles I’ve purchased. Originally, I meant to go through it with Scarlett. I even thought we had a good hook in the fact that one of the characters in the book (which teaches programming through storytelling – I LOVE this idea and wish more kids’ programming books thought of it) would help. Unfortunately, it seems I scarred her when I had her do the Python for Kids book before she could truly read and write, making it a frustrating experience that turned her off of programming until we discovered Scratch. Well, I wanted to learn Ruby and it’s the one book I have, so I figured I could go through it now and I’ll have a better idea of what’s in there if any of the three kids ever wants to go through it. I put it into my “free time rotation”, but that list has grown so long that there was too much time between coming back to Ruby. So I decided to go ahead and just finish the book. It can’t be THAT long compared to an adult’s introduction to a programming language.

Here are some of the neat things I learned:

One of my favorite things in Python is relatively new to the language: F-strings. It allows you do do:

number = 1
print(f"The number is.... {number}")

instead of:

number = 1
print("The number is... %d".format(number))

While it’s clear in a trivial example like that, it can be hard to keep track of what’s going on once you start having 3 or 4 variables in there.

Ruby already had this built-in!

number = 1
puts "The number is... #{number}"

I thought that Python was pretty easy for new programmers with its much more English syntax and the fact that it uses tabs rather than curly braces for its code blocks. But Ruby is even more “user-friendly” from this perspective. Take this code example from the book:

flowmatic_on = true
water_available = true
if flowmatic_on && water_available
  flow_rate = 50
elsif !flowmatic_on
  puts "Flomatic is off!"
else
  puts "No water!"
end
if flow_rate > 50
  puts "Warning! flow_rate is above 50! It's #{flow_rate}."
  flow_rate = 50
  puts "The flow_rate has been reset to #{flow_rate}."
elsif flow_rate < 50
  puts "Warning! flow_rate is below 50! It's #{flow_rate}."
  flow_rate = 50
  puts "The flow_rate's been reset to #{flow_rate}."
else
  puts "The flow_rate is #{flow_rate} (thank goodness)"
end

First of all, there isn’t even a colon required after the conditionals (if/else). Second, the indentation is completely optional. Third, use of the word end clarifies to the programmer (especially if it’s not your code) that this set of conditionals is done. So for a complete novice, it seems that Ruby does indeed earn its status as one of the easier new languages to learn. I’m not sure if puts is more reasonable than print. “puts” might make the novice ask – put where? “print” might have them thinking it’s going to a printer.

In fact, it gets even MORE user friendly because a few paragraphs later while simplifying they explain that if you have a conditional that looks like this:

if variable != value
   do something

it can be rewritten as

unless variable == value
   do something

And that is definitely more human-readable.

There is one part where I found Python a little more readable. In Ruby you can do Python-style for loops:

stops = ["East Bumpspark", "Endertromb Avenue", "New Mixico", "Mal
Abochny"]
for stop in stops
  next if stop.empty?
end

And other than the next keyword and question mark, it’s more or less the same as in Python. But here’s the more Ruby-esque (what’s the Ruby community’s version of Pythonic?) way to do it:

stops = ["East Bumpspark", "Endertromb Avenue", "New Mixico",
"Mal Abochny"]
stops.each do |stop|
  next if stop.empty?
end

And I find that WAY less readable; weird for a language that until now seemed more readable than Python. The cool thing is that they also support list comprehensions (who knows, maybe it appeared in Ruby before Python?)

stops = ["East Bumpspark", "Endertromb Avenue", "New Mixico",
"Mal Abochny"]
stops.each { |stop| next if stop.empty? }

Having finished the book, I think that naive Ruby is definitely more readable than Python. But once you start getting into a lot of the refactoring that emphasizes doing more code on one line, a lot of that syntax gets a bit cryptic.

Python

While I did fix a small documentation error in my Extra Life Donation Tracker, I haven’t done any project coding in Python over the last month. (Or rather I hadn’t when I first started writing this blog post, but since then I have started another project (details below)). But I have been really enjoying the Python Morsels exercises.

Python Morsels

What I’d like to do in this section is talk about the different exercises Trey has given me and what I’ve learned from each one.

add

The first problem that Trey gave me was to do matrix addition. The way he put it was if you had two lists of lists, you have to add up the numbers that line up with each other. It’s a bit clearer with the example he gave:

>>> matrix1 = [[1, -2], [-3, 4]]
>>> matrix2 = [[2, -1], [0, -1]]
>>> add(matrix1, matrix2)
[[3, -3], [-3, 3]]

Since this was the first of the problems he gave me, I hadn’t yet aligned my brain into Python problem solution mode. So it was really hard and I never got past the base question. However, I did learn a few things. First of all, here’s the solution I came up with:

def add(matrix1, matrix2):
    return [[val1 + val2 for val1, val2 in zip(list1, list2)] for list1, list2 in zip(matrix1, matrix2)]


if __name__ == "__main__":
    matrix1 = [[1, -2], [-3, 4]]
    matrix2 = [[2, -1], [0, -1]]
    add(matrix1, matrix2)

Interestingly, when I was reading Serious Python, I’d noted that I might want to learn how to use the built-in function zip. So when I was trying to figure this problem out, I came across that and decided to see if it could help me. It turned out to be perfect! Additionally, from Trey’s List Comprehension page, I learned how to finally do a good list comprehension. So, what this code is doing is first taking my matrices and making a tuple that consists of the two lists I need to add. (This is the right-most zip – the one that involves the matrix1 and matrix2 variables) Then the left-hand side is taking that tuple and adding up the numbers that line up with each other (first with first, second with second, and so on) and creating a list to return. It’s incredibly elegant that I can do all that with just one line (not counting the function signature). The most important thing I learned is that any time you have a for loop that is just creating a list, you should use list comprehension.

circle

Once I got to circle, this is when I really started to see the benefits of the way Trey has Python Morsels set up. So, first off, he asked me to create a class to represent a circle. I didn’t even need to stretch myself for this one.

import cmath


class Circle:
    def __init__(self, radius: float = 1):
        self.radius: float = radius
        self.diameter: float = radius * 2
        self.area: float = cmath.pi * self.radius * self.radius

    def __repr__(self):
        return f"Circle({self.radius})"

But then he started adding in constraints. First I had to be able to have the radius update and have the other values update automatically. Then I had to be able to set the diameter and have everything else adjust. Finally, it couldn’t have a negative radius set. This really stumped me. How could I make sure things were changing if someone changed the value of the radius? I could create methods to do that if I were just designing this for myself, but he had unit tests it had to pass that required me to allow someone to do circle.radius = some_value and it had to work. So I finally learned the true Pythonic way to do setter and getter methods. Having last used them in Java in school, I had been doing it in a very un-Pythonic way. So here was my final code for that challenge:

import cmath


class Circle:
    def __init__(self, radius: float = 1):

        self._radius: float = radius

    @property
    def radius(self) -> float:
        return self._radius

    @radius.setter
    def radius(self, new_radius):
        if new_radius < 0:
            raise ValueError("Radius cannot be negative")
        else:
            self._radius = new_radius

    @property
    def diameter(self) -> float:
        return self.radius * 2

    @diameter.setter
    def diameter(self, diameter) -> None:
        if diameter < 0:
            raise ValueError("Diameter cannot be negative")
        self.radius = diameter/2

    @property
    def area(self) -> float:
        return cmath.pi * self.radius * self.radius

    @area.setter
    def area(self, area) -> None:
        raise AttributeError

    def __repr__(self):
        return f"Circle({self.radius})"

Then, after reading Trey’s solution I realized tehre were a few things I had in there that weren’t necessary. For example, I didn’t need to create an @area.setter just to raise an AttributeError. Simply not having an @area.setter method would have done that for me. So I ended up with the following in the end:

import cmath


class Circle:
    def __init__(self, radius: float = 1):

        self.radius: float = radius

    @property
    def radius(self) -> float:
        return self._radius

    @radius.setter
    def radius(self, new_radius):
        if new_radius < 0:
            raise ValueError("Radius cannot be negative")
        else:
            self._radius = new_radius

    @property
    def diameter(self) -> float:
        return self.radius * 2

    @diameter.setter
    def diameter(self, diameter) -> None:
        self.radius = diameter/2

    @property
    def area(self) -> float:
        return cmath.pi * self.radius * self.radius

    def __repr__(self):
        return f"Circle({self.radius})"
fix_csv

Every once in a while, I’ve got enough experience to nail the solution without any trouble. This was on such case.

import argparse
import csv

parser = argparse.ArgumentParser(description="Change CSV delimiters")
parser.add_argument('original', help="The original CSV file to convert")
parser.add_argument('destination', help="The destination CSV file, comma delimited")
parser.add_argument('--in-delimiter', help="The character for the delimiter in your input file")
parser.add_argument('--in-quote', help="The quote character in the in-file")
args = parser.parse_args()

infile_delimiter = "|"
infile_quote = '\"'

if args.in_delimiter:
    infile_delimiter = args.in_delimiter

if args.in_quote:
    infile_quote = args.in_quote

with open(args.original) as csv_file:
    original_input = None
    if args.in_delimiter or args.in_quote:
        original_input = list(csv.reader(csv_file, delimiter=infile_delimiter, quotechar=infile_quote))
    else:
        dialect = csv.Sniffer().sniff(csv_file.read())
        csv_file.seek(0)
        original_input = list(csv.reader(csv_file, dialect))

with open(args.destination, 'w') as csv_file:
    output_writer = csv.writer(csv_file, delimiter=',')
    output_writer.writerows(original_input)

For this program, the only part I needed a hint for was in the second bonus to find out that the Sniffer (line 25) class exists in the csv module.

tail

This was another one where the base solution was pretty easy for me, but as Trey tacked on more and more restrictions with the bonuses, it forced me to really think outside the box and, eventually, learn about a new part of Python. The problem statement was to take in a sequence and a number. The number tells you how many items from the end of the sequence should be kept and returned as a list. His initial examples:

>>> tail([1, 2, 3, 4, 5], 3)
[3, 4, 5]
>>> tail('hello', 2)
['l', 'o']
>>> tail('hello', 0)
[]

At first I was able to do a nice easy solution:

def tail(sequence, number_of_elements):
    if number_of_elements > 0:
        return list(sequence[-number_of_elements:])
    else:
        return []

But his Bonus 2 unit test created a problem for me because it created a sequence so large it would crash my system! So I needed to figure something out! By going through his hints I learned about deques. Essentially it’s a capped list where you tell it how big it can get and it FIFOs (First In, First Out) the rest of the items. So I ended up with:

import collections
import collections.abc


def tail(sequence, number_of_elements):
    if number_of_elements > 0:
        if isinstance(sequence, collections.abc.Sequence):
            return list(sequence[-number_of_elements:])
        else:
            deque = collections.deque(maxlen=number_of_elements)
            for item in sequence:
                deque.append(item)
            return list(deque)
    else:
        return []

In his solution I learned I could have even made things more elegant by replacing the for loop with:

list(deque(iterable,maxlen=n))
count_words

For this one, Trey started out with:

>>> count_words("oh what a day what a lovely day")
{'oh': 1, 'what': 2, 'a': 2, 'day': 2, 'lovely': 1}
>>> count_words("don't stop believing")
{"don't": 1, 'stop': 1, 'believing': 1}

Which was pretty easy for me as I’d done something similar in Impractical Python. But as I worked through the bonuses, which eventually required Regular Expressions, I’d done enough of these by this point to know when I was writing ugly, inelegant code. Here was my solution before reading through Trey’s solution:

from collections import Counter
import re


def count_words(phrase):
    counter = Counter()
    sentence_list = phrase.lower().split(" ")
    # regular expression to get rid of punctuation.
    front_pattern = re.compile('\A\W\w')
    end_pattern = re.compile('\w+\W$')
    for word in sentence_list:
        if front_pattern.match(word):
            word = word[1:]
        elif end_pattern.match(word):
            word = word[:-1]
        counter[word] += 1
    return counter

What’s interesting is that, just like deque last week, I don’t actually need to make use of a for-loop. The Counter function will auto-iterate for me. (Which is weird, because the documentation shows a for-loop, but maybe that’s just to make things more clear?)

A more important thing is the Regular Expression part (I really should start going through at least one of my many RE books from O’Reilly). Instead of trying to eliminate the prefix and suffix punctuation, I should have just done:

r"\b[\w'-]+\b"

This means look for words, apostrophes, and dashes and get rid of whatever’s outside the word. So this gets by the problem I was having at first with the word “don’t”. So here is the final solution – a mashup of my code and Trey’s:

from collections import Counter
import re


def count_words(phrase):
    return Counter(re.findall(r"\b[\w'-]+\b", phrase.lower()))

Crazy how much shorter that is, right?!?!

point

This one once again had me learning more and more with each bonus after the base solution didn’t present any challenges to me. We were to make a Point class to represent a point in 3D space, allow math functions on it, determine if two point objects were the same point in space, and allow unpacking of the coordinates with multiple assignment. When it came to the third bonus, (multiple assignment) I didn’t even have the first clue of what I needed to do to solve this bonus. So, off to hints land! This comes together with everything I’ve been learning so far with Trey and ties nicely with all the iteration, iterator, etc stuff he’s been having us do! The solution is to do iter and create a generator that gives the answers. So here’s my code after all that:

class Point:
    def __init__(self, x: int, y: int, z: int):
        self.x = x
        self.y = y
        self.z = z

    def __add__(self, other):
        return Point(self.x+other.x, self.y+other.y, self.z+other.z)

    def __sub__(self, other):
        return Point(self.x-other.x, self.y-other.y, self.z-other.z)

    def __mul__(self, other):
        return Point(other*self.x, other*self.y, other*self.z)

    def __rmul__(self, other):
        return Point(other*self.x, other*self.y, other*self.z)

    def __eq__(self, other):
        if other.x == self.x and other.y == self.y and other.z == self.z:
            return True
        return False

    def __iter__(self):
        yield self.x
        yield self.y
        yield self.z

    def __repr__(self):
        return f"Point(x={self.x}, y={self.y}, z={self.z})"

This is one of the exercises that works really well to me as the proof of why Python Morsels is a really awesome exercise for improving the understanding of Pythonic code. As I said above – the algorithmic answer was easy. But it was not the best, most Pythonic code. Let’s take a look at what I can change based on his official solution for the base problem.

class Point:
    def __init__(self, x: int, y: int, z: int):
        self.x, self.y, self.z = x, y, z

    def __add__(self, other):
        return Point(self.x+other.x, self.y+other.y, self.z+other.z)

    def __sub__(self, other):
        return Point(self.x-other.x, self.y-other.y, self.z-other.z)

    def __mul__(self, other):
        return Point(other*self.x, other*self.y, other*self.z)

    def __rmul__(self, other):
        return Point(other*self.x, other*self.y, other*self.z)

    def __eq__(self, other):
        return (self.x, self.y, self.z) == (other.x, other.y, other.z)

    def __iter__(self):
        yield self.x
        yield self.y
        yield self.z

    def __repr__(self):
        return f"Point(x={self.x}, y={self.y}, z={self.z})"

I was most excited about the refactoring to the equality code. I’ve gone from 3 lines to just one. I already knew from previous exercises that often when dealing with a return, an “else” is redundant. So I didn’t have that in my original solution. But I always forget that you can just do something like this because if it’s true, it’ll cause a return true.

When I was working on my base solution, I had the intuitive idea that I could use dataclasses, but I didn’t have any experience with them, so I didn’t bother in order to see if I could get things working without using it. So here’s the solution that’s the more Pythonic while eliminating a lot of boiler plate code.

from dataclasses import astuple, dataclass


@dataclass
class Point:

    x: float
    y: float
    z: float

    def __add__(self, other):
        return Point(self.x+other.x, self.y+other.y, self.z+other.z)

    def __sub__(self, other):
        return Point(self.x-other.x, self.y-other.y, self.z-other.z)

    def __mul__(self, other):
        return Point(other*self.x, other*self.y, other*self.z)

    def __rmul__(self, other):
        return Point(other*self.x, other*self.y, other*self.z)

    def __iter__(self):
        yield from astuple(self)

So now I’m able to eliminate init, eq, and repr. I’m also able to simplify the iter method. It’s also a lot less code.

parse_range

This problem, where Trey wanted to give us a string with a list of numeric ranges and have us return a list with the numbers in that range, was the first time that I disagreed with Trey on some of the solutions in terms of their readability. Now, that may simply be because he’s more advance than I am or it may be one those those to-may-toe/to-mah-toe things. Or it might actually be that, as an instructor, he’s purposely making choices with his base solutions and bonus solutions to teach us something about refactoring, but it was interesting that I ended up making code that didn’t have to change much from base to bonus 3 while his had to change a lot. Now, his final solution was way more elegant than mine. But the steps in between were not, which I found interesting. Here’s my final code (again, his is better):

def parse_ranges(string):
    ranges = string.split(',')
    for item in ranges:
        number = item.split('-')
        for thing in number:
            try:
                int(thing)
            except ValueError:
                number.remove(thing)
        for numeral in range(int(number[0]), int(number[-1])+1):
            yield numeral


if __name__ == "__main__":
    parse_ranges('1-2,4-4,8-10')
is_anagram

This is one of those programs that almost everyone does when they’re learning programming because it usually demonstrates a lot of concepts at once. Trey then ups the ante by having us ignore spaces and punctuation and allowing accented vowels to be matched with non-accented vowels. Things got off to a bad start when I accidentally confused anagrams with palindromes. (Mostly because the first example Trey gave us almost looks like a palindrome). Wasted about an hour on that, but I did get a better understanding of various string and list functions. Once I realized it was anagrams we were after, it was pretty easy to realize you just wanted to use sets and do comparison. Unlike a list, sets don’t care about order, just membership. Of course, that also meant I had to check for length as the skin, sinks test was there to remind us. After all the bonuses I came up with:

import re

from unicodedata import normalize


def is_anagram(string1: str, string2: str) -> bool:
    """Returns true if string1 and string2 are anagrams of each other"""
    # get rid of unicode
    string1 = normalize('NFD', string1)
    string2 = normalize('NFD', string2)

    # get rid of all non-alphanumerics
    string1 = re.sub('[^A-Za-z0-9]+', '', string1)
    string2 = re.sub('[^A-Za-z0-9]+', '', string2)

    if len(string1) != len(string2):
        return False
    else:
        return set(string1.lower()) == set(string2.lower())


if __name__ == "__main__":
    result = is_anagram("teá", "eat")
    print(result)

I knew I probably wasn’t doing this to most efficient way when I did sets because I had to have the extra return False when they had different lengths. If there’s one thing I’ve learned about Trey’s problem sets, it’s that they’re usually much simpler than you want to do them at first. I think his solutions were actually pretty darn elegant. He offers using both Counter and sorted. Counter works because it’s creating a dictionary counting up each letter and if they are equal, then they inherently have the same length. For sorted, it’s basically putting the letters in order and the first time there’s a duplicate (or if one is longer than the other), it’ll become False. Funnily enough, he ended up with the same bonus solutions as me. I mean, he was using Counter, but he also used replace.

I’m surprised that for his bonus 2 example, he mentions using a generator. I was explicitly trying NOT to use any kind of for loops beacuse I was certain that wasn’t the solution he’d be looking for. I’m SHOCKED he doesn’t do anything at all with regular expressions. Could have saved myself about an hour’s worth of work just doing a for loop.

In the end he creates 2 helper functions to help him do it. I actually almost like mine better, even if Counter is more elegant.

meetup_date

Here Trey wanted us to give the date for a meetup if you have the data something like – I want it to be the the fourth Thursday of the month. For this particular exercise, I’d like to go through each of my solutions because my feelings as I went through each one were pretty fun. I’d also like to go through how it compares with his solutions to give a great example of how much I love learning from these. So, first up was to just calculate the 4th Thursday of a month.

from datetime import date


def meetup_date(year, month):
    """Given a year and month, return the day of the month that is the fourth Thursday."""
    thursdays = 0
    for day_of_month in range(1, 32):
        if date(year, month, day_of_month).weekday() == 3:
            thursdays = thursdays + 1
        if thursdays == 4:
            return date(year, month, day_of_month)


if __name__ == "__main__":
    date = meetup_date(2020, 9)
    print(date)

My first inclination was to use the Calendar module. Its method itermonthdays4 seems to return exactly what we need – a datetime.date object. However, after playing with that for about half an hour, I couldn’t quite get it to do what I needed. After that I decided to start off with a naive attempt at the problem to see if a better solution presented itself.

So, interestingly, Trey says it will be tricky with just datetime, but I found it SO MUCH easier than what I was trying to do with calendar. I’ll be VERY interested in seeing what his solution is!

Next he wanted us to accept the nth day of the month, both specified by the user:

from datetime import date


def meetup_date(year, month, nth=4, weekday=3):
    """Given a year and month, return the day of the month that is the fourth Thursday."""
    target_weekday = 0
    for day_of_month in range(1, 32):
        if date(year, month, day_of_month).weekday() == weekday:
            target_weekday = target_weekday + 1
        if target_weekday == nth:
            return date(year, month, day_of_month)


if __name__ == "__main__":
    date = meetup_date(2020, 9)
    print(date)

Bonus 1 was extremely easy. All I had to do was un-hardcode a few values. I suspect with bonus 2, things might get a bit trickier.

Then we had to be able to count from the back of the month.

from datetime import date


def meetup_date(year, month, nth=4, weekday=3):
    """Given a year and month, return the day of the month that is the fourth Thursday."""
    start = 0
    end = 0
    step = 0
    if nth > 0:
        start = 1
        end = 32
        step = 1
    else:
        start = 31
        end = 0
        step = -1
    target_weekday = 0
    for day_of_month in range(start, end, step):
        try:
            if date(year, month, day_of_month).weekday() == weekday:
                target_weekday = target_weekday + 1
            if target_weekday == abs(nth):
                return date(year, month, day_of_month)
        except:
            print("short month")


if __name__ == "__main__":
    date = meetup_date(2020, 2, -1)
    print(date)

OK, things are getting ridiculous here. There is no way this is the right way to solve this – it’s bonkers. But it works! Which, I think is the whole reason I’m doing these exercises. To learn the more elegant ways to program in Python.

Finally we had to use a Weekday object:

from datetime import date
from collections import namedtuple

weekday_tuple = namedtuple('Weekday', ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'],
                           defaults=[0, 1, 2, 3, 4, 5, 6])
Weekday = weekday_tuple()


def meetup_date(year, month, nth=4, weekday=3):
    """Given a year and month, return the day of the month that is the fourth Thursday."""
    start = 0
    end = 0
    step = 0
    if nth > 0:
        start = 1
        end = 32
        step = 1
    else:
        start = 31
        end = 0
        step = -1
    target_weekday = 0
    for day_of_month in range(start, end, step):
        try:
            if date(year, month, day_of_month).weekday() == weekday:
                target_weekday = target_weekday + 1
            if target_weekday == abs(nth):
                return date(year, month, day_of_month)
        except:
            print("short month")


if __name__ == "__main__":
    date = meetup_date(2018, 1, nth=1, weekday=Weekday.MONDAY)
    print(date)

He said object, not class, so I’m guessing maybe a named tuple or extended dictionary? Looks like I was right. A namedTuple with default values turned out to provide a working solution for Bonus 3. However my code is bananas. Let’s see what Trey actually recommends.

So it looks like my intuition to use the Calendar module was correct. For almost every solution, except Bonus 1, he mentions that the Calendar module is the easiest and/or more readable. And a big focus on these exercises is not just to make things more elegant or Pythonic, but to make them more readable and maintainable. At one point I also thought of using a generator, but having gotten stuck with the Calendar module, I had already decided to go on the naive route. This is one of the rare times where I’m not sure if I prefer my solution or Trey’s solution more in terms of understanding what it does as well as adaptability. It was very easy to adapt mine without needing to create helper functions. I just moved from magic values to variable names. But near the end Trey ends up having to create helper functions.

For example, this is his first solution:

from calendar import Calendar, weekday, THURSDAY

def meetup_date(year, month):
    """Return a date of the fourth Thursday of the given month."""
    nth = (
        4
        if weekday(year, month, 1) != THURSDAY
        else 3
    )
    thursday_calendar = Calendar(THURSDAY).monthdatescalendar(year, month)
    return thursday_calendar[nth][0]

Essentially he’s saying that if first day of the month is a Thursday, then you want the 3rd week of a calendar that has Thursday as the first day of the week (instead of Sunday or Monday). Otherwise, you want the fourth week. This is why things get tricky in bonus 2 when you can count backwards and there are sometimes 5 weeks in a month. With my solution, it doesn’t matter, we’re just counting calendar days and figuring out if we’ve reached the right Thursday yet.

However, you can’t deny the Pythonic-ness of his Bonus 2 solution, even if it takes a bit more to understand.

from datetime import date
from calendar import monthcalendar, THURSDAY

def weekdays_in_month(year, month, weekday):
    """Return list of all 4/5 dates with given weekday and year/month."""
    return [
        date(year, month, week[weekday])
        for week in monthcalendar(year, month)
        if week[weekday] != 0
    ]

def meetup_date(year, month, *, nth=4, weekday=THURSDAY):
    """Return date of the nth weekday of the given month."""
    return weekdays_in_month(year, month, weekday)[nth-1 if nth > 0 else nth]

Basically, he’s using a helper function that puts all the Thursdays in a month into a list via list comprehension. Then he just uses list index (hence the need to subtract 1 from nth since lists start counting from 0). It’s pretty genius!

As for Bonus 3, I guess a class would have been fine. But at least I learned about using namedTuples.

Podman APIs Project (not a Python Morsel)

When I heard that Podman switched to a new restful interface, I decided I could write a Python implementation of the API that would help me implement some automation. So I started a new repo, podmanapis. What makes things a bit tricky is that the restful API is not consistent in what it returns. Sometimes it returns JSON, sometimes it just returns 200 OK. Other times it returns a stream. So trying to make a consistent Python API is a bit of a challenge. Not impossible, but a bit more work, Definitely means my implementation will end up being “opinionated”.

Review: InvestiGators

InvestiGatorsInvestiGators by John Patrick Green
My rating: 4 of 5 stars

Read this to my four-year-olds and I found it to be a blast. Most of the word-play went over their heads. In fact, after finishing it with my four-year-olds, I recommended it to my 8-year-old. We’ll see what she thinks. This is definitely one of those books you can read with the kids and, if you like Dad Jokes and Puns, you’ll be enjoying it rather than wishing you were doing something else.

View all my reviews

Review: Jumpstarting the Onion Omega2

Jumpstarting the Onion Omega2Jumpstarting the Onion Omega2 by Wolfram Donat
My rating: 3 of 5 stars

A good intro to the Onion Omega2 and Omega2+ boards. Unfortunately, the Onion folks have changed their interface, making a large portion of the book no longer accurate. You can kind of work your way through it if you’re technical and have a history with Linux on embedded devices.

View all my reviews

Review: Getting Started with Soldering: A Hands-On Guide to Making Electrical and Mechanical Connections

Getting Started with Soldering: A Hands-On Guide to Making Electrical and Mechanical ConnectionsGetting Started with Soldering: A Hands-On Guide to Making Electrical and Mechanical Connections by Marc de Vinck
My rating: 4 of 5 stars

I haven’t soldered in at least a good 15 years, if not more. I couldn’t remember the technique and I didn’t want to mess up my components for a new project I’m working on, so I read this book. It does a VERY good job of walking you through the basics, including what to do when things get messed up. It even has an advanced chapter at the end for surface mount soldering. I would highly recommend anyone who’s either new to soldering or wants to renew their skills.

View all my reviews

Review: Jumpstarting the Raspberry Pi Zero W

Jumpstarting the Raspberry Pi Zero WJumpstarting the Raspberry Pi Zero W by Akkana Peck
My rating: 4 of 5 stars

This book is a great introduction to creating electronics projects with the smallest raspberry pi at this time. Seriously, it’s the same size as an Arduino MKR board – like a stick of gum. I wish I’d read it before starting my raspberry pi adventures, because I learned a few tips, particularly for running headless. The author spots a good job providing code examples and a decent variety of projects. The wearable project is the most impractical when you compared to an adafruit flora, but still provides important information. Overall a great resource if you want to go the raspberry pi route for electronics hacking.

View all my reviews

Review: Clarkesworld Magazine, Issue 142

Clarkesworld Magazine, Issue 142 (Clarkesworld Magazine, #142)Clarkesworld Magazine, Issue 142 by Neil Clarke
My rating: 4 of 5 stars

An almost perfect issue (in terms of my tastes) in which I loved all but one of the stories. Here are my thoughts-per-story, which may have some more text than my status updates if I came up against character limits during updates:

Gubbinal: In a future where we’ve colonized Saturn’s moons, our protagonist hunts for artifacts created by self-replicating robots. The plot then shifts into something perhaps more fantasy than SF and ends in quite a fascinating way.

A Gaze of Faces: Take Assassin’s Creed and mix in Wool. Then add a dash of Alien. Make it a short story. It sounds crazy, but it’s a great mix that works really well to tell a very different take on the trope of on the colony thinking back to “Origin Earth” that’s been a trope since at least Asimov’s Foundation when one of the characters mentions that the idea of an origin planet for humans is just superstitious fables. I’d love to spend more time in this world. Perhaps as a series of short stories or novellas as was done for Wool.

The James Machine: Explores how people deal with love and loss through the lens of SF. It’s a pretty moving story and I really loved it. It was also interesting how each “chapter” was titled with what seems like a click-bait headline.

For What Are Delusions if not Dreams: The theme of this issue seems to revolve around past information, AI, identity. This one was not my favorite. I don’t like these short stories where I spend most of it confused about what the heck is going on. I think I kind of understood by the end and it had a decent ending for the story it was trying to tell.

To Fly like an Angel: (In Bill Hader’s Stefan voice) This story has everything: A future in which everything we readers know is true is considered a conspiracy theory (like clouds causing rain), a supposed Utopia that’s actually a Dystopyia, an awesome cyberpunk hacker lady-person, and a few twists and turns – all in a short story.

Swift as a Dream and Fleeting as a Sigh: I really loved this story. Ostensibly it was about an AI psychologist. It seemed to be about how the AI could think so much faster than a human – a trope I’ve seen before. Yet in the background, the conversations the AI is having with hits patients reveals a really neat SF world. And then a neat plot twist right at the end is the cherry on top.

Last Gods: In a post-apocalyptic future after intense global warming and possibly other issues, the remaining humans have developed a new set of gods. A deeply intense story about love and identity. Worked quite well.

Non-Fiction:
The Monster at the Movies: Film Adaptations of Frankenstein: The author explores the themes of the novel Frankenstein and how various movie adaptations have tackled these themes. I could actually see this expanded to perhaps a novella-length non-fiction book and get a lot out of it.

Author interview: a look at some themes the author has explored in anthologies and sorry stories

Another word: your life is epic: about recognizing when the mundane events in your life can be used as the core to your story. It doesn’t always have to be the dramatic moments

Editor’s desk: a threat followed through: Neil mentions getting some ideas for future columns and invites readers to send in their own ideas


View all my reviews