The Renegade Coder https://therenegadecoder.com Code First. Ask Questions Later. Sun, 12 Jan 2020 00:13:04 +0000 en-US hourly 1 https://wordpress.org/?v=5.3.2 https://i0.wp.com/therenegadecoder.com/wp-content/uploads/2017/05/the-renegade-coder-icon-cropped.png?fit=32%2C32&ssl=1 The Renegade Coder https://therenegadecoder.com 32 32 127809749 How to Comment Code in Python: Inline, Multiline, and Docstring https://therenegadecoder.com/code/how-to-comment-code-in-python/ https://therenegadecoder.com/code/how-to-comment-code-in-python/#respond Fri, 17 Jan 2020 15:00:00 +0000 https://therenegadecoder.com/?p=20719

How do you comment code in Python? Turns out there are three different ways, each with their own advantages and disadvantages.

The post How to Comment Code in Python: Inline, Multiline, and Docstring appeared first on The Renegade Coder.

]]>

As we kick off 2020, I wanted to start getting back to some of my favorite content: Python “how to” content. Today, we’ll be looking at how to comment code in Python—a skill we should all have.

To summarize, there are three main ways to make comments in Python. To make inline comments, use the hash mark, #. To make multiline comments, use a hash mark on every line. Alternatively, use triple quotes, """. These kick off a multiline string which can be used to simulate comments. For more details, check out the options below.

Table of Contents

Problem Description

One thing I’ve done throughout this series is create content that targets a specific issue and address it with a few solutions. Of course, many of those solutions require some fundamental understanding of how Python works. In other words, at no point have I actually written any of those fundamental articles. Well, I suppose it’s better late than never.

Today, I want to look at a few ways of commenting code in Python. For those that don’t know, comments are ways of documenting code directly. Specifically, a comment is text that has no semantic affect on your programs. In other words, comments don’t do anything but provide context for the reader.

As an example, we might want to write some mathematical expression like the Pythagorean Theorem:

a_squared = 3**2
b_squared = 4**2
c_squared = a_squared + b_squared

Clearly, this expression resembles the Pythagorean Theorem based on variable name choices alone. However, not everyone will be able to tell at first glance. In other words, we might want to add a comment that tells the reader what the purpose of this expression is. For example, we might say “uses the Pythagorean Theorem to compute c^2.” How do we go about doing that? Luckily, this article will give us a few options.

Solutions

In this portion of the article, we’ll take a look at a few different ways to write comments in Python. Keep in mind that this isn’t really an article on commenting styles or even a commentary on how to write comments. Instead, we’ll just be sharing the syntax. It’s up to you to figure out how you want to use the tools provided.

Inline Comments

In Python, you can create a comment using the hash mark, #. As soon as this mark appears, everything following it until the end of the line is considered a comment:

# Uses the Pythagorean Theorem to compute c^2
a_squared = 3**2
b_squared = 4**2
c_squared = a_squared + b_squared

Since comments don’t start until the hash mark appears, we can comment the ends of lines as well:

# Uses the Pythagorean Theorem to compute c^2
a_squared = 3**2  # Computes a^2
b_squared = 4**2  # Computes b^2
c_squared = a_squared + b_squared  # Computes c^2

Normally, I’m of the belief that your code should be primarily self-documenting. That said, an inline comment here and there can be helpful for future readers—including yourself.

Block Comments Using Inline Comments

Fun fact: Python doesn’t have block comments. In other words, there is no built-in syntax for handling multiline comments. As a result, PEP 8 recommends using repeated inline comments for block comments:

# Uses the Pythagorean Theorem to compute c^2.
# First, we compute a^2 and b^2. Then, the 
# expression is constructed as a^2 + b^2 and 
# assigned to c^2.
a_squared = 3**2  # Computes a^2
b_squared = 4**2  # Computes b^2
c_squared = a_squared + b_squared  # Computes c^2

Again, these comments are probably excessive; their role is to provide an example of a block comment.

Block Comments Using Multiline Strings

With all that said, it’s possible to simulate block comments with multiline strings:

"""
Uses the Pythagorean Theorem to compute c^2.
First, we compute a^2 and b^2. Then, the 
expression is constructed as a^2 + b^2 and 
assigned to c^2.
"""
a_squared = 3**2  # Computes a^2
b_squared = 4**2  # Computes b^2
c_squared = a_squared + b_squared  # Computes c^2

Now, that looks a little cleaner to me. In addition, it’s a bit easier to manage in source code in my opinion.

That said, keep in mind that this isn’t a true comment. We’ve instead created a string constant which isn’t assigned to a variable. In practice, this isn’t really an issue as the strings will get optimized out in the bytecode.

Another word of caution: sometimes this style of comment can be interpreted as a docstring. For example, if we insert this comment just below a function header, we’ll have created a docstring for documentation purposes:

def pythagorean_theorem(a, b):
  """
  Computes the length of the squared third leg of a right triangle.
  """
  a_squared = a**2
  b_squared = b**2
  c_squared = a_squared + b_squared
  return c_squared

In this example, our multiline comment is actually a docstring which we can use to document the method:

def pythagorean_theorem(a, b):
  """
  Computes the length of the squared third leg of a right triangle.
  :param a: the length of the first leg of the triangle
  :param b: the length of the second leg of the triangle
  :return: a^2 + b^2
  """
  a_squared = a**2
  b_squared = b**2
  c_squared = a_squared + b_squared
  return c_squared

Then, this docstring becomes a runtime attribute of the function. In other words, we can inspect that attribute as follows:

print(pythagorean_theorem.__doc__)

As you can see, docstrings aren’t like comments in the sense that docstrings still exist at runtime—regular comments do not. Many IDEs and other tools can then extract these docstrings for documentation purposes. How cool is that?

Challenge

At this point, I’d usually measure performance, but I didn’t feel like that would be applicable. Instead, let’s jump straight to the challenge!

Now that we know three different ways to comment code in Python, let’s talk about good commenting practices. In the comments below, share at least one tip you recommend when it comes to commenting code. Feel free to share anything from commenting styles to commenting etiquette. Bonus points for anything that’s Python specific.

As always, I’ll share my own tip below as well! If possible, I’d love to get a little discussion going. Depending on how it goes, I might compile the results in another article.

A Little Recap

And with that, we’re all done. As always, here’s a little recap of our three ways to comment Python code:

# Here is an inline comment in Python

# Here
# is
# a
# multiline
# comment
# in
# Python

"""
Here is another multiline comment in Python.
This is sometimes interpretted as a docstring,
so be careful where you put these.
"""

If you liked this article, and you want to see more like it, hop on my mailing list. Alternatively, you can contribute even more by becoming a patron and getting your name on the Wall of Fame. Finally, I’d love it if you ran over to YouTube and subscribed to my channel.

Since you’re here, you might benefit from my Python 3 Beginner Cheat Sheet. It’s basically a two-page document of Python syntax including if statements, loops, and expressions. If you’re looking to learn more about Python expressions, I also have a fun lab you can try out.

Beyond that, you can always check out some of these related articles:

Otherwise, thanks for stopping by! I appreciate it.

The post How to Comment Code in Python: Inline, Multiline, and Docstring appeared first on The Renegade Coder.

]]>
https://therenegadecoder.com/code/how-to-comment-code-in-python/feed/ 0 20719
8 Coolest Python Programming Language Features https://therenegadecoder.com/code/coolest-python-programming-language-features/ https://therenegadecoder.com/code/coolest-python-programming-language-features/#respond Mon, 13 Jan 2020 15:00:00 +0000 https://therenegadecoder.com/?p=20754

Lately, I've been having fun writing Python lists (pun intended). As a result, here are some the coolest Python programming language features.

The post 8 Coolest Python Programming Language Features appeared first on The Renegade Coder.

]]>

After writing nearly 20 articles just about Python, I’ve decided to take some time to reflect on what I’ve learned. For instance, I recently wrote a compilation article which includes 70+ Python code snippets. Now, I’ve put together a list of some of the coolest Python programming language features.

Table of Contents

Coolest Python Features List

And without further ado, let’s take a look at some of the coolest Python features. If you think I’ve missed any, feel free to drop them in the comments.

List Comprehensions

By far, my favorite feature in Python is the list comprehension. Honestly, the feature isn’t all that interesting; it’s just a convenient way to generate lists. That said, it’s a feature that I haven’t seen in any other popular language (e.g. Java, C, C++, etc.). As a result, I make sure to take advantage of it as often as possible. Here are a few examples:

# Generates a list containing values from 0 to 9
[i for i in range(10)]

# Generates a list of all even values from 0 to 9
[i for i range(10) if i % 2 == 0]

# Generates a list containing values from 1 to 10
[i + 1 for i in range(10)]

# Generates a list containing values from  0 to -9
[-i for i in range(10)]

# Generates all possible pairs between 0 and 9
[(a, b) for a in range(10) for b in range(10)]

# Shallow copies another list
my_list = [1, 3, 5, 7, 9]
[item for item in my_list]

Since a list comprehension creates a list, we’re able to work with the output like any other list:

# Generates a list containing values from 0 to 9
nums = [i for i in range(10)]
nums[0]  # returns 0
nums[1]  # returns 1

If you’re interested in learning how to write these yourself, I have an article just for you. In it, you’ll learn more about the syntax as well as a few application areas. If you have any of your own examples, feel free to share them in the comments.

Generator Expressions

One of the nice things about learning the list comprehension syntax is that it allows you to also write generator expressions. After all, they’re very similar—one just saves you space. That’s right! Generator expressions don’t actually create lists. Instead, they provide the means for generating one item at a time of a list without ever constructing that list. Take a look:

# Generates values from 0 to 9
(i for i in range(10)])

# Generates values from 0 to 9
(i for i range(10) if i % 2 == 0)

# Generates values from 1 to 10
(i + 1 for i in range(10)])

# Generates values from  0 to -9
(-i for i in range(10))

# Generates all possible pairs between 0 and 9
((a, b) for a in range(10) for b in range(10))

# Generates a shallow copy of another list
my_list = [1, 3, 5, 7, 9]
(item for item in my_list)

Notice how similar the syntax is to the list comprehension. However, the application is slightly different. Instead of indexing the elements, we have to use a special function:

# Generates values from 0 to 9
nums = (i for i in range(10)])
next(num)  # returns 0
next(num)  # returns 1

Since a generator is an iterable, we can also get away with using the for loop syntax:

# Generates values from 0 to 9
nums = (i for i in range(10)])
for num in nums:
    print(num)  # prints each item in the generator

Once we’ve exhausted every element, the generator expression becomes useless. In other words, we can only generate every element once. After that, we have to write the expression again.

Slice Assignment

Have you ever wanted to replace entire sections of a list? Well, Python has a feature that allows you to do just that in a single line: slice assignment. Like slicing, slice assignment allows you to specify a region of a list. Of course, the difference is that slice assignment then lets you replace that region with whatever you want:

my_list = [1, 2, 3]

# Appending a list with slice assignment
my_list[len(my_list):] = [4]

# Prepending a list with slice assignment
my_list[:0] = [0]

# Replacing middle element
midpoint = len(my_list) // 2
my_list[midpoint: midpoint + 1] = [-2]

# Replacing arbitrary subsection
my_list[:2] = [3, 4, 5]

As I’ve mentioned in a related article, slice assignment doesn’t stop there. We can use any iterable on the right side. For example, we could use strings, tuples, list comprehensions, or even generator expressions. In other words, our previous two features can make an appearance:

my_list = [1, 2, 3]
my_list[len(my_list):] = (item for item in range(5))

Since learning about slice assignment in late 2019, I’ve been obsessed with it. As a result, I think it’s my second favorite feature right behind list comprehensions. Right now, I don’t have an article covering this feature in more detail, so you’re welcome to share some of your favorite examples in the comments.

Iterable Unpacking (aka Destructuring)

If you’ve checked out my article on getting the last item of a list, you might recall that iterable unpacking was one of the solutions. The idea being that we can split a list into two pieces: the last item and everything else:

my_list = [1, 2, 3]
*remainder, last_item = my_list

Well, iterable unpacking can do more than retrieve the end of a list. For example, it can be used to swap variables:

a = 1
b = 2
b, a = a, b

Normally, we’d need three lines of code to perform a swap: one to create a temporary variable, another to overwrite one of the variables, and the last to copy the temporary variable to the other variable. With iterable unpacking, it’s a single line of code.

If iterable unpacking looks familiar to you, you might know it from its other name: destructuring. Oddly enough, I featured destructuring in an article covering some of my favorite features of any programming language.

That said, I don’t use iterable unpacking very often. If you have any good examples that would supplement this list, feel free to share them.

Negative Indexing

Of all the features on this list, negative indexing is perhaps the most subtle. After all, many modern programming languages have some form of list indexing. However, few have a way of getting the last element of a list so elegantly:

my_list = [1, 2, 3]
last_item = my_list[-1]

In addition to being able to access list elements in reverse, we can also use negative indexing with list methods like insert(), pop(), and index():

my_list = ['h', 'e', 'l', 'l', 'o']
my_list.insert(-1, 'l')  # ['h', 'e', 'l', 'l', 'l', 'o']
my_list.pop(-2)  # ['h', 'e', 'l', 'l', 'o']
my_list.index('l', -2)  # 3

