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: 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

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: The Crimson Campaign

The Crimson Campaign (Powder Mage, #2)The Crimson Campaign by Brian McClellan
My rating: 4 of 5 stars

This trilogy is definitely turning out to be one of those 1 book broken up into 3 trilogies than a series of stories following the main characters. The last book was a slightly cleaner break, but still ended with a lot still unresolved. This one, on the other hand, ends on a cliffhanger….well on a cliff….a mountain. Almost nothing is resolved in this book. Although one of the plots was, thankfully, resolved; that’s a good thing because I don’t think the protagonist involved in that plot could take much more.

As the middle of a narrative, it’s hard to write a review that isn’t spoiler-filled, but I’d like to focus on a few things that I think this book does very well compared to a lot of other fantasy and military fiction I’ve read. First of all, I like that this book deals with PTSD in more of a realistic way than most fiction. It’s not normal to just kill lots of others and get through that trauma without issues. I don’t know what happened in the Greek and Roman times, but in more modern times, most men (and, later on, women) haven’t had an easy time of it. One of the ways to cope is with drugs and McClellan has a character spend time in an period-appropriate fantasy equivalent of an opium den. I don’t think all our media needs necessarily have all the characters get shell-shocked, but having some more representation is a good thing. If anything else, it tempers some of the “excitement” that can end up being glorification of violence.

Second, I think McClellan does a good job of making use of a world that doesn’t have cell phones or even telegraph lines. A lot of the characters find themselves in plots that wouldn’t happen if everyone had perfect knowledge of what was happening around the world. It was quite the reminder of how spoiled we’ve become. (And makes me think of this article from 9 years ago about 10 classic Seinfeld episodes that fall apart if they have cell phones: https://gizmodo.com/10-classic-seinfe…)

Finally, a topic that has been discussed over the past 10 years (or more?) – rape narratives. There’s a tension in writing a violent narrative. On the one hand, rape is a real thing that happens to real people (both men and women) and if you see the phrase ” _____ and pillage” you know what the first word is. It’s a real issue during wars – both ancient and modern. But, on the other hand, writers have been so lazy for so long: Does your Hero need a reason to be moved to action? Rape or Kill his significant other! (see women in fridges: https://tvtropes.org/pmwiki/pmwiki.ph…) I *think* McClelland does a good job balancing things across these two entries in the trilogy. It’s a real threat that a few of the women characters deal with (including wondering if the cost of being helped is an expectation to bed the helper), but it’s not something that ends up happening. I think that keeps it realistic while not having to resort to tired tropes or retraumatize readers who may have gone through that in real life. I could be way off base here, but compared to other similar situations I’ve read in other books where the writers took the lazy out, I prefer this way.

Overall, McClellan continues an exciting war narrative in a world full of magic that is in a period of transition from monarchy to some kind of Republic or Democracy.

View all my reviews

Web Browsers Update: Vivaldi on Windows Part 2 and Firefox

This post continues a series on exploring new browsers:

Vivaldi

I’ve been meaning to get to this post for a while now, but the recent Vivaldi update blog post spurred me to go ahead and write it. Vivaldi continues to do a decent job for me on Windows. As I’ve been planning on posting, I don’t do much web browsing on Windows. It’s mostly just uploading videos to YouTube and looking up various sites related to the games I’m playing. Pretty much any browser could fit in there. That said, in Vivaldi’s blog post they have a video demonstrating their new pause mode and before they pause things, they have the tabs tiled. I had completely forgotten that was a thing! It would have changed the way I did my FunkWhale vs Ampache video. That is, of course, a common issue with Vivaldi (and its predecessor, Opera). It has a million features and if you aren’t always making use of them, it’s easy to forget about them.

A grid of 3 Vivaldi tabs

Firefox

One of the big themes with Opera was always being the first (or one of the first) with various web features (like tabs), but having them made famous by others. Looks like the trend continues with Vivaldi (Opera’s successor – although Opera does live on as a Chinese company). I mentioned in the video from my Vivaldi Part 1 post that Vivaldi had the ability to send a video to another window like Picture-in-Picture in the original “Big Screen TVs” (back when I was a kid and what is a normal size TV now was HUUUUUGE). Then in July I got an email from Firefox that led to this blog post. Firefox had added Picture-in-Picture. I wonder if Vivaldi will continue to be the browser that comes up with all the features, but gets none of the love (in terms of user percentage).

That said, Firefox recently had to lay off a bunch of folks, leaving the future of Firefox, the Rust Programming language, and many other initiatives in limbo. I went to use Firefox Send the other day and found it disabled.

Where do we go from here?

While I continue to really enjoy Vivaldi, I’m not really taking too much of an advantage of its features on Windows. I think I’m going to move on to Brave on Windows to see how that fares. After that will be the new Cromium-powered Edge. I’m not in a rush and things are picking up at work as we move to normalize things as COVID-19 cases continue to fall in the state. But that should be the next update. As I mentioned last time, Vivaldi was more performant on my Laptop, but the twins have mostly been using my laptop to play Stardew Valley with each other (laptops and our HTPC) and so I haven’t had as much time on the laptop. My main desktop is my powerhouse, so I haven’t yet made any real changes there. I do have all the browsers installed on here, I just haven’t taken the time to mess around with them yet. I may save that for after I settle on my Windows default browser.

FunkWhale vs Ampache

One of the categories of software people often go to /r/selfhosted to ask about, is for software to host music. This has become even more important with the dissolution of Google Music and Amazon and others removing the ability to upload your own music to listen to. I’ve got some experience with both FunkWhale and Ampache, so I decided to create a video to compare and contrast the two.

Review: Clarkesworld Magazine, Issue 141

Clarkesworld Magazine, Issue 141Clarkesworld Magazine, Issue 141 by Neil Clarke
My rating: 4 of 5 stars

This is one of those issues where I liked every story and every non-fiction essay. Read below for my thoughts per story.

A space of one’s own: a whimsical dystopia that reminds me of the Terry Gilliam film Brazil. I’m a world of overcrowding buildings can be resized and reconfigured.

Vault: another dystopia. This time there is a bit of a video game metaphor (at least to me) in the fact that the protagonists gain energy based on how many athletic tricks they do while traversing a planet. Explained away as causing their suits to collect more sunlight. The climax comes late, but could be an interesting universe for more stories.

The cosmonaut’s caretaker: An alternate future where the USSR still exists in a space-faring universe. The story takes some time to do world building, but expertly so, with practically no info dumps. Then it gets to the main thrust of the plot which involves our Captain’s current job when his post catches up to him. Didn’t want to stop reading until I was done.

Your multicolored life: this story definitely went places I didn’t expect with each of its protagonists, but it won me over by the end.

Heron of Earth: A post-human story unlike any I’ve ever read. It’s not about the trials and tribulations of becoming post-human. It’s not about whether we should do it. It’s more of a story that takes place after all that is over. It’s a short journey and I’m not even sure if it fulfills the MICE criteria. But it reads like a meditation and I’d like to see more of this world.

The Deeps of the Sky: An alien world in which insect-like creatures mine metals from a storm. It seems to take place on a Saturn or Jupiter-style planet where everything needs to live in the atmosphere and if there is a surface, it’s below a crushing amount of atmosphere. Another of those short stories that makes me desire more stories in the same universe.

Meridian: A SF version of what happens when a kid is put into the adoption system and it fails him. Made me sad to realize it’d probably continue to be a problem in the future. A good ending that doesn’t pander to the reader.

Non-Fiction:

The Effects of Space and Other Worlds on the Human Body: Going deeper than many popular articles I’ve read on the topic of the effects of space on the human body, it looks at how many different aspects of physiology and even baterial adaptation could affect our ability to expand beyond planet Earth.
Book covers, Moorcock, and The Mexicanx Initiative: A Conversation with John Picacio: A conversation with an artist who does book covers as well as other art about his history and his process.

Another Word: The Future, Ordinary: Cat Rambo takes some time to celebrate the SF that adheres to the trope “15 Minutes into the Future”. She talks about what we can take away from it, how it can help us think about how we structure society, and how it can make your stories out of date before they’re even published. As usual, Rambo’s prose does an excellent job of making me think and makes me think it would be delightful to have a conversation about SFF with them.

Editor’s Desk: A eulogy for Gardner Dozois, who, among among other things, was the reprints editor at Clarkesworld.


View all my reviews

Review: Clarkesworld Magazine, Issue 141

Clarkesworld Magazine, Issue 141Clarkesworld Magazine, Issue 141 by Neil Clarke
My rating: 4 of 5 stars

This is one of those issues where I liked every story and every non-fiction essay. Read below for my thoughts per story.

A space of one’s own: a whimsical dystopia that reminds me of the Terry Gilliam film Brazil. I’m a world of overcrowding buildings can be resized and reconfigured.

Vault: another dystopia. This time there is a bit of a video game metaphor (at least to me) in the fact that the protagonists gain energy based on how many athletic tricks they do while traversing a planet. Explained away as causing their suits to collect more sunlight. The climax comes late, but could be an interesting universe for more stories.

The cosmonaut’s caretaker: An alternate future where the USSR still exists in a space-faring universe. The story takes some time to do world building, but expertly so, with practically no info dumps. Then it gets to the main thrust of the plot which involves our Captain’s current job when his post catches up to him. Didn’t want to stop reading until I was done.

Your multicolored life: this story definitely went places I didn’t expect with each of its protagonists, but it won me over by the end.

Heron of Earth: A post-human story unlike any I’ve ever read. It’s not about the trials and tribulations of becoming post-human. It’s not about whether we should do it. It’s more of a story that takes place after all that is over. It’s a short journey and I’m not even sure if it fulfills the MICE criteria. But it reads like a meditation and I’d like to see more of this world.

The Deeps of the Sky: An alien world in which insect-like creatures mine metals from a storm. It seems to take place on a Saturn or Jupiter-style planet where everything needs to live in the atmosphere and if there is a surface, it’s below a crushing amount of atmosphere. Another of those short stories that makes me desire more stories in the same universe.

Meridian: A SF version of what happens when a kid is put into the adoption system and it fails him. Made me sad to realize it’d probably continue to be a problem in the future. A good ending that doesn’t pander to the reader.

Non-Fiction:

The Effects of Space and Other Worlds on the Human Body: Going deeper than many popular articles I’ve read on the topic of the effects of space on the human body, it looks at how many different aspects of physiology and even baterial adaptation could affect our ability to expand beyond planet Earth.
Book covers, Moorcock, and The Mexicanx Initiative: A Conversation with John Picacio: A conversation with an artist who does book covers as well as other art about his history and his process.

Another Word: The Future, Ordinary: Cat Rambo takes some time to celebrate the SF that adheres to the trope “15 Minutes into the Future”. She talks about what we can take away from it, how it can help us think about how we structure society, and how it can make your stories out of date before they’re even published. As usual, Rambo’s prose does an excellent job of making me think and makes me think it would be delightful to have a conversation about SFF with them.

Editor’s Desk: A eulogy for Gardner Dozois, who, among among other things, was the reprints editor at Clarkesworld.


View all my reviews

New Dishes I Cooked in August 2020

chicken cacciatore

Just one new dish in August. We had lots of repeat foods, but only one new dish. As far as I know, I didn’t have it until I was an adult and visited my sister-in-law in Long Island. At their favorite Italian restuarant / pizza joint I ordered it once when we were on the restaurant side. This America’s Test Kitchen version was very tasty and I would definitely make it again. Danielle liked it too, but it wasn’t a MUST COOK AGAIN dish with her.

Review: Clarkesworld Magazine, Issue 140

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

A nice balanced collection. My favorite was Cold Comfort. Below are my per story reviews and/or thoughts.

——-


A Vastness: A very interesting story of what we do when we’re so driven, we are willing to risk everything. And a great ending that was very unexpected. It felt a bit shorter than these usually are, so it was extra neat to have it work so well.


Not Now: A story about how messed up the media can be and how uncaring about those they’re covering. It focuses specially on how it can tear families apart.


Fleeing Oslyge: a war narrative about the psychology of attackers and victims by making the attackers aliens. Pretty harrowing and really makes me think even more about the consequences of war.


Farewell, Doraemon: A pretty deep story about growing up in the middle of nowhere, consequences of actions, and relationships. It takes place in China so the details are different, but it definitely has a lot of similar themes to American stories of going from the sticks to the big city and back. The SF elements are somewhat tangential to most of the story. So if you read it without wondering when it’s going to get SF, you’ll get more out of it.

Cold comfort: A story about a scientist willing to do what it takes to get their anti-climate change research to work. Had a surprisingly neat ending.

In panic town, on the backward moon: an alternate universe detective story on Mars. Fun prose and style.

Metallic mayhem in the movies: a history of Mecha in film and tv

Spies, radios, and the afterlife: The conversation goes a number is interesting places: what it’s like to be a bilingual author and have your work translated by someone else, the odd situation in the 1917 Russian revolution, and the afterlife.

Another word: Chinese Science fiction going abroad: a survey of the history of the translation of Chinese Science fiction. What I thought was interesting considering the size of China was how long it took Chinese SF to be translated and published outside of China. And after that it’s crazy how long it took to snowball in popularity.

Editor’s desk: a look at how Neil is working to increase the author pool for clarkesworld to be more diverse across national boundaries and other dimensions.

View all my reviews

Review: Rave Master Vol. 9

Rave Master Vol. 9Rave Master Vol. 9 by Hiro Mashima
My rating: 3 of 5 stars

While this continues the shonen tropes of the hero who can’t be beaten just because of his “can-do” attitude, Mr. Mashima increases the stakes and gives a compelling story behind the rise of Demon Card and the paths of the two Gales. It even has a silly joke to make up for the ridiculous name “Demon Card”. Apparently it was supposed to be “Demon Guard” and a typo messed it up. I wonder if it’s one of those katakana/kanji jokes or not.

As for the plot, I found myself wondering how we could be at this point in the story at volume 9 out of (what i know in the future compared to when Mr. Mashima was writing it) 33 volumes. Turns out he was thinking about ending the story here so that he could work on another project. Then he decided to explore more of the world and continue the story. We’ll see if the new and different stakes make the plot work better, worse, or just the same.

Onto Vol 10….

View all my reviews

Review: Ancillary Sword

Ancillary Sword (Imperial Radch #2)Ancillary Sword by Ann Leckie
My rating: 5 of 5 stars

I’m a huge world-building junkie. It’s one of the reasons I love science fiction and fantasy. And, as I realized while writing the previous sentence, it’s also why I love history non-fiction books and podcasts. I love learning about the society and what drives people to act the way they act. Humans are all human and have always had the same desires, but how those manifest and how we react to them are defined within our cultural contexts. An insult that might have demanded a duel in 1800s America might now simply result in a screed on Twitter. So, I loved the first book’s building up of Radchai culture. In the first book, the plot was almost incidental. It was a TRUE trilogy in that it reads, in retrospect, as the beginning of an incredibly long book.

This book, being the middle, proceeds with quite a bit stronger plot presence. It is STILL very strongly about culture and the intersection of colonialism and the conquered. It is STILL very much about how a strongly regimented etiquette system (as I’ve seen depicted in historical Britain and, to some degree, modern Japan) regulates what people can say and do in ways that Ms. Leckie uses to great effect. But the plot moves forward in a much more straightforward manner. Kind of….

Because this entry in the series is essentially a political thriller in a Robert Ludlum sense. Breq is sent on a mission by one half of Anaander Mianaai to a planet where he is unfamiliar and has no idea who to trust. Within that context a series of smaller mysteries have to be solved in the service of the larger mystery – all with very large consequences for the space station, the planet it orbits, and the system at large. (view spoiler)

As I mentioned in one of my status updates, between the insistence on drinking tea and the intense feelings of superiority of the Rachai over those they’ve conquered and their inability to even attempt to distinguish various subcultures in the planets they’ve conquered, draws a stark parallel with the British and the way they wreaked havok on the world that we’re still reaping the consequences for; namely the way they created random countries in the Middle East, Africa, and the Indian Sub-continent. Although, their constant use of “Citizen” also feels perhaps Roman. And it does seem that they are having trouble due to over-expansion, as the Romans did.

If there’s one final thing, it’s that it was incredibly spooky to read this book in the wake of the George Floyd protests. This book was published in 2014, which means it was probably done writing and in the publisher’s hands at the beginning of 2014 or end of 2013. And yet, it had the following parts in it that each made me almost drop the Kobo in shock as if Ms. Leckie was reaching into the future when she wrote the book.

First scene, one of the minorities on the station had an interaction with the cops for painting on an unauthorized wall. Things escalate and then Breq has this line:

“That hold,” I interrupted, “is not suitable for use on citizens. And it’s entirely possible to suffocate someone by kneeling on their back that way.”

WHAT!?!? HOW!?!? Then 12 chapters later:

Breq is having a conversation with the Governor:

“…I suppose you know her field workers are threatening to stop working unless she meets a whole list of demand?”
“I only just heard a few hours ago.”
“And by dealing with them in such circumstances, we are rewarding these people for threatening us. What do you think they’ll do but try it again, since it go them what they wanted once already? And we need things calm here.”
These people are citizens.” I replied, my voice as calm and even as I could make it… “When they behave properly, you will say there is no problem. When they complain loudly, you will say they cause their own problems with their impropriety. And when they are driven to extremes, you say you will not reward such actions. What will it take for you to listen?”

Damn. This book isn’t for everyone. If you aren’t a world-building nut, you may find a lot of that tedious. If you hated the first book, I wouldn’t recommend this one either. But if you were even luke-warm on that first book – it was place-setting so that this one could hit so hard. Read this book and experience the amazing work of Ms. Ann Leckie.

View all my reviews