Last active
August 27, 2026 05:48
-
-
Save lgastako/de57f43b660711b7a101b9a043a57302 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env -S uv run --script | |
| # The task: | |
| # | |
| # Receive an input formatted as hour:minute (e.g.: 12:13) and write it on the | |
| # terminal with ASCII art in a style of a 7 segments display. | |
| # We'll do this by wishful thinking. The basic presmise is that you assume that | |
| # the perfect function you need is available, then if it's not, you implement | |
| # it by wishful thinking, and keep doing this recurisvely until you get to the | |
| # bottom where the functions you do need are available. | |
| # In a real project we'd probably interview the stakeholders to nail down | |
| # details of the spec, like "what does it mean to receive an input?" i.e. is it | |
| # as an argument to the program, on stdin, do we need a webserver that accepts | |
| # POST requests? What do we do if the input is malformed? etc. | |
| # Since this is just an example we'll keep things simple and just assume that | |
| # we will receive 1 or more formatted timestamps as the arguments to our | |
| # program on the CLI. | |
| # We start by assuming via wishful thinking that there already exists the exact | |
| # function we need, which in this case is something like "to_segmented" that | |
| # takes an argument in our input format and returns the ASCII art of that input | |
| # as a 7-segment display. So we can just parse the arguments, and print the | |
| # result of calling this function on each argument: | |
| import sys | |
| def main(): | |
| args = sys.argv[1:] | |
| # print a leading blank line to balance space against the trailing | |
| # blank lines. | |
| print() | |
| for arg in args: | |
| print(to_segmented(arg)) | |
| # Print a trailing blank line after each timestamp to keep multiple | |
| # timestamps separated | |
| print() | |
| # Of course that function doesn't exist, so now we have to implement it. We | |
| # again use wishful thinking to assume/pretend that there are already functions | |
| # that do exactly what we need, which is first, to parse the input format | |
| # into the pieces we need to easily render the segmented display, and then | |
| # to render those pieces. | |
| def to_segmented(timestamp): | |
| hours, minutes = parse_timestamp(timestamp) | |
| return segmented_time(hours, minutes) | |
| # Of course parse_timestamp doesn't exist, so we need to write it. Here we are | |
| # getting to the first place where the functions we need actually do exist. We | |
| # just need to split the string on the colon and then return the part before | |
| # the colon as the hours and the part after as minutes. In the real world we'd | |
| # probably have to thread more error handling throughout, but for purposes of | |
| # this example, I'll keep it simple and just raise an exception if the input is | |
| # not in the right format. | |
| def parse_timestamp(timestamp): | |
| pieces = timestamp.split(":") | |
| if len(pieces) != 2: | |
| raise ValueError("Expected hours and minutes separated by a colon, but got: " + timestamp) | |
| hours, minutes = pieces | |
| try: | |
| int(hours) | |
| except ValueError: | |
| raise ValueError("Expected hours to be an int but was: " + hours) | |
| try: | |
| int(minutes) | |
| except ValueError: | |
| raise ValueError("Expected minutes to be an int but was: " + minutes) | |
| if not 1 <= len(hours) <= 2: | |
| raise ValueError("Hours should be one or two digits but was: " + hours) | |
| if len(minutes) != 2: | |
| raise ValueError("Minutes should be two digits but was: " + minutes) | |
| return hours, minutes | |
| # Now we need to implement the `segmented_time` function. First we'll pad the | |
| # first digit with a space if it's a single digit, then once we have the padded | |
| # digits for hours and minutes, we need to map them to individual segmented | |
| # display digits (which we assume we have a function for via wishful thinking), | |
| # then we assume via wishful thinking that we have a function to join those | |
| # individual digits together horizontally. | |
| def segmented_time(hours, minutes): | |
| hours = hours.rjust(2, " ") | |
| all_chars = hours + ":" + minutes | |
| segmented_chars = [segmented_char(c) for c in all_chars] | |
| return join_horizontally(segmented_chars) | |
| # Now we need to implement the `segmented_char` function. For this function, | |
| # the easiest thing for us would be if there was already a dictionary mapping | |
| # all characters to their segmented versions, then we could just look them up | |
| # and return them. So we assume that dictionary exists via wishful thinking. | |
| # Unfortunately, since we can't reference things until we've defined them in | |
| # python, I'll have to define the dictionary here first, but you can assume | |
| # that temporally I would've written the function below this definition before | |
| # writing the actual dictionary. In practice I'd probably just inline the | |
| # dictionary lookup in the segmented_time function above, but I'm really just | |
| # trying to drive home the nature of programming by wishful thinking here. | |
| SEGMENTED_CHARS = { | |
| "0": "\n".join([" *** ", "* *", "* *", "* *", "* *", "* *", " *** "]), | |
| "1": "\n".join([" *", " *", " *", " *", " *", " *", " *"]), | |
| "2": "\n".join([" *** ", " *", " *", "*****", "* ", "* ", "*****"]), | |
| "3": "\n".join([" *** ", " *", " *", " *** ", " *", " *", " *** "]), | |
| "4": "\n".join(["* *", "* *", "* *", " *** ", " *", " *", " *"]), | |
| "5": "\n".join([" *** ", "* ", "* ", " *** ", " *", " *", " *** "]), | |
| "6": "\n".join([" *** ", "* ", "* ", " *** ", "* *", "* *", " *** "]), | |
| "7": "\n".join([" *** ", " *", " *", " *", " *", " *", " *"]), | |
| "8": "\n".join([" *** ", "* *", "* *", " *** ", "* *", "* *", " *** "]), | |
| "9": "\n".join([" *** ", "* *", "* *", " *** ", " *", " *", " *** "]), | |
| ":": "\n".join([" ", " * ", " * ", " ", " * ", " * ", " "]), | |
| " ": "\n".join([" ", " ", " ", " ", " ", " ", " "]), | |
| } | |
| def segmented_char(c): | |
| return SEGMENTED_CHARS[c] | |
| # Now we need the join_horizontally function that takes the individual | |
| # characters which are now rendered as multiple lines of output and joins them | |
| # horizontally. This is, again, one of the functions where the rubber meets the | |
| # road and we call into existing python functions to do the real work. | |
| def join_horizontally(blocks, sep=" "): | |
| split_blocks = [block.split("\n") for block in blocks] | |
| line_groups = zip(*split_blocks) | |
| return "\n".join(sep.join(line_group) for line_group in line_groups) | |
| # At this point we'd have to go back and fill in the SEGMENTED_CHARS dictionary, | |
| # which, in our case, already exists above. And we're done. | |
| if __name__ == "__main__": | |
| main() |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This was written in response to this reddit post:
https://www.reddit.com/r/learnprogramming/comments/1vza6k2/do_developers_have_a_mental_framework_to_solve/