If you like negative indexing, you’ll be happy to know this feature doesn’t stop with lists. Feel free to use it with strings, tuples, and other sequences.

Dictionary Comprehensions

Previously in this list, I mentioned list comprehensions. Apparently, that feature is so good that the developers decided to expand its capabilities to encompass other data structures like dictionaries. After all, wouldn’t it be nice to be able to generate a dictionary in a single line of code? Well, as of PEP 274, you can:

# Generates a dictionary of numbers to letters
{num: chr(65 + num) for num in range(5)} 

# Generates the same thing
nums = [1, 2, 3, 4, 5]
letters = ["A", "B", "C", "D", "E"]
{num: letter for num, letter in zip(nums, letters)}

Typically, I use a dictionary comprehension to merge two lists into a dictionary. That said, I’m sure there are other use cases. Feel free to share some of your favorite it in the comments.

Chaining Comparisons

In many modern programming languages, comparing values is a simple process. For example, in Java, I can compare two numbers as follows:

17 > 5

In this example, the result is a boolean, true. As a result, the following expression is illegal in Java:

17 > 5 > 1

Here, 17 > 5 evaluates to true. Then, the expression true > 1 is evaluated. Since this is nonsensical, the compiler crashes.

In Python, however, we can chain comparisons without any risks. In other words, the same expression above is perfectly valid, and it returns True.

Under the hood, each comparison is computed just like Java. However, each intermediate result is ANDed with the result of the other comparison. For example, 17 > 5 returns True. Then, 5 > 1 returns True. Finally, the results are combined by and which returns True.

Personally, I haven’t used this feature much, but it’s gotten a lot of attention on the development end. For example, PEP 535 mentions a few updates to the chaining protocol. If you know of any cool use cases for this feature, let me know in the comments.

f-Strings

Finally, we come to one of my favorite “new” (PEP 498) Python features, f-Strings. Normally, when we create strings for debugging, we lazily print them with concatenation. If we’re clever, we might use some of the string formatting techniques. Now, we can get the best of both worlds with f-Strings:

age = 25
name = 'Jeremy'
print(f'My name is {name}, and I am {age}')

In this example, we’ve created a string from two variables: name and age. All we had to do was prepend our string with an f. Then, we can place any expression we want in braces, and it will be interpreted. For example, age is a number which is automatically converted to its string representation.

I really love this f-String syntax because it solves a lot of common string formatting issues. For example, it makes string formatting very easy to read (unlike concatenation). Likewise, it makes it obvious what the output string will look like (again, unlike concatenation). Finally, there’s no issues with positioning of variables (unlike string interpolation). What you see is what you get.

While f-Strings are really helpful, I don’t find myself using them beyond debugging. Do you have any examples you’d like to share?

Honorable Mentions

As someone who really loves working with Python, I had a hard time keeping this list short. As a result, here are a few additional features that didn’t make the cut:

  • For/Else loops
  • Imaginary numbers
  • Any() and All()
  • Returning multiple values (tuples)
  • Arbitrarily large integers
  • Keyword arguments
  • Sets
  • Joining strings
  • Multiplying strings
  • Walrus operator
  • String interpolation
  • Slicing

Of course, with how enormous the standard library is in Python, I’m sure there are even more clever bits of syntax. In addition, Python development is very active, so I wouldn’t be surprised to see even more features in the future. Perhaps I’ll update this list from time to time as new features appear.

Recommendations

With all that said, thanks for sticking around to check out this list. Over the next couple months, I’ll be focusing more on Python and Git content, so expect to see more articles like this in the future. If there’s anything you’d like to see, drop a note in the comments or feel free to contact me directly.

In the meantime, support this website by hopping on the mailing list, becoming a patron, or exploring the shop. Otherwise, keep browsing the following related articles:

Finally, you might get some value out of the following Python resources from Amazon (ad):

Otherwise, thanks again for your time. I appreciate it!

The post 8 Coolest Python Programming Language Features appeared first on The Renegade Coder.

]]>
https://therenegadecoder.com/code/coolest-python-programming-language-features/feed/ 0 20754
How to Add an Item to a List in Python: Append, Slice Assignment, and More https://therenegadecoder.com/code/how-to-add-an-item-to-a-list-in-python/ https://therenegadecoder.com/code/how-to-add-an-item-to-a-list-in-python/#respond Fri, 10 Jan 2020 15:00:00 +0000 https://therenegadecoder.com/?p=20713

Whether or not you know how to add an item to a list in Python, you'll want to read this article just to learn about slice assignment. Trust me.

The post How to Add an Item to a List in Python: Append, Slice Assignment, and More appeared first on The Renegade Coder.

]]>

Lately, I’ve been thinking about new Python topics to write about, so I took to Google. When I searched “Python how to”, “Python how to add to a list” popped up first. Since this must be a popular search term, I decided it was worth an article. In other words, today we’ll learn how to add an item to a list in Python.

To save you some time, you can start adding items to a list right now with the append() method: my_list.append(item). If you have more complex needs, consider using extend(), insert(), or even slice assignment. See the rest of the article for more details.

Table of Contents

Problem Description

For people who come from other programming languages, tasks like creating and adding to a list can be daunting. After all, almost every language supports lists in one form or another (e.g. arrays, lists, etc.), but not all languages have the same syntax. For instance, here’s an example of an array in Java:

int[] myArray = new int[10];
myArray[0] = 5; // [5, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Here, we’ve created a fixed-size array of 10 elements, and we’ve set the first element to 5. In other words, we don’t really add elements to arrays in Java. Instead, we modify existing elements.

Meanwhile, in a language like Rust, arrays are declared a little differently:

let mut my_array: [i32; 10] = [0; 10];
my_array[0] = 5; // [5, 0, 0, 0, 0, 0, 0, 0, 0, 0]

In Rust, we have to explicitly declare the array as mutable with mut. That way, we can modify the array just like in Java. In addition, the syntax is quite a bit different. For example, we set the type to i32, the size to 10, and all elements to 0.

Of course, there are languages with built-in lists much similar to what you might find in Python. For example, Ruby’s (dynamic) arrays can be created and modified as follows:

my_array = []
my_array << 5  # [5]

Unlike the previous solution, we didn’t have to setup our array with a certain size. Instead, we’re able to start with an empty array. Then, we pushed a 5 into the array, and called it a day.

Oddly enough, in Python, the syntax for creating a list is quite similar:

my_list = []

But, how do we add anything to this list? That’s the topic of this article.

Solutions

In this section, we’ll take a look at various ways to add an item to a list in Python. Since this task is pretty straightforward, there aren’t very many options. In fact, that’s sort of by design in Python (i.e. “There should be one– and preferably only one –obvious way to do it.”). That said, I’ve included a few silly solutions to keep this piece interesting.

Add an Item to a List Statically

To be honest, this is sort of a nonanswer. That said, if you want to populate a list, you can declare the elements statically:

my_list = [2, 5, 6]

Rather than adding the items one at a time, we’ve decided to initialize the list exactly as we want it to appear. In this case, we’ve created a list with three items in it: 2, 5, and 6. In the next section, we’ll look at our first way to modify an existing list.

Add an Item to a List by Slice Assignment

In Python, there is this very peculiar piece of syntax that I only just recently learned about called slice assignment. While slicing can be used to return a section of a list, slice assignment can be used to replace a section of a list. In other words, it’s possible to write an expression which adds a value to the end of a list:

my_list = []
my_list[len(my_list):] = [5]

Here, we have an expression which replaces the end of the list, which is an empty list, with a list containing a single value. In essence, we’ve added an item to the end of the list.

Interestingly enough, this syntax can be used to replace any part of a list with any other list. For example, we could add an item to the front of the list:

my_list = [1, 2, 3, 4] 
my_list[:0] = [0] # [0, 1, 2, 3, 4]

Likewise, we can replace any sublist with any other list of any size:

my_list = [1, 2, 3, 4] 
my_list[:2] = [6, 7, 8, 9] # [6, 7, 8, 9, 3, 4]

Fun fact: we aren’t restricted to lists with this syntax. We can assign any iterable to the slice:

my_list = []
my_list[:] = "Hello" # ['H', 'e', 'l', 'l', 'o']
my_list[:] = (1, 2, 3) # [1, 2, 3]
my_list[:] = (i for i in range(5)) # [0, 1, 2, 3, 4]

When I first added this solution to the list, I thought it was kind of silly, but now I’m seeing a lot of potential value in it. Let me know if you’ve ever used this syntax and in what context. I’d love to see some examples of it in the wild.

Add an Item to a List with Append

On a more traditional note, folks who want to add an item to the end of a list in Python can rely on append:

my_list = []
my_list.append(5)

Each call to append will add one additional item to the end of the list. In most cases, this sort of call is made in a loop. For example, we might want to populate a list as follows:

my_list = []
for i in range(10):
    my_list.append(i)

Of course, if you’re going to do something like this, and you’re not using an existing list, I recommend using a list comprehension:

my_list = [i for i in range(10)]

At any rate, append() is usually the go-to method for adding an item to the end of a list. Of course, if you want to add more than one item at a time, this isn’t the solution for you.

Add an Item to a List with Extend

If you’re looking to combine two lists, extend() is the method for you:

my_list = []
my_list.extend([5])

In this example, we append a list of a single element to the end of an empty list. Naturally, we can append a list of any size:

my_list = [3, 2]
my_list.extend([5, 17, 8])  # [3, 2, 5, 17, 8]

Another great feature of extend() is that we’re not restricted to lists; we can use any iterable. That includes tuples, strings, and generator expressions:

my_list = []
my_list.extend("Hello") # ['H', 'e', 'l', 'l', 'o']
my_list.clear()
my_list.extend((1, 2, 3)) # [1, 2, 3]
my_list.clear()
my_list.extend(i for i in range(5)) # [0, 1, 2, 3, 4]

Of course, like append(), extend() doesn’t return anything. Instead, it modifies the existing list. Also like append(), extend() only adds the iterable to the end of the other list. There’s no way to specify where the input iterable goes. If you want more control, I suggest slice assignment or our next method, insert().

Add an Item to a List with Insert

If append() and extend() aren’t doing it for you, I recommend insert(). It allows you to add an item to a list at any index:

my_list = []
my_list.insert(0, 5)

In this case, we inserted a 5 at index 0. Naturally, we can choose any valid index:

my_list = [2, 5, 7]
my_list.insert(1, 6) # [2, 6, 5, 7]

And just like with regular list syntax, we can use negative indices:

my_list = [2, 5, 7]
my_list.insert(-1, 9)  # [2, 5, 9, 7]

How cool is that?! Unfortunately, however, we can’t really insert an entire list. Since Python lists don’t restrict type, we can add any item we want. As a result, inserting a list will literally insert that list:

my_list = [4, 5, 6]
my_list.insert(1, [9, 10])  # [4, [9, 10], 5, 6]

Luckily, slice assignment can help us out here!

Performance

With all the solutions ready to go, let’s take a look at how they compare in terms of performance. Since each solution doesn’t do exactly the same thing, I’ll try to be fair in how I construct my examples. For instance, all the following examples will append a value at the end of each of the sample lists (ignoring the static assignment solutions):

setup = """
empty_list = []
small_list = [1, 2, 3, 4]
large_list = [i for i in range(100000)]
"""

static_list_empty = "empty_list = []"
static_list_small = "small_list = [1, 2, 3, 4, 5]"

slice_assignment_empty = "empty_list[len(empty_list):] = [5]"
slice_assignment_small = "small_list[len(small_list):] = [5]"
slice_assignment_large = "large_list[len(large_list):] = [5]"

append_empty = "empty_list.append(5)"
append_small = "small_list.append(5)"
append_large = "large_list.append(5)"

extend_empty = "empty_list.extend([5])"
extend_small = "small_list.extend([5])"
extend_large = "large_list.extend([5])"

insert_empty = "empty_list.insert(len(empty_list), 5)"
insert_small = "small_list.insert(len(small_list), 5)"
insert_large = "large_list.insert(len(large_list), 5)"

Now, let’s take a look at all the empty list examples:

>>> import timeit
>>> min(timeit.repeat(setup=setup, stmt=static_list_empty))
0.06050460000005842
>>> min(timeit.repeat(setup=setup, stmt=slice_assignment_empty))
0.4962195999996766
>>> min(timeit.repeat(setup=setup, stmt=append_empty))
0.17979939999986527
>>> min(timeit.repeat(setup=setup, stmt=extend_empty))
0.27297509999971226
>>> min(timeit.repeat(setup=setup, stmt=insert_empty))
0.49701270000059594

As expected, the most straightforward solution performs the best. Let’s see how that plays out as we grow our list:

>>> min(timeit.repeat(setup=setup, stmt=static_list_small))
0.1380927000000156
>>> min(timeit.repeat(setup=setup, stmt=slice_assignment_small))
0.5136848000001919
>>> min(timeit.repeat(setup=setup, stmt=append_small))
0.1721136000005572
>>> min(timeit.repeat(setup=setup, stmt=extend_small))
0.28814950000014505
>>> min(timeit.repeat(setup=setup, stmt=insert_small))
0.5033762000002753

Again, append() gets the job done the quickest. Now, let’s take a look at an enormous list:

>>> min(timeit.repeat(setup=setup, stmt=slice_assignment_large))
0.5083946000004289
>>> min(timeit.repeat(setup=setup, stmt=append_large))
0.18050170000060461
>>> min(timeit.repeat(setup=setup, stmt=extend_large))
0.28858020000006945
>>> min(timeit.repeat(setup=setup, stmt=insert_large))
0.5108225000003586

Amazingly, all these solutions seem to scale really well. That said, append() takes the cake. After all, it is amortized O(1). In other words, appending to a list is a constant time operation as long as we don’t run out of space.

That said, take these performance metrics with a grain of salt. After all, not every solution is going to be perfect for your needs.

Challenge

Now that we know how to add an item to a list, let’s try writing a simple sorting algorithm. After all, that’s the perfect task for someone who wants to get familiar with the various list manipulation methods.

Personally, I don’t care which sorting algorithm you implement (e.g. bubble, insertion, merge, etc.) or what type of data you choose to sort (e.g. numbers, strings, etc.). In fact, I don’t even care if you sort the data in place or create an entirely separate list. All I care about is that you use one or more of the methods described in this article to get it done.

When you think you have a good solution, feel free to share it in the comments. As always, I’ll share an example in the comments.

A Little Recap

With all that out of the way, let’s take a look at all our solutions again:

# Statically defined list
my_list = [2, 5, 6]

# Appending using slice assignment
my_list[len(my_list):] = [5]  # [2, 5, 6, 5]

# Appending using append()
my_list.append(9)  # [2, 5, 6, 5, 9]

# Appending using extend()
my_list.extend([-4])  # [2, 5, 6, 5, 9, -4]

# Appending using insert()
my_list.insert(len(my_list), 3)  # [2, 5, 6, 5, 9, -4, 3]

By far, this has been one of my favorite articles to write in awhile. There’s nothing quite like learning something new while writing a short response to the question “How do I add an item to a list?”

If you liked this article, help me get it in front of more eyes by giving it a share. In addition, you can show your support by hopping on my mailing list, joining me on Patreon, or subscribing to my YouTube channel. Likewise, you might benefit from additional Python resources like my Python 3 Beginner Cheat Sheet. Otherwise, check out some of these related articles:

Other than that, that’s all I’ve got! Thanks again for your support. See you next time!

The post How to Add an Item to a List in Python: Append, Slice Assignment, and More appeared first on The Renegade Coder.

]]>
https://therenegadecoder.com/code/how-to-add-an-item-to-a-list-in-python/feed/ 0 20713
How to Check if a String Contains a Substring in Python: In, Index, and More https://therenegadecoder.com/code/how-to-check-if-a-string-contains-a-substring-in-python/ https://therenegadecoder.com/code/how-to-check-if-a-string-contains-a-substring-in-python/#respond Mon, 06 Jan 2020 06:40:00 +0000 https://therenegadecoder.com/?p=20607

One concept that threw me for a loop when I first picked up Python was checking if a string contains a substring. After all, in my first language, Java, the ...

The post How to Check if a String Contains a Substring in Python: In, Index, and More appeared first on The Renegade Coder.

]]>

One concept that threw me for a loop when I first picked up Python was checking if a string contains a substring. After all, in my first language, Java, the task involved calling a method like indexOf() or contains(). Luckily, Python has an even cleaner syntax, and we’ll cover that today.

To summarize, we can check if a string contains a substring using the in keyword. For example, "Hi" in "Hi, John" returns true. That said, there are several other ways to solve this problem including using methods like index() and find(). Check out the rest of the article for more details.

Table of Contents

Problem Description

A common problem in programming is detecting if a string is a substring of another string. For example, we might have a list of addresses stored as strings, and we want to find all addresses on a certain street (e.g. Elm Street):

addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"

In that case, we might check which addresses contain the street name (e.g. 123 Elm Street). How do we do something like this in Python?

In most programming languages, there’s usually some substring method. For instance, in Java, strings have an indexOf method which returns a positive number if the substring was found.

Even without a special method, most languages allow you to index strings like arrays. As a result, it’s possible to manually verify that a string contains a substring by looking for a match directly.

In the following section, we’ll take a look at several possible solutions in Python.

Solutions

As always, I like to share a few possible solutions to this problem. That said, if you want the best solution, I suggest jumping to the last solution.

Checking if String Contains Substring by Brute Force

Whenever I try to solve a problem like this, I like to think about the underlying structure of the problem. In this case, we have a string which is really a list of characters. As a result, what’s stopping us from iterating over those character to find our substring:

addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"

for address in addresses:
    address_length = len(address)
    street_length = len(street)
    for index in range(address_length - street_length + 1):
        substring = address[index:street_length + index]
        if substring == street:
            print(address)

Here, I’ve written a sort of nasty set of loops which iterate over all addresses, compute lengths of some strings, iterate over all substrings of the appropriate size, and prints the results if a proper substring is found.

Luckily, we don’t have to write our own solution to this. In fact, the entire inner loop is already implemented as a part of strings. In the next section, we’ll look at one of those methods.

Checking if String Contains Substring Using index()

If we want want to check if a string contains a substring in Python, we might try borrowing some code from a language like Java. As mentioned previously, we usually use the indexOf() method which returns an index of the substring. In Python, there’s a similar method called index():

addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"

for address in addresses:
    try:
        address.index(street)
        print(address)
    except ValueError:
        pass

Here, we call the index function without storing the result. After all, we don’t actually care what the index is. If the method doesn’t find a matching substring, it’ll throw an exception. Naturally, we can catch that exception and move on. Otherwise, we print out the address.

While this solution gets the job done, there’s actually a slightly cleaner solution, and we’ll take a look at it in the next section.

Checking if String Contains Substring Using find()

Interestingly enough, Python has another method similar to index() which functions almost identically to indexOf() from Java. It’s called find(), and it allows us to simplify our code a little bit:

addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"

for address in addresses:
    if address.find(street) > 0:
        print(address)

Now, that’s a solution I can get behind. After all, it’s quite reminscent of a similar Java solution.

Again, it works like index(). However, instead of throwing an exception if the substring doesn’t exist, it returns -1. As a result, we can reduce our try/except block to a single if statement.

That said, Python has an even better solution which we’ll check out in the next section.

Checking if String Contains Substring Using in Keyword

One of the cool things about Python is how clean and readable the code can be. Naturally, this applies when checking if a string contains a substring. Instead of a fancy method, Python has the syntax built-in with the in keyword:

addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"

for address in addresses:
    if street in address:
        print(address)

Here, we use the in keyword twice: once to iterate over all the addresses in the address list and again to check if the address contains the street name. As you can see, the in keyword has two purposes:

  • To check if a value is present in a sequence like lists and strings
  • To iterate through a sequence

Of course, to someone coming from a language like Java, this can be a pretty annoying answer. After all, our intuition is to use a method here, so it takes some getting used to. That said, I really like how this reads. As we’ll see later, this is also the fastest solution.

Performance

With all these solutions ready to go, let’s take a look at how they compare. To start, we’ll need to set the solutions up in strings:

setup = """
addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"
"""

brute_force = """
for address in addresses:
    address_length = len(address)
    street_length = len(street)
    for index in range(address_length - street_length + 1):
        substring = address[index:street_length + index]
        if substring == street:
            pass # I don't want to print during testing
"""

index_of = """
for address in addresses:
    try:
        address.index(street)
        # Again, I don't actually want to print during testing
    except ValueError:
        pass
"""

find = """
for address in addresses:
    if address.find(street) > 0:
        pass # Likewise, nothing to see here
"""

in_keyword = """
for address in addresses:
    if street in address:
        pass # Same issue as above
"""

With these strings ready to go, we can begin testing:

>>>> import timeit
>>> min(timeit.repeat(setup=setup, stmt=brute_force))
4.427290499999998
>>> min(timeit.repeat(setup=setup, stmt=index_of))
1.293616
>>> min(timeit.repeat(setup=setup, stmt=find))
0.693925500000006
>>> min(timeit.repeat(setup=setup, stmt=in_keyword))
0.2180926999999997

Now, those are some convincing results! As it turns out, brute force is quite slow. In addition, it looks like the error handling of the index() solution isn’t much better. Luckily, find() exists to eliminate some of that overhead. That said, in is the fastest solution by far.

As is often the case in Python, you’ll get the best performance out of common idioms. In this case, don’t try to write your own substring method. Instead, use the built-in in keyword.

Challenge

Now that you know how to check if a string contains a substring, let’s talk about the challenge. We’re going to write a simple address search engine which filters on two keywords rather than one: street and number. However, we may not get both pieces of information at the time of search. As a result, we need to deal with finding addresses which exactly match whatever keywords are available.

For this challenge, you can write any solution you want as long as it prints out a list of addresses that exactly matches the search terms. For instance, take the following list of addresses:

addresses = [
    "123 Elm Street",
    "123 Oak Street",
    "678 Elm Street"
]

If a user searches just “Elm Street”, then I would expect the solution to return “123 Elm Street” and “678 Elm Street”. Likewise, if a user searches “123”, then I would expect the solution to return “123 Elm Street” and “123 Oak Street”. However, if the user provides both “123” and “Elm Street”, I would expect the solution to only return “123 Elm Street”—not all three addresses.

Feel free to have fun with this. For example, you could choose to write an entire front end for collecting the street and number keywords, or you could assume both of those variables already exist.

In terms of input data, feel free to write your own list of addresses or use my simple example. Alternatively, you can use a website which generates random addresses.

Ultimately, the program needs to demonstrate filtering on two keywords. In other words, find a way to modify one of the solutions from this article to match the street, address, or both—depending on what is available at the time of execution.

In the comments below, I’ll share my solution. Feel free to do the same!

A Little Recap

And with that, we’re finished. As a final recap, here are all the solutions you saw today:

addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"

# Brute force (don't do this)
for address in addresses:
    address_length = len(address)
    street_length = len(street)
    for index in range(address_length - street_length + 1):
        substring = address[index:street_length + index]
        if substring == street:
            print(address)

# The index method
for address in addresses:
    try:
        address.index(street)
        print(address)
    except ValueError:
        pass

# The find method
for address in addresses:
    if address.find(street) > 0:
        print(address)

# The in keyword (fastest/preferred)
for address in addresses:
    if street in address:
        print(address)

As always, if you liked this article, make sure to give it a share. If you’d like more articles like this to hit your inbox, hop on my mailing list. While you’re at it, consider joining me on Patreon. Also, you might get some value out of my Python 3 Beginner Cheat Sheet which features code snippets like these.

If you’re interested in learning more Python tricks, check out some of these related articles:

Finally, check out seem of these Python resources on Amazon (ad):

Otherwise, that’s all I have. Thanks again for your support!

The post How to Check if a String Contains a Substring in Python: In, Index, and More appeared first on The Renegade Coder.

]]>
https://therenegadecoder.com/code/how-to-check-if-a-string-contains-a-substring-in-python/feed/ 0 20607
How to Perform a Reverse Dictionary Lookup in Python: Generator Expressions and More https://therenegadecoder.com/code/how-to-perform-a-reverse-dictionary-lookup-in-python/ https://therenegadecoder.com/code/how-to-perform-a-reverse-dictionary-lookup-in-python/#respond Fri, 03 Jan 2020 15:00:00 +0000 https://therenegadecoder.com/?p=18371

When it comes to dictionaries, looking up a value by key is easy, but what about the reverse? Today, we'll perform a reverse dictionary lookup in Python.

The post How to Perform a Reverse Dictionary Lookup in Python: Generator Expressions and More appeared first on The Renegade Coder.

]]>

Welcome to yet another Python tutorial. Today, we’re taking a look at dictionaries and how we can perform a reverse dictionary lookup. In words, how do we get a key from a dictionary given a value?

As it turns out, there are three main solutions. First, we could try explicitly looping over the dictionary using something like my_dict.items(). Alternatively, we could create a generator expression: next(key for key, value in my_dict.items() if value == value_to_find). Finally, we could invert the dictionary completely to retrieve the key like normal.

Table of Contents

Problem Introduction

Awhile back, I wrote an article about how to invert a dictionary. In other words, how do we swap keys and values in a dictionary? Well, as it turns out, sometimes we don’t need to flip an entire dictionary. All we need is a key given a value.

Normally when we use a dictionary, we pass it a key to retrieve a value. But, what if we want to retrieve a key given a value? In other words, what if we want to perform a reverse dictionary lookup. For example, given the following dictionary, we might want to retrieve the first key that matches the value “red”:

my_dict = {
  "color": "red", 
  "width": 17, 
  "height": 19
}

In this case, we would expect our solution to return “color”. Of course, there might be multiple keys that match. How do we decide which one to grab?

Luckily, we won’t be digging into the nuance in this article. Instead, we’ll be looking at a handful of solutions that return the first key or every key that matches the value.

Solutions

In this article, we’ll take a look at a few ways to perform a reverse ditionary lookup. As always, we’ll kick things off with a brute force solution. Then, we’ll look at some more sophisticated solutions.

Reverse Dictionary Lookup by Brute Force

Perhaps a straightforward way of solving this problem is to iterate over the dictionary until we find the value we’re looking for:

my_dict = {"color": "red", "width": 17, "height": 19}
value_to_find = "red"
for key, value in my_dict.items():
    if value == value_to_find:
        print(f'{key}: {value}')

In this case, we’re searching the dictionary for the value “red”. During each iteration, we’ll check if the value we’re looking for matches the current value. If it does, we print the results.

If we copy this solution verbatim, it will actually spit out all the matching keys. In this case, we’ll only see “color: red”. That said, a larger dictionary could yield duplicates.

At any rate, there are plenty of more interesting solutions ahead!

Reverse Dictionary Lookup Using a Generator Expression

Instead of looping over our dictionary explicitly, we could leverage a generator expression (PEP 289) which looks a lot like a list comprehension:

my_dict = {"color": "red", "width": 17, "height": 19}
value_to_find = "red"
key = next(key for key, value in my_dict.items() if value == value_to_find)
print(f'{key}: {value_to_find}')

Naturally, the difference between a list comprehension and a generator expression is that there’s no list created. In other words, we save memory and possibly time.

In the example above, instead of generating a list of all of the key-value pairs and iterating over them, we repeatedly generate a new key-value pair until we find one that matches. This clever bit of code is basically a condensed version of our loop from our brute forced solution. Of course, the iteration stops when we find what we need.

Again, be aware that this solution will only return the first key that matches our lookup value. If we wanted more than one key, we’d have to store the generator expression:

exp = (key for key, value in my_dict.items() if value == value_to_find)
next(exp) # First matching key
next(exp) # Second matching key

If we call next more times than there are matches, we get a StopIteration error. As a workaround, we can use a for-each loop directly:

exp = (key for key, value in my_dict.items() if value == value_to_find)
for key in exp:
    print(key)

Now, isn’t that nice?

Reverse Dictionary Lookup Using an Inverse Dictionary

As I mentioned in the problem description, we can always completely flip the dictionary:

my_dict = {"color": "red", "width": 17, "height": 19}
value_to_find = "red"
my_inverted_dict = {value: key for key, value in my_dict.items()}
key = my_inverted_dict[value_to_find]

If you haven’t had a chance to read the other article, basically this solution takes advantage of a dictionary comprehension. In other words, it constructs a new dictionary from the original dictionary. Naturally, the part that does the magic is value: key which reverses the mapping.

Unfortunately, this solution won’t work for every circumstance because not all values are hashable (e.g. lists), but it gets the job done. Likewise, it only saves the last key for any duplicate values. As a result, other possible keys are lost.

If we want a solution that generates a list of keys, we can do something like the following:

my_dict = {"color": "red", "width": 17, "height": 19}
value_to_find = "red"
my_inverted_dict = dict()
for key, value in my_dict.items():
    my_inverted_dict.setdefault(value, list()).append(key)
keys = my_inverted_dict[value_to_find]

In this example, we end up with a list of keys rather than a single key.

Performance

As always, let’s take a look at the performance of each of these solutions. First, we’ll need to set them up in strings:

setup = """
my_dict = {"color": "red", "width": 17, "height": 19}
value_to_find = "red"
"""

brute_force_single = """
for key, value in my_dict.items():
    if value == value_to_find:
        break
"""

brute_force_multi = """
for key, value in my_dict.items():
    if value == value_to_find:
        pass
"""

generator_single = """
next(key for key, value in my_dict.items() if value == value_to_find)
"""

generator_multi = """
exp = (key for key, value in my_dict.items() if value == value_to_find)
for key in exp:
    pass
"""

inverse_single = """
my_inverted_dict = {value: key for key, value in my_dict.items()}
my_inverted_dict[value_to_find]
"""

inverse_multi = """
my_inverted_dict = dict()
for key, value in my_dict.items():
    my_inverted_dict.setdefault(value, list()).append(key)
my_inverted_dict[value_to_find]
"""

For the sake of completeness, I adapted each solution to each possible scenario. Either we want a single key, or we want many keys. As a result, each test is labeled single or multi, respectively.

In terms of testing, here are the results:

>>> import timeit
>>> min(timeit.repeat(setup=setup, stmt=brute_force_single))
0.19409550000000309
>>> min(timeit.repeat(setup=setup, stmt=brute_force_multi))
0.3046430999997938
>>> min(timeit.repeat(setup=setup, stmt=generator_single))
0.6223289999998087
>>> min(timeit.repeat(setup=setup, stmt=generator_multi))
0.6531434000003173
>>> min(timeit.repeat(setup=setup, stmt=inverse_single))
0.5350638999998409
>>> min(timeit.repeat(setup=setup, stmt=inverse_multi))
1.2309030999999777

Weirdly enough, the generator expression solution is actually quite slow. Perhaps, there’s a bit of overhead with creating a generator expression. I was interested to see how this solution scales with larger dictionaries, so I updated the setup string and reran my tests:

>>> setup = """
my_dict = {"color": "red", "width": 17, "height": 19, "health": 15, "depth": 100, "direction": "north", "material": "metal", "power": 17, "strength": 17, "weight": 111, "x": 0, "y": 0, "z": 0, "song": "Madeline", "band": "The Wonder Years", "friend": "rupert"}
value_to_find = "red"
"""
>>> min(timeit.repeat(setup=setup, stmt=brute_force_single))
0.18737550000059855
>>> min(timeit.repeat(setup=setup, stmt=brute_force_multi))
0.9153716000000713
>>> min(timeit.repeat(setup=setup, stmt=generator_single))
0.5850626999999804
>>> min(timeit.repeat(setup=setup, stmt=generator_multi))
1.2661715000003824
>>> min(timeit.repeat(setup=setup, stmt=inverse_single))
1.4036990000004153
>>> min(timeit.repeat(setup=setup, stmt=inverse_multi))
5.085829500000727

Again, I was a bit bothered by the results, so I tried changing the value we were searching for:

>>> setup = """
my_dict = {"color": "red", "width": 17, "height": 19, "health": 15, "depth": 100, "direction": "north", "material": "metal", "power": 17, "strength": 17, "weight": 111, "x": 0, "y": 0, "z": 0, "song": "Madeline", "band": "The Wonder Years", "friend": "rupert"}
value_to_find = "The Wonder Years"
"""
>>> min(timeit.repeat(setup=setup, stmt=brute_force_single))
0.8808984999996028
>>> min(timeit.repeat(setup=setup, stmt=brute_force_multi))
0.9333926999997857
>>> min(timeit.repeat(setup=setup, stmt=generator_single))
1.303262800000084
>>> min(timeit.repeat(setup=setup, stmt=generator_multi))
1.295239500000207
>>> min(timeit.repeat(setup=setup, stmt=inverse_single))
1.3928389000002426
>>> min(timeit.repeat(setup=setup, stmt=inverse_multi))
5.030787800000326

Again, brute force has the best performance. When I looked into why, I found that there’s a bit of overhead as I suspected. If I had the time, I’d probably run each of this solutions through cProfiler as outlined in my performance article. That said, I’ll defer to the responses in this Stack Overflow thread.

Overall, it looks like each solution performs in the order they were presented. In other words, brute force is slightly faster than a generator expression. Meanwhile, flipping the dictionary can be extremely costly.

Challenge

With all the fun stuff out of the way, let’s take a look at your challenge. Since covering the reverse dictionary lookup, I thought it would be fun to challenge you with the following:

Look at all three solutions above (or 6 if you include the various requirements). Can you break down exactly why each solution performs the way it does? In other words, can you explain the differences in performance between each solution? Why would looping over a dictionary be faster than using a generator expression? Why wouldn’t flipping the dictionary be fastest?

As I alluded to previously, you can use any tools at your disposal to support your reasoning. For instance, you might try using cProfiler to exam the inner workings of each solution. Likewise, you might try running various tests like I did with timeit. Perhaps a plot of each solution under different workloads would help you figure out asymptotic runtimes.

Maybe, you don’t want to run any empirical testing tools at all. Instead, you want to look directly at the source code and trace what work it has to do to accomplish our task. Whatever you choose to do, make sure you share your results in the comments!

A Little Recap

And with that, we’re done! Here’s all the solutions from this article in one place:

my_dict = {"color": "red", "width": 17, "height": 19}
value_to_find = "red"

# Brute force solution (fastest) -- single key
for key, value in my_dict.items():
    if value == value_to_find:
        print(f'{key}: {value}')
        break

# Brute force solution -- multiple keys
for key, value in my_dict.items():
    if value == value_to_find:
        print(f'{key}: {value}')

# Generator expression -- single key
key = next(key for key, value in my_dict.items() if value == value_to_find)
print(f'{key}: {value_to_find}')

# Generator expression -- multiple keys
exp = (key for key, value in my_dict.items() if value == value_to_find)
for key in exp:
    print(f'{key}: {value}')

# Inverse dictionary solution -- single key
my_inverted_dict = {value: key for key, value in my_dict.items()}
print(f'{my_inverted_dict[value_to_find]}: {value_to_find}')

# Inverse dictionary solution (slowest) -- multiple keys
my_inverted_dict = dict()
for key, value in my_dict.items():
    my_inverted_dict.setdefault(value, list()).append(key)
print(f'{my_inverted_dict[value_to_find]}: {value_to_find}')

With all that out of the way, it’s time for me to ask you for a little help! Specifically, I’d love it if you hopped on my mailing list or even became a patron. In addition, I’m trying to grow my YouTube channel, so head on over and subscribe.

Alternatively, you can show your support by browsing The Renegade Coder shop. For instance, you might get some value out of the Python 3 Beginner Cheat Sheet which features tips for writing comprehensions, conditionals, and loops.

Otherwise, check out some of these Python resources on Amazon (ad):

If you have the time, I’d appreciate it if you stuck around to check out some of these related articles:

Otherwise, thanks for stopping by! I appreciate it.

The post How to Perform a Reverse Dictionary Lookup in Python: Generator Expressions and More appeared first on The Renegade Coder.

]]>
https://therenegadecoder.com/code/how-to-perform-a-reverse-dictionary-lookup-in-python/feed/ 0 18371
Democratic Synthesis: Taking Think-Pair-Share to the Next Level https://therenegadecoder.com/teach/democratic-synthesis-taking-think-pair-share-to-the-next-level/ https://therenegadecoder.com/teach/democratic-synthesis-taking-think-pair-share-to-the-next-level/#respond Mon, 30 Dec 2019 15:00:00 +0000 https://therenegadecoder.com/?p=20067

While exploring active learning this semester, I accidentally created my own extended activity which I've been jokingly calling Democratic Synthesis.

The post Democratic Synthesis: Taking Think-Pair-Share to the Next Level appeared first on The Renegade Coder.

]]>

During this past semester, I learned about active learning and how I could use it in the classroom. Oddly enough, I was already incorporating a lot of active learning in my classroom without even realizing it. In this article, I want to share one of my favorite active learning techniques which I jokingly named Democratic Synthesis.

Table of Contents

What Is Active Learning?

Before we dive into my technique, I should probably explain active learning. As the name suggest, active learning is any learning technique where students are engaged (i.e. any time the instructor isn’t lecturing).

Active learning techniques vary wildly. For example, asking students to briefly reflect on an activity or lesson would be enough to constitute active learning. Meanwhile, asking students to do something more involved like a gallery walk would also constitute active learning. The point of any active learning technique is to make sure students are engaged in the learning process.

Other common active learning strategies include:

  • Quick Write: ask students to respond to a brief writing prompt
  • Peer Instruction: ask students to respond to a multiple choice question, discuss their response with a friend, and choose a response again
  • Jigsaw: ask students to break up into groups to learn about a topic, assign each student a subtopic to study, and ask students to regroup to share their new knowledge

In the following section, we’ll take a look at a common active learning technique known as Think-Pair-Share. Naturally, it forms the basis for my wild idea, Democratic Synthesis.

What Is Think-Pair-Share?

Think-Pair-Share (TPS) is an active learning technique which leverages student collaboration. As the name suggests, there are three parts to TPS:

  1. Think about the topic or question currently posed
  2. Pair up with a student
  3. Share thoughts

For example, you might ask a class to think about the merits of Medicare for All, a current hot topic in the United States. This allows students to bring in their own knowledge of the subject before introducing any new information. After thinking time passes, you can ask students to discuss their thoughts with a peer.

In many cases, the “share” portion of the activity can be opened up to the class. Perhaps pairs could report out on what they discussed, or maybe this discussion sets up a more rigorous activity. All that really matters is that students get a chance to thank about their response before they discuss it with anyone in the class.

In my experience, TPS works well in a Computer Science class, but I prefer to follow other active learning techniques like Peer Instruction (and Democratic Synthesis). That way, you can introduce a problem that students can solve together—rather than speculate. That said, it might be fun to use TPS in a more design oriented course like game development.

What Is Democratic Synthesis?

Democratic Synthesis, as the fictional name implies, is a teaching technique where students come up with an answer to a problem as a class. In particular, it takes Think-Pair-Share to the next level.

First, we introduce a problem to the students. For a few minutes, students will try to solve the problem on their own. When they come up with a solution, we’ll ask them to submit it to an online discussion tool like TopHat. Then, they’ll pair up and discuss their thoughts.

Once all the solutions are posted and the students have had a chance to discuss them, we ask the students to vote for their favorite solution. In other words, which solution do they think best solves the problem.

Once a solution rises to the top, we have a couple options. We can either tackle the “best” solution as a class and determine if it’s valid, or we can leave that task to another round of Think-Pair-Share.

In either case, we’ll want to arrive at a correct solution. Usually, that means finding errors in the “best” solution and modifying them until the solution works. This iterative process allows students to make mistakes and learn from them in a safe environment.

Democratic Synthesis in Action

At this point, I figured I’d share the exact activity I did with my class using Democratic Synthesis. Then, I’ll talk about how that activity could be modified depending on your needs.

Step 1: State the Problem

For this lecture, I was covering recursion. At this point in the semester, the students were already quite comfortable with recursion conceptually, but they didn’t know exactly how to write their own recursive functions. As a result, I figured I’d ask them to tackle a challenging example as a class.

To kick off this activity, I asked the students to write me a recursive power function. Of course, there were some challenges weaved in. For example, the function didn’t have a return type. Instead, they had to propagate the results onto a reference type parameter (i.e. NaturalNumber).

Step 2: Accept Individual Solutions

As students were working out their solutions, I asked them to submit their results to a discussion board on TopHat which I kept open at the front of the class. In other words, students could see solutions begin to roll in.

For this portion of the activity, I didn’t set a time limit. Honestly, I wasn’t sure how long it would take them to come up with a solution, so I patiently monitored them throughout the process. As students began veering off-topic, I moved onto the next step.

Step 3: Discuss Solutions in Pairs

Once enough solutions had been submitted, I asked the students to discuss their solutions with each other. Here, the goal was to get students to compare two solutions to see what they could learn:

  • Was their solution correct? Could they prove it?
  • How did their partner’s solution compare? Was it faster/slower?
  • Did their partner try to solve the problem the same way? Is there more than one way to solve it?

Again, the discussions weren’t prompted in any way. I just asked students to get with a partner and share what they came up with. I’m not sure if prompting them with the questions above would be helpful.

Likewise, I didn’t time limit this portion of the activity either. However, it may be a good idea if time is a concern.

Step 4: Democratically Elect the “Best” Solution

After some discussion, I gave students the opportunity to elect the best solution in the class. In TopHat, this was as easy as telling students to “like” their favorite response. Then, I was able to sort the responses by “likes,” so we could crown a victor.

In my experience, students often pick an incorrect answer. For whatever reason, an incorrect solution will begin to get votes, and other students will see that. As a result, they will vote it up as well. Before too long, we’ll have a unanimous incorrect answer.

Step 5: Debrief

Now, this is my favorite part of the activity. In all this buildup, students will feel like they’ve really created something. As a result, we get to have fruitful discussions about the results.

When I first did this activity, my students selected an incorrect answer. At that point, I took the opportunity to go through the process of “proving” its correctness. For example, I tested a base case—it failed. Then, I asked students to share a fix. After some deliberation, we were able to adjust our code to handle the base case.

Again, I began the testing process. First, I tested the base case. Then, I tested the next smallest subproblem. After a few iterations, the students were confident that the improved solution worked.

To finish off the activity, I asked the students if there was anything they would change or do differently. Since everyone was satisfied, we stopped the activity.

Oddly enough, I did this activity again later. However, instead of testing the code myself, I asked my students to follow the same process I did in a round of TPS/Jigsaw. In other words, I picked the top five answers and broke the class up into five small groups. Then, I asked the students to test their group’s solution before discussing their findings. Ultimately, I asked each group to report out whether or not their solution worked and how they would fix it if it didn’t.

Observations and Caveats

After performing this activity twice, I felt like it was a huge hit. For one, students really enjoyed working through a problem like this with some structure. At no point were students overwhelmed, and failure was normalized. In other words, mistakes felt like an opportunity to learn rather than a punishment.

That said, there were definitely some concerns with this activity. For instance, the democratic vote might select a correct answer. In that case, what do you do? On one hand, you could go through the proving process anyway. Alternatively, you could select the top five answers, so you have a better chance of finding a solution that needs some work.

In addition, it’s important that students get a chance to solve the problem alone. Much like TPS, the thinking part is critical to ensuring that every student has something to bring to the table.

Finally, coming up with a good question is critical. Personally, I like to pick a question that is just out of reach for the students at their current ability level. In other words, think about what your students know and what you’d like them to learn by the end of the class. Then, work toward that goal organically with Democratic Synthesis.

Overall, I’m quite pleased with this activity, and I think other teachers might find value in it. I’m not sure how it would work outside of a Computer Science setting, but I’m sure it could be adapted just fine.

Happy Teaching!

And with that, there’s nothing left to chat about. If you liked this activity, and you think you might try it in your classroom, let me know! I’d like to hear how it goes. Otherwise, how might you change or adapt this activity to your own needs?

In the meantime, stick around and check out some of my other teaching related articles:

In addition, if you want to see more articles like these, hop on my mailing list or support me on Patreon. Likewise, I occasionally generate teaching resources which you can find in my shop. Finally, help me grow my YouTube channel by becoming a subscriber. Otherwise, thanks for stopping by!

The post Democratic Synthesis: Taking Think-Pair-Share to the Next Level appeared first on The Renegade Coder.

]]>
https://therenegadecoder.com/teach/democratic-synthesis-taking-think-pair-share-to-the-next-level/feed/ 0 20067
71 Python Code Snippets for Everyday Problems https://therenegadecoder.com/code/python-code-snippets-for-everyday-problems/ https://therenegadecoder.com/code/python-code-snippets-for-everyday-problems/#respond Fri, 27 Dec 2019 15:00:00 +0000 https://therenegadecoder.com/?p=20413

Everything you regularly Google just found its place in this list. Say hello to a growing list of Python code snippets for everyday problems.

The post 71 Python Code Snippets for Everyday Problems appeared first on The Renegade Coder.

]]>

If you’ve been following me for any amount of time, you know that I regularly publish Python code snippets for everyday problems. Well, I figured I’d finally aggregate all those responses in one massive article with links to all those resources.

Table of Contents

Everyday Problems

In this section, we’ll take a look at various common scenarios that arise and how to solve them with Python code. Specifically, I’ll share a brief explanation of the problem with a list of Python code solutions. Then, I’ll link all the resources I have.

Inverting a Dictionary

Sometimes when we have a dictionary, we want to be able to flip its keys and values. Of course, there are concerns like “how do we deal with duplicate values?” and “what if the values aren’t hashable?” That said, in the simple case, there are a few solutions:

# Use to invert dictionaries that have unique values
my_inverted_dict = dict(map(reversed, my_dict.items()))

# Use to invert dictionaries that have unique values
my_inverted_dict = {value: key for key, value in my_dict.items()}

# Use to invert dictionaries that have non-unique values
from collections import defaultdict
my_inverted_dict = defaultdict(list)
{my_inverted_dict[v].append(k) for k, v in my_dict.items()}

# Use to invert dictionaries that have non-unique values
my_inverted_dict = dict()
for key, value in my_dict.items():
    my_inverted_dict.setdefault(value, list()).append(key)

# Use to invert dictionaries that have lists of values
my_dict = {value: key for key in my_inverted_dict for value in my_map[key]}

For more explanation, check out my article titled “How to Invert a Dictionary in Python.” It includes a breakdown of each solution, their performance metrics, and when they’re applicable. Likewise, I have a YouTube video which covers the same topic.

Summing Elements of Two Lists

Let’s say you have two lists, and you want to merge them together into a single list by element. In other words, you want to add the first element of the first list to the first element of the second list and store the result in a new list. Well, there are several ways to do that:

ethernet_devices = [1, [7], [2], [8374163], [84302738]]
usb_devices = [1, [7], [1], [2314567], [0]]

# The long way
all_devices = [
    ethernet_devices[0] + usb_devices[0],
    ethernet_devices[1] + usb_devices[1],
    ethernet_devices[2] + usb_devices[2],
    ethernet_devices[3] + usb_devices[3],
    ethernet_devices[4] + usb_devices[4]
]

# Some comprehension magic
all_devices = [x + y for x, y in zip(ethernet_devices, usb_devices)]

# Let's use maps
import operator 
all_devices = list(map(operator.add, ethernet_devices, usb_devices))

# We can't forget our favorite computation library
import numpy as np 
all_devices = np.add(ethernet_devices, usb_devices)

If you’d like a deeper explanation, check out my article titled “How to Sum Elements of Two Lists in Python” which even includes a fun challenge. Likewise, you might get some value out of my YouTube video on the same topic.

Checking if a File Exists

One of the amazing perks of Python is how easy it is to manage files. Unlike Java, Python has a built-in syntax for file reading and writing. As a result, checking if a file exists is a rather brief task:

# Brute force with a try-except block (Python 3+)
try: 
    with open('/path/to/file', 'r') as fh:
        pass
except FileNotFoundError: 
    pass

# Leverage the OS package (possible race condition)
import os 
exists = os.path.isfile('/path/to/file')

# Wrap the path in an object for enhanced functionality
from pathlib import Path
config = Path('/path/to/file') 
if config.is_file(): 
    pass

As always, you can learn more about these solutions in my article titled “How to Check if a File Exists in Python” which features three solutions and performances metrics.

Converting Two Lists Into a Dictionary

Previously, we talked about summing two lists in Python. As it turns out, there’s a lot we can do with two lists. For example, we could try mapping one onto the other to create a dictionary.

As with many of these problems, there are a few concerns. For instance, what if the two lists aren’t the same size? Likewise, what if the keys aren’t unique or hashable? That said, in the simple case, there are some straightforward solutions:

column_names = ['id', 'color', 'style']
column_values = [1, 'red', 'bold']

# Convert two lists into a dictionary with zip and the dict constructor
name_to_value_dict = dict(zip(column_names, column_values))

# Convert two lists into a dictionary with a dictionary comprehension
name_to_value_dict = {key:value for key, value in zip(column_names, column_values)}

# Convert two lists into a dictionary with a loop
name_value_tuples = zip(column_names, column_values) 
name_to_value_dict = {} 
for key, value in name_value_tuples: 
    if key in name_to_value_dict: 
        pass # Insert logic for handling duplicate keys 
    else: 
        name_to_value_dict[key] = value

Once again, you can find an explanation for each of these solutions and more in my article titled “How to Convert Two Lists Into a Dictionary in Python.” If you are a visual person, you might prefer my YouTube video which covers mapping lists to dictionaries as well.

Checking if a List Is Empty

If you come from a statically typed language like Java or C, you might be bothered by the lack of static types in Python. Sure, not knowing the type of a variable can sometimes be frustrating, but there are perks as well. For instance, we can check if a list is empty by its type flexibility—among other methods:

my_list = list()

# Check if a list is empty by its length
if len(my_list) == 0:
    pass  # the list is empty

# Check if a list is empty by direct comparison (only works for lists)
if my_list == []:
    pass  # the list is empty

# Check if a list is empty by its type flexibility **preferred method**
if not my_list:
    pass  # the list is empty

If you’d like to learn more about these three solutions, check out my article titled “How to Check if a List in Empty in Python.” If you’re in a pinch, check out my YouTube video which covers the same topic.

Cloning a List

One of my favorite subjects in programming is copying data types. After all, it’s never easy in this reference-based world we live, and that’s true for Python as well. Luckily, if we want to copy a list, there are a few ways to do it:

my_list = [27, 13, -11, 60, 39, 15]

# Clone a list by brute force
my_duplicate_list = [item for item in my_list]

# Clone a list with a slice
my_duplicate_list = my_list[:]

# Clone a list with the list constructor
my_duplicate_list = list(my_list) 

# Clone a list with the copy function (Python 3.3+)
my_duplicate_list = my_list.copy()  # preferred method

# Clone a list with the copy package
import copy
my_duplicate_list = copy.copy(my_list)
my_deep_duplicate_list = copy.deepcopy(my_list)

# Clone a list with multiplication?
my_duplicate_list = my_list * 1  # do not do this

When it comes to cloning, it’s important to be aware of the difference between shallow and deep copies. Luckily, I have an article covering that topic.

Finally, you can find out more about the solutions listed above in my article titled “How to Clone a List in Python.” In addition, you might find value in my related YouTube video titled “7 Ways to Copy a List in Python Featuring The Pittsburgh Penguins.”

Retrieving the Last Item of a List

Since we’re on the topic of lists, lets talk about getting the last item of a list. In most languages, this involves some convoluted mathematical expression involving the length of the list. What if I told you there is are several more interesting solutions in Python?

my_list = ['red', 'blue', 'green']

# Get the last item with brute force using len
last_item = my_list[len(my_list) - 1]

# Remove the last item from the list using pop
last_item = my_list.pop() 

# Get the last item using negative indices *preferred & quickest method*
last_item = my_list[-1]

# Get the last item using iterable unpacking
*_, last_item = my_list

As always, you can learn more about these solutions from my article titled “How to Get the Last Item of a List in Python” which features a challenge, performance metrics, and a YouTube video.

Making a Python Script Shortcut

Sometimes when you create a script, you want to be able to run it conveniently at the click of a button. Fortunately, there are several ways to do that.

First, we can create a Windows shortcut with the following settings:

\path\to\trc-image-titler.py -o \path\to\output

Likewise, we can also create a batch file with the following code:

@echo off
\path\to\trc-image-titler.py -o \path\to\output

Finally, we can create a bash script with the following code:

#!/bin/sh
python /path/to/trc-image-titler.py -o /path/to/output

If you’re looking for more explanation, check out the article titled “How to Make a Python Script Shortcut with Arguments.”

Sorting a List of Strings

Sorting is a common task that you’re expected to know how to implement in Computer Science. Despite the intense focus on sorting algorithms in most curriculum, no one really tells you how complicated sorting can actually get. For instance, sorting numbers is straightforward, but what about sorting strings? How do we decide a proper ordering? Fortunately, there are a lot of options in Python:

my_list = ["leaf", "cherry", "fish"]

# Brute force method using bubble sort
my_list = ["leaf", "cherry", "fish"]
size = len(my_list)
for i in range(size):
    for j in range(size):
        if my_list[i] < my_list[j]:
            temp = my_list[i]
            my_list[i] = my_list[j]
            my_list[j] = temp

# Generic list sort *fastest*
my_list.sort()

# Casefold list sort
my_list.sort(key=str.casefold)

# Generic list sorted
my_list = sorted(my_list) 

# Custom list sort using casefold (>= Python 3.3)
my_list = sorted(my_list, key=str.casefold) 

# Custom list sort using current locale 
import locale
from functools import cmp_to_key
my_list = sorted(my_list, key=cmp_to_key(locale.strcoll)) 
 
# Custom reverse list sort using casefold (>= Python 3.3)
my_list = sorted(my_list, key=str.casefold, reverse=True)

If you’re curious about how some of these solutions work, or you just want to know what some of the potential risks are, check out my article titled “How to Sort a List of Strings in Python.”

Parsing a Spreadsheet

One of the more interesting use cases for Python is data science. Unfortunately, however, that means handling a lot of raw data in various formats like text files and spreadsheets. Luckily, Python has plenty of built-in utilities for reading different file formats. For example, we can parse a spreadsheet with ease:

# Brute force solution
csv_mapping_list = []
with open("/path/to/data.csv") as my_data:
  line_count = 0
  for line in my_data:
    row_list = [val.strip() for val in line.split(",")]
    if line_count == 0:
      header = row_list
    else:
      row_dict = {key: value for key, value in zip(header, row_list)}
      csv_mapping_list.append(row_dict)
    line_count += 1

# CSV reader solution
import csv
csv_mapping_list = []
with open("/path/to/data.csv") as my_data:
    csv_reader = csv.reader(my_data, delimiter=",")
    line_count = 0
    for line in csv_reader:
        if line_count == 0:
            header = line
        else:
            row_dict = {key: value for key, value in zip(header, line)}
            csv_mapping_list.append(row_dict)
        line_count += 1

# CSV DictReader solution
import csv
with open("/path/to/dict.csv") as my_data:
    csv_mapping_list = list(csv.DictReader(my_data))

In this case, we try to get our output in a list of dictionaries. If you want to know more about how this works, check out the complete article titled “How to Parse a Spreadsheet in Python.”

Sorting a List of Dictionaries

Once you have a list of dictionaries, you might want to organize them in some specific order. For example, if the dictionaries have a key for date, we can try sorting them in chronological order. Luckily, sorting is another relatively painless task:

csv_mapping_list = [
  {
    "Name": "Jeremy",
    "Age": 25,
    "Favorite Color": "Blue"
  },
  {
     "Name": "Ally",
     "Age": 41,
     "Favorite Color": "Magenta"
  },
  {
    "Name": "Jasmine",
    "Age": 29,
    "Favorite Color": "Aqua"
  }
]

# Custom sorting
size = len(csv_mapping_list)
for i in range(size):
    min_index = i
    for j in range(i + 1, size):
        if csv_mapping_list[min_index]["Age"] > csv_mapping_list[j]["Age"]:
            min_index = j
    csv_mapping_list[i], csv_mapping_list[min_index] = csv_mapping_list[min_index], csv_mapping_list[i]

# List sorting function
csv_mapping_list.sort(key=lambda item: item.get("Age"))

# List sorting using itemgetter
from operator import itemgetter
f = itemgetter('Name')
csv_mapping_list.sort(key=f)

# Iterable sorted function
csv_mapping_list = sorted(csv_mapping_list, key=lambda item: item("Age"))

All these solutions and more outlined in my article titled “How to Sort a List of Dictionaries in Python.”

Writing a List Comprehension

One of my favorite Python topics to chat about is list comprehensions. As someone who grew up on languages like Java, C/C++, and C#, I had never seen anything quite like a list comprehension until I played with Python. Now, I’m positively obsessed with them. As a result, I put together an entire list of examples:

# Define a generic 1D list of constants
my_list = [2, 5, -4, 6]

# Duplicate a 1D list of constants
[item for item in my_list]

# Duplicate and scale a 1D list of constants
[2 * item for item in my_list]

# Duplicate and filter out non-negatives from 1D list of constants
[item for item in my_list if item < 0]

# Duplicate, filter, and scale a 1D list of constants
[2 * item for item in my_list if item < 0]

# Generate all possible pairs from two lists
[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)]

# Redefine list of contents to be 2D
my_list = [[1, 2], [3, 4]]

# Duplicate a 2D list
[[item for item in sub_list] for sub_list in my_list]

# Duplicate an n-dimensional list
def deep_copy(to_copy):
    if type(to_copy) is list:
        return [deep_copy(item) for item in to_copy]
    else:
        return to_copy

As always, you can find a more formal explanation of all this code in my article titled “How to Write a List Comprehension in Python.” As an added bonus, I have a YouTube video which shares several examples of list comprehensions.

Merging Two Dictionaries

In this collection, we talk a lot about handling data structures like lists and dictionaries. Well, this one is no different. In particular, we’re looking at merging two dictionaries. Of course, combining two dictionaries comes with risks. For example, what if there are duplicate keys? Luckily, we have solutions for that:

yusuke_power = {"Yusuke Urameshi": "Spirit Gun"}
hiei_power = {"Hiei": "Jagan Eye"}
powers = dict()

# Brute force
for dictionary in (yusuke_power, hiei_power):
    for key, value in dictionary.items():
        powers[key] = value

# Dictionary Comprehension
powers = {key: value for d in (yusuke_power, hiei_power) for key, value in d.items()}

# Copy and update
powers = yusuke_power.copy()
powers.update(hiei_power)

# Dictionary unpacking (Python 3.5+)
powers = {**yusuke_power, **hiei_power}

# Backwards compatible function for any number of dicts
def merge_dicts(*dicts: dict):
    merged_dict = dict()
    for dictionary in dicts:
        merge_dict.update(dictionary)
    return merged_dict

If you’re interested, I have an article which covers this exact topic called “How to Merge Two Dictionaries in Python” which features four solutions as well performance metrics.

Formatting a String

Whether we like to admit it or not, we often find ourselves burying print statements throughout our code for quick debugging purposes. After all, a well placed print statement can save you a lot of time. Unfortunately, it’s not always easy or convenient to actually display what we want. Luckily, Python has a lot of formatting options:

name = Jeremy
age = 25

# String formatting using concatenation
print("My name is " + name + ", and I am " + str(age) + " years old.")

# String formatting using multiple prints
print("My name is ", end="")
print(name, end="")
print(", and I am ", end="")
print(age, end="")
print(" years old.")

# String formatting using join
print(''.join(["My name is ", name, ", and I am ", str(age), " years old"]))

# String formatting using modulus operator
print("My name is %s, and I am %d years old." % (name, age))

# String formatting using format function with ordered parameters
print("My name is {}, and I am {} years old".format(name, age))

# String formatting using format function with named parameters
print("My name is {n}, and I am {a} years old".format(a=age, n=name))

# String formatting using f-Strings (Python 3.6+)
print(f"My name is {name}, and I am {age} years old")

Keep in mind that these solutions don’t have to be used with print statements. In other words, feel free to use solutions like f-strings wherever you need them.

As always, you can find an explanation of all these solutions and more in my article titled “How to Format a String in Python.” If you’d rather see these snippets in action, check out my YouTube video titled “6 Ways to Format a String in Python Featuring My Cat.”

Printing on the Same Line

Along a similar line as formatting strings, sometimes you just need to print on the same line in Python. As the print command is currently designed, it automatically applies a newline to the end of your string. Luckily, there are a few ways around that:

# Python 2 only
print "Live PD",

# Backwards compatible (also fastest)
import sys
sys.stdout.write("Breaking Bad")

# Python 3 only
print("Mob Psycho 100", end="")

As always, if you plan to use any of these solutions, check out the article titled “How to Print on the Same Line in Python” for additional use cases and caveats.

Testing Performance

Finally, sometimes you just want to compare a couple chunks of code. Luckily, Python has a few straightforward options:

# Brute force solution
import datetime
start_time = datetime.datetime.now()
[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)] # example snippet
end_time = datetime.datetime.now()
print end_time - start_time

# timeit solution
import timeit
min(timeit.repeat("[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)]"))

# cProfile solution
import cProfile
cProfile.run("[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)]")

Again, if you want more details, check the article titled “How to Performance Test Python Code.”

Share Your Own Problems

As you can see, this article and its associated series is already quite large. That said, I’d love to continue growing them. As a result, you should consider sharing some of your own problems. After all, there has be something you Google regularly. Why not share it with us?

In the meantime, help grow this collection by hopping on my newsletter, visiting the shop, subscribing to my YouTube channel, and/or becoming a patron. In addition, you’re welcome to browse the following related articles:

Otherwise, thanks for stopping by! I appreciate the support.

The post 71 Python Code Snippets for Everyday Problems appeared first on The Renegade Coder.

]]>
https://therenegadecoder.com/code/python-code-snippets-for-everyday-problems/feed/ 0 20413
JuxtaMIDI: A MIDI File Visualization Dashboard https://therenegadecoder.com/code/juxtamidi-a-midi-file-visualization-dashboard/ https://therenegadecoder.com/code/juxtamidi-a-midi-file-visualization-dashboard/#respond Mon, 23 Dec 2019 15:00:00 +0000 https://therenegadecoder.com/?p=20105

Last spring, I put together a MIDI visualization dashboard with a partner for a grad school course. Today, I'm sharing the results.

The post JuxtaMIDI: A MIDI File Visualization Dashboard appeared first on The Renegade Coder.

]]>

Earlier this spring, I worked on a project to visualize music. Since it was a term project, I got to put a lot of work and energy into it, and naturally I wanted to share it with the world. Unfortunately, if you’ve ever done any work in academia, you know that a lot of work has to stay secret due to competition. Luckily, I no longer plan to publish a paper on this topic. Instead, I can finally share the results here! Meet JuxtaMIDI: a MIDI file visualization dashboard.

Table of Contents

Motivation

As I mentioned, in the spring, I worked on a data visualization project for a grad school course. In it, we were asked to form teams to develop a term project. Naturally, we were allowed to work on just about anything as long as it was data visualization related. Since I have a strong interest in music, I figured I’d try my hand at that.

Once we got into our teams, I worked with my partner, Stephen Wu, to flesh out some ideas for visualizing music. Ultimately, we decided to put together a MIDI file visualization dashboard.

MIDI, for those that might not know, is a music file format which thinks of music in terms of events. In other words, we don’t have to deal with any signal processing for visualization purposes. All the data we need is already at the note level. Of course, MIDI limits our application space, but it allowed us to create our dashboard a lot quicker.

Essentially, we wanted to create a dashboard which would allow musicians to see differences in MIDI files. In particular, we figured musicians could use it as a part of their practice regiment. In other words, they would record themselves playing a song then compare it directly to some expert recording. Given enough time, they would be able refine their technique until they mastered the song.

After a few months, we had a working dashboard which could be used to compare any number of MIDI recordings. That dashboard became known as JuxtaMIDI.

If you’d like to take a look at it, feel free to head on over to the JuxtaMIDI website hosted on GitHub pages. Naturally, you’ll need some MIDI files for demonstration purposes, so I’ve hosted a few in the homepage. There you can also find the sample report which outlines this project a bit more formally.

Design

The overall design of the JuxtaMIDI dashboard has 5 main panels of which 3 feature plots of the MIDI data (click to expand):

JuxtaMIDI Dashboard

In the following subsections, we’ll take a look at each panel in action.

Notes Played Plot

In the largest panel at the top of the dashboard, you’ll find the notes played panel:

JuxtaMIDI Notes Played Plot

Essentially, this is a plot of what notes are being play at what time. Specifically, the notes are organized from lowest to highest along the y-axis while time is listed on the x-axis. In this case, time is in in MIDI ticks. Although, it would be nice to see time in seconds.

To make this plot more interesting, we actually have a song playback feature which allows the user to play their recording against this plot. To track progress through the song, we use a vertical line. To help you remember which song was playing, we’ve encoded the time marker with the song’s color (more on that later):

JuxtaMIDI Notes Played Plot with Song Progress Marker

In addition, since songs tend to have a lot of notes, we gave this pane a scroll bar. That way, the plot would be a little easier to read.

Finally, each note has a popup which renders when you hover over it:

JuxtaMIDI Notes Played Plot with Tooltip

In it, you’ll see the note’s name, start time, velocity, and duration as well as which track it’s from.

Note Frequency Plot

Below the notes played plot on the left, you’ll find the note frequency plot:

JuxtaMIDI Note Frequency Plot

As the name suggests, the note frequency plot indicates how many times a note was played—with no relation to duration. Specifically, the x-axis features each note that appeared, sorted by the most frequent note. Meanwhile, the y-axis indicates the number of times a note was played.

The main idea behind this plot was to give a quick overview of wrong notes. As you can see in the example above, there are two recordings being compared. In this case, there are a few note counts that differ: C3, G4, F#4, F#3, C#3, and A3. To highlight that difference, we fade all pairs with the same frequency.

Finally, each bar has a bit of interactivity:

JuxtaMIDI Note Frequency Plot with Tooltip

Specifically, if we hover over a bar, we should see the note list alongside its frequency and which track it came from.

Note Velocity Plot

To the right of the note frequency plot, you’ll find the note velocity plot:

JuxtaMIDI Note Velocity Plot

Basically, this plot features the velocity (i.e. volume) of the music over time. On the y-axis, we feature the velocity. Meanwhile, on the x-axis, we feature the time in the same units as the notes played plot (i.e. ticks).

Like the notes played plot, this plot also features a progress marker:

JuxtaMIDI Note Velocity Plot with Song Progress Marker

Theoretically, we would use this marker to identify the current dynamic level (e.g. piano, forte, etc.) of the piece. Of course, it’s a bit hard to identify that here, but that’s the idea.

Of all the plots, this one is the most ambiguous. Ideally, we wanted to be able to show dynamics, so we could compare swells in recordings. Unfortunately, we were never really able to get something that resembled dynamics. In our current plot, we show the range of velocities by drawing upper and lower bounds for the velocities at any given time. Of course, we’d want some way to represent dynamics with a single curve.

Song Management Pane

On the left side of the dashboard, you’ll find the song selection pane:

JuxtaMIDI Song Management Pane

Initially, the dashboard is empty. When you want to add a song to you it, you click the “Add MIDI” button, and you’ll be prompted to select a MIDI file from your computer.

Once a song is loaded in, you’ll see all the graphs populate with the new data. In addition, a new element will be added to the song management pane: the new song. Notice how the background of the song has a color. That color matches all elements related to that song in each of the three plots.

In addition to providing an encoding mechanism for each song, this pane also features individual controls for each song. For example, the slider on the bottom left toggles the song on and off all plots. In this case, if we turn the Mario-Few.mid song off, we’ll remove the dark blue markings from each plot:

JuxtaMIDI Dashboard with One Song Toggled Off

Likewise, hitting the play button on a song causes the time-based plots to feature a time marker as mentioned previously.

Meanwhile, the pencil icon can be used to change the name of the song. For example, a file lacking a descriptive name could be renamed in this interface for clarity:

JuxtaMIDI Song Management Pane with One Song Toggled Off

Finally, the trash bin icon can be used to completely remove a track from the dashboard. To get the track back, the user would have to reload it.

At this time, song colors are assigned in a fixed order based on the order in which they’re loaded into the dashboard. If we were to add another song, the next color in the sequence would be green:

JuxtaMIDI Dashboard with Three Songs

In the future, it would be nice to be able to select colors. For now, this process is automated for the user.

Plot Selection Pane

Finally, we included a section of the UI dedicated to isolating each plot for full screen viewing purposes:

JuxtaMIDI Plot Selection Pane

Each button can be used to view a different plot. For example, if we want to blow up the notes pane, we could click the notes button. As a result, the notes pane will become the focus of the dashboard:

JuxtaMIDI Dashboard with Notes Played Pane Expanded

As you can imagine, each of the plots can be blown up in this manner for your viewing pleasure.

Implementation

For anyone interested, I’m happy to share how this dashboard was built. Of course, you’re welcome to take a look at the code yourself over on GitHub.

In terms of tech, we used vanilla JavaScript, HTML, and CSS—no frameworks. Specifically, the layout of the dashboard was built using CSS grid. Meanwhile, the plots were generated in D3 and the interactivity elements were added using Popper and Tippy. From a MIDI parsing perspective, we used the midi-parser library. Then, for playing MIDI files, we used the MIDIPlayer library.

Together, these tools were used to generate the JuxtaMIDI dashboard. Since it has been awhile since I’ve touched the code, I don’t feel comfortable sharing too many details. In addition, the code was hastily pieced together, so there are a lot of opportunities for bug fixes and overall improvements.

That said, we put together a slightly more official report as a part of the submission if you’d like to check that out. In it, you’ll see a lot of the same information here. In addition, however, you’ll also see a case study and several references.

Support

Hey, if you thought this project was cool, feel free to let me know. I currently don’t plan on working on it anymore, but I’d be happy to maintain it if anyone wanted to see it grow.

As always, thanks for sticking around. If you’d like to see more content like this, hop on my newsletter or become a patron. In addition, I have a modest store with a few Python products in it which I’d love for you to review. Finally, check out some these books on data visualization on Amazon:

Also, now that you know I’m interested in music, feel free to check out my niche site, Trill Trombone. In my spare time, I’ve been trying to build up a small income generating website covering one of my hobbies, music. Show it some love!

Finally, check out some of these related articles:

Otherwise, thanks for stopping by! See you next time.

The post JuxtaMIDI: A MIDI File Visualization Dashboard appeared first on The Renegade Coder.

]]>
https://therenegadecoder.com/code/juxtamidi-a-midi-file-visualization-dashboard/feed/ 0 20105
Reflecting on My Fourth Semester of Teaching https://therenegadecoder.com/teach/reflecting-on-my-fourth-semester-of-teaching/ https://therenegadecoder.com/teach/reflecting-on-my-fourth-semester-of-teaching/#respond Fri, 20 Dec 2019 15:00:00 +0000 https://therenegadecoder.com/?p=19900

And just like that, another semester of teaching has come and gone. Let's talk about how my fourth semester of teaching went!

The post Reflecting on My Fourth Semester of Teaching appeared first on The Renegade Coder.

]]>

Welcome back to yet another teaching reflection. This time around, I’m sharing a reflection on my fourth semester of teaching. Spoiler alert: I taught a different class!

Table of Contents

Logistics

Like last semester, I taught CSE 2221, a software components course at The Ohio State University. Of course, this time around, I ran the course! In other words, I taught every lecture and ran every lab. In addition, I even had my own graders.

In terms of schedule, I had the evening slot that ran 4:10 – 5:05 PM from Tuesday to Friday. On Tuesdays and Thursdays, I had a lecture. On Wednesdays and Fridays, I had a lab. Conveniently, I also held my office hours immediately after class Tuesday through Thursday. Outside of that, I planned for class.

In terms of material, I taught mostly object oriented programming (OOP) and software craftsmanship with some data structures sprinkled in. Specifically, I covered the following topics:

  • Recursion
  • Natural Numbers
  • XML/RSS
  • Expression Trees
  • Queues
  • Stacks
  • Sequences
  • Sets
  • Design by Contract

If that seems like an odd collection of topics, I definitely agree. That said, we made it work. Unfortunately, there wasn’t a lot of theory to back up these topics, so students often learned them only at a surface level.

Weirdly enough, this course served as a bit of a weed out class for students. In particular, they had to pass this course to get into the major, so I held an awfully large amount of power over the future of my students. As a result, I took great care to ensure that everyone had a chance to pass.

That said, final grades were fairly poor overall:

GradeTotal
A4
A-0
B+4
B7
B-4
C+8
C5
C-0
D+0
D0
E0
EN5

As you can see, a handful of students didn’t even manage to finish the course. Obviously, I’m not super proud of these grades, and I do think most of the students deserved better. Unfortunately, this is sort of what happens when you have such an exam-heavy course without a curve.

In the future, I’ll be looking for ways to improve student morale and success, but I’ll talk about that more later.

Reflection

This semester of teaching was by far the hardest on me emotionally. In fact, there was little good I could lean on here, but I’ll share my holistic reflection anyway.

The Good

One of my favorite parts of this semester was passing off grading to a couple of undergraduate students. As you may recall, I really, really don’t like grading, so I was thankful to have some help. Of course, working with other people always comes with some messiness, but we’ll chat about that later.

In addition, I really enjoyed working with my students this semester. Since I was able to cover more challenging topics, my students typically had more insightful questions. In fact, I think this was my most active class so far. At least half of my students answered questions on a regular basis.

On top of that, I learned about a cool teaching technique called Peer Instruction (PI) which I leveraged literally every class after learning it. In particular, I follow a style of PI which works in a question format. Recently, I shared my process in another article.

Finally, I took a practicum course which helped me learn a ton of new ways to support my students. For instance, I learned about Classroom Assessment Techniques (CATs) which helped me figure out what my students weren’t learning.

The Bad

All that said, I’m slightly less happy with the material I’m teaching. Much of it is practical without a good theory background. As a result, most of the students hit a roadblock in their understanding of the material which can be frustrating.

In addition, we have a lot of software craftsmanship rules which aren’t necessarily realistic, and the students tend argue about them a lot—especially if it factors into their grade. Likewise, I have a hard time rationalizing the rules sometimes. For example, why aren’t we allowed to have multiple return statements in a method?

Finally, I’m not sure I like working with computer science students. While I enjoyed their insight, I didn’t really have to do much to motivate them. They were all there because they wanted to be there, so I didn’t get much of that excitement you get when you introduce something cool to someone for the first time. Oh well!

The Ugly

By far, this was my worst semester of teaching to date. In general, there were basically three main issues I faced:

  • I didn’t know the material well enough
  • I wasn’t prepared to manage graders
  • I didn’t love the culture

Coming into this class, I wasn’t ready to teach some of the topics. Either, I wasn’t all that interested in the subject, or it was pretty new to me. As a result, I spent a lot of time this semester brushing up on topics which is hard to do while balancing classes and research.

In addition, this was the first semester that I had to manage people other than my own students. As a result, I had to deal with a lot of the messiness that comes up. If you’re interested in learning more about my struggles with managing my graders, I wrote an entire article about it. Likewise, I’ve had some issues with mentorship as well.

Finally, as I’ve hinted in the previous section, it can be hard to work directly in the culture of elitism and gatekeeping. As someone who is trying to bring some compassion to the engineering space, I find it demoralizing to work with people who are already in too deep.

All that said, I think next semester will be better, but we’ll see!

The Horrendous

If you thought things couldn’t get any worse, think again. This semester, I had the displeasure of dealing with a few mischievous students. If you recall, I had a similar issue in my first semester where a student routinely manipulated me for their own gain. Well, it happened again. If you’re interested, I’ve already written a premium article which shares all the details, but I’m happy to summarize.

Throughout the semester, I had a handful of students try to get away with plagiarizing their work. In particular, we caught a few students working in groups and/or stealing code from online. To make matters worse, when they were caught, we couldn’t even get a hold of them to report it.

On another occasion, I had a student plead for some additional exam time—not once but twice. First they showed up late to a midterm then they showed up late to a final. Of course, I didn’t know how to handle the situation in either case, so I told them there was nothing I could do. After all, I didn’t want other students to feel like I was giving their peer some special privilege. At the same time, however, I like to give students the benefit of the doubt. Unfortunately, there is no right answer.

By far, the worst part of teaching has been these little situations that crop up where I don’t necessarily know how to properly handle them. There were too many of those this semester.

Changes

One thing that I never thought to do with my reflections was think about how I could make changes going forward. Of course, I feel some of those changes were implied, but I think it might be even better if I documented them explicitly.

To be honest, I was inspired to include this section by the prompt for a reflection I had to write for a course. In it, we had to talk about changes we wanted to make in the future. Here’s my response if you’re interested. Of course, I’ll more or less be outlining the same responses below.

Setting Up Better Grader Expectations

One of the things I majorly failed to do this semester was setup grading expectations with my undergraduate teaching assistants. For whatever reason, I felt a short meeting explaining my teaching philosophy and discussing course logistics would be enough. Unfortunately, I was wrong.

As mentioned previously, I ran into issues throughout the semester where my graders—through no fault of their own—handed out less than fair grades to our students. On several occasions, I had to play mediator between my students and my graders as grades continued to come back negative.

In the future, I’d like to share some examples of how I grade with my graders. Right now, I have several projects already graded that I could pass off as examples. Perhaps if I could gather an example of each appropriate grade tier, it would help my future graders properly assess future students.

In addition, I’d like to put together a set of rubrics—even if I only use them for a semester. That way, I can publish these rubrics, so my graders and students are on the same page. Nothing is worse than a mismatch of expectations between graders and students.

Establishing Better Alternatives to Plagiarism

While I feel like I’m good at developing positive relationships with my students, that doesn’t always make me approachable in every scenario. For example, the pressure of deadlines can sometimes compel students to cheat rather than to ask for help. Next semester, I want to ensure that students feel comfortable reaching out to me for help like extensions when they’re in need.

First, I’ll make sure to take a more serious stance on academic integrity. In the past, I sort of blew it off because I figured students wouldn’t consider it if they felt like they were breaching my trust. In other words, if I could connect with my students, they wouldn’t feel comfortable cheating. Unfortunately, that’s not the case. As a result, I want to outline the policy better in my first lecture.

Then, I’ll probably add a little snippet to the course policies about plagiarism where I’ll give them a few alternatives. Obviously, I don’t want students abusing these opportunities, but it sure beats cheating. No one enjoys going through the academic integrity process.

Including Real-World Examples in My Curriculum

One of the things I got dinged on in my teaching reviews this semester was my lack of real-world examples. Oddly enough, I feel like everything we do in programming courses is related to real-world processes, but we’re not always explicit in where new concepts apply. As a result, I want to make sure I tie in lessons with real-world examples.

At this point, I’m not sure what that’ll look like. For example, when I teach about trees, I also teach about a real-world application of trees like XML. Is that enough? After all, we take this a step further and use this knowledge of XML and trees to read RSS feeds. Is that enough? Honestly, I’m not sure.

In the future, I’ll have trouble figuring out what constitutes a real-world example in programming. Perhaps I’ll eventually have to dig into a real-world codebase. Who knows?

Feedback

As always, I like to share a bit of feedback from my students. Like usual, I’ll share all the results from my personal survey as well as the university sanctioned Student Evaluation of Instruction (SEI).

Once again, I’m too lazy to extract the latest reviews, so I’ll just show everything in aggregate. That said, at some point, I’ll probably do a semester breakdown. At any rate, here’s the cumulative course breakdown:

As you can see, I already have more responses to my personal survey from the new class than the class I taught for two semesters. With that in mind, let’s get into it!

Level of Effort

To kick off the survey, I like to ask the students how much effort they felt they had to put into the course to do well:

As you can see, students often feel that they have to work pretty hard. Not once in my last four semesters has a student claimed the course was easy. Apparently, learning to program is tough.

Contribution to Learning

The next question I like to ask is about how much the student felt the course contributed to their learning. In particular, I ask questions about their knowledge of the material before and after the course. Then, I ask them how much knowledge is needed to complete the course and how much they felt the course contributed to that knowledge:

One thing that really sticks out to me is how much stronger my students feel at the end of the course than at the beginning. Of course, I suspect that’s because I teach a sophomore course now.

For the first time, I’m finding that students don’t think the course contributes anything to their learning. Honestly, that’s a bit rough to hear, but I’m sure there are things I can improve.

In addition, this is the first time a student felt they didn’t really learn much. Perhaps there are things I can be doing to ensure all my students get something out of the course.

All that said, I still think these curves are great. There’s a clear distinction between where students were at the start of the course and where they ended up. In addition, many of the students felt the course contributed a lot to their growth.

Skill and Responsiveness of Instructor

At this point in the survey, I like to ask a few questions about my abilities as an instructor.

This is one of those charts that I’m always really proud to share as it isolates how students feel about me by ignoring uncontrollable issues like course structure and curriculum. That said, this is the first semester I’m seeing red, so that’s unfortunate.

One thing I noticed is that students almost unanimously agree that I’m available and helpful which is awesome. After all, I might not be the best lecturer or grader, but I make up for it in being supportive and approachable.

Course Content

Once I get my selfish fill, I like to ask a few questions about the curriculum. For example, was the course workload appropriate? And, were the learning objectives clear?

This time around, I got quite a bit of diversity in responses. For example, this is the first time students felt the learning objectives weren’t clear. In addition, a few students felt like they couldn’t participate fully. I’d be curious to know why!

All that said, I still feel pretty good about these curves. If anything, I figure I’ll be more comfortable with this course next semester, so a lot of these confusion issues should be addressed.

Course Selection

At this point, I like to ask students why they took the course. Unfortunately, this chart is slowly losing its value because the course I teach now (CSE 2221 – Software Components) is a major requirement. That said, I figure I’ll continue to share it for historical purposes:

As you can see, that yellow slice of the pie continues to shrink.

Value

At this point, we get to the fun stuff! First, I open up the opportunity for students to tell me what they felt was the most valuable part of the course.

Every semester, I get a handful of people who really like the labs:

Projects/Labs

Anonymous, Fall 2019

Labs and the interactive class activities were the most helpful.

Anonymous, Fall 2019

The lectures were great for being exposed to the knowledge that was going to be taught, but the labs really helped by actually being able to put that knowledge into real examples and programs.

Anonymous, Fall 2019

At least one of my students appreciated my attempts at clarifying our course discipline:

Helping to be straightforward with the course expectations (no break, no multiple returns, etc.) as well as explaining which ones were more important.

Anonymous, Fall 2019

In addition, a few students appreciated the office hours:

I liked the office hours and being able to ask questions directly to the person that was grading my assignments in order to get back points or avoid mistakes in the future.

Anonymous, Fall 2019

The office hours and meeting with graders to really understand the shortcomings of the projects.

Anonymous, Fall 2019

Finally, one student just straight up enjoyed my effort:

The instructor was extremely good and put lots of effort into overall engagement.

Anonymous, Fall 2019

Overall, I’d say it’s nice to receive a bit of validation for the work you put into a course. I always like to look back on these quotes when I’m having a rough day.

Improvements

When you collect feedback, it’s always a good idea to take some good with some bad. After asking students about their favorite part of the course, I like to ask them what they would like to see in terms of improvements.

One of the biggest critiques I got from students this semester was grading, so naturally I expected to see that a bit here as well:

Better grading on projects (from TAs)

Anonymous, Fall 2019

The comments on the projects didn’t always match up with the scores. (There were times when I got extra comments critiquing my coding style, but they were not factored into my grade). This made comments confusing because I don’t understand what is actually wrong with my code and what is a style suggestion. I also felt that studying for exams was more difficult than it should’ve been. I appreciate when the homework correlated to the labs or the labs correlated to the projects. It was helpful for completing the labs and projects and for studying for the exam.

Anonymous, Fall 2019

This doesn’t have much to do with you as an instructor, but the project grades got so picky and harsh it started to become hard to actually want to do them and learn from them because it felt like no matter how much work was put into [them], a bad or subpar grade would be the result. It became hard to be motivated about the projects after so much of this.

Anonymous, Fall 2019

Outside of grading, I got a lot of insightful feedback around course organization:

I would make it so that the projects weren’t so heavily graded but at the same time encourage more time on projects that everyone struggled with instead of moving on.

Anonymous, Fall 2019

I would use more practical aspects of Java rather than use osu components like Queue and NaturalNumber

Anonymous, Fall 2019

Make the labs relate to the project, or add on in similar ways. The labs seemed kind of random and randomly placed.

Anonymous, Fall 2019

I would make sure that the students had a little more time to do the projects

Anonymous, Fall 2019

Overall, I think most of this is manageable. Of course, I don’t have control over all of it, but I can make steps toward improving most of it.

Testimonials

At the end of the survey, I like to leave the space open for honest testimonials. For me, these serve two purposes:

  • I have something to look at on a rough day.
  • I have something to reference in future employment opportunities.

This time around, I got quite a few responses, so I’ll try to keep this short by sharing my favorites:

Jeremy absolutely carried the course. For anyone from a beginner to someone using this as a refresher, this is ideal in most (if not all) ways. The in-class coding challenges and subsequent discussions were very useful in gathering some alternative perspectives, and explanations were clear and concise.

Anonymous, Fall 2019

Mr. Grifski is an awesome teacher who seems really interested in what he has to teach to the class. He is fun and keeps the class engaging while also being informative.

Anonymous, Fall 2019

I enjoy the interactive sections of class (where you ask a question on Tophat then we work with people around us to solve the answer). I wish I had the opportunity to look back at those questions to review for exams as well. I also really like the interactive activities because its a way I can check for understanding and it’s simpler than the labs or projects (because there are a lot of small details to check for in them). I think having extra problems like these is really helpful for learning about different components because it’s a way to test more information than what might be reviewed in the homework, labs or projects.

Anonymous, Fall 2019

I thought you did an amazing job teaching the course and rolling with the punches that the graders threw in terms of our project grades being so harsh. It’s clear that you have a real passion for teaching and a great understanding of how teaching should be done to make students interested in learning about the topics provided. I enjoyed the in class questions and game at the end of the year. I thought your grading of the one project (project 10) and the exams was fair and helpful for the future. Overall, while this class can be a pain with the course guidlines and discipline, you handled the material and questions having to do with this very well and you made even the dry subjects worth showing up to lecture for. I hope you continue down the current teaching path your on because I really enjoyed being a part of it

Griffen, Fall 2019

Every semester, I seem to get one of these really supportive testimonials that reminds me why I do what I do. Despite how hard this semester was, I’m thankful for the students who supported me along the way.

Article Idea

Since a lot of my students know about my website, I like to give them an opportunity to share article ideas. For the first time, I actually had a single taker:

How coding has changed so far, how you expect it to change in the future…

Anonymous, Fall 2019

Unfortunately, I don’t know much history of programming, but it sounds like a great article. Any takers?

Student Evaluation of Instruction

If you look back through the previous reflections, you’ll notice that this section is called “Student Evaluation of Instructor.” As it turns out, that’s not what SEI stands for. Instead, it stands for “Student Evaluation of Instruction” which implies that the review is somehow more holistic. As a result, I’ll review the results as such.

As always, I like to dump the table of results below, but you’re welcome to check out the complete report:

QuestionInstructorDepartmentCollegeUniversity
The subject matter of this course was well organized 4.574.254.254.27
This course was intellectually stimulating 4.394.204.174.18
This instructor was genuinely interested in teaching 4.874.314.434.46
The instructor encouraged students to think for themselves 4.834.294.384.40
The instructor was well prepared 4.744.284.354.38
The instructor was genuinely interested in helping students 4.964.324.444.44
I learned a great deal from this instructor 4.744.084.154.17
The instructor created an atmosphere conducive to learning 4.874.144.224.28
The instructor communicated the subject matter clearly 4.784.114.184.22
Overall, I would rate this instructor as 4.914.244.364.37

While I thought I had a really rough semester, my students were extremely supportive. In fact, these are some of my best SEIs to date. I guess that shows that as long as you put your students’ needs first, your shortcomings don’t really matter.

All that said, I’d like to share some of the great feedback I got:

Although there were some difficulties associated with this being his first time teaching the course, Jeremy is clearly passionate about teaching, and wanted us to succeed. If he had more control over the course material I think it would’ve been a much smoother experience overall. That being said, he worked really hard to make sure we understood what was going on –great professor.

Anonymous, Fall 2019

Really enjoyed this professor throughout this course. He genuinely cared about his students and his teaching philosophy is great. One of the best teachers I’ve encountered and being able to have someone like that in a class that matters is much appreciated. Overall amazing teacher.

Anonymous, Fall 2019

Fantastic teacher and I wish he taught software 2 and foundations as well just so I could have him again

Anonymous, Fall 2019

Amazing instructor. He was involved with students and was very understanding.

Anonymous, Fall 2019

If stuff like this doesn’t make you feel good after such a hard semester, nothing will. I’m thankful for these opportunities to get some candid feedback from students; it always makes for a great end to a semester.

Looking Forward

At this point, my reflection is already quite long (4000+ words), so I think I’ll cut it off there. After all, I’d prefer not to spend my whole break writing!

Anyway, thanks again for taking some time to follow me on this journey. In addition, special thanks to all my past students who have helped me grow and develop. I’m really looking forward to teaching full time—if only this pesky PhD wasn’t in the way.

While all this support is great, I have to ask for a little more. In particular, I’d love it if you hopped on my mailing list or even became a patron. Right now, I’m comfortably supported by five patrons, but I’d love to grow that number in the coming year.

In the meantime, you might find some of the following books from Amazon valuable:

Likewise, stick around and check out some of my previous reflections:

Otherwise, thanks again for your support! I appreciate it.

The post Reflecting on My Fourth Semester of Teaching appeared first on The Renegade Coder.

]]>
https://therenegadecoder.com/teach/reflecting-on-my-fourth-semester-of-teaching/feed/ 0 19900
Dealing With Difficult College Students https://therenegadecoder.com/teach/dealing-with-difficult-college-students/ https://therenegadecoder.com/teach/dealing-with-difficult-college-students/#respond Mon, 16 Dec 2019 15:00:00 +0000 https://therenegadecoder.com/?p=20135

As I gain experience, I'm continually reminded that there will always be difficult college students. This semester was no different.

The post Dealing With Difficult College Students appeared first on The Renegade Coder.

]]>
To view this content, you must be a member of The Renegade Coder's Patreon at $5 or more
- Click "Read more" to unlock this content at the source

The post Dealing With Difficult College Students appeared first on The Renegade Coder.

]]>
https://therenegadecoder.com/teach/dealing-with-difficult-college-students/feed/ 0 20135