How to Build a Tiered List Maker with Python
Have you ever wanted an easy way to categorize and rank a collection of items? Whether you‘re evaluating albums, movies, restaurants or something else entirely, a tiered list provides a great visual format for slotting items into ranked buckets. In this article, we‘ll walk through how to build a fully-functional tiered list maker using Python.
We‘ll cover everything from the basic data structures to the visual rendering of the final list. By the end, you‘ll have a flexible command-line tool that can generate tiered lists for any domain. Let‘s get started!
What is a Tiered List?
A tiered list, also known as a tier list or tier ranking, is a way of categorizing items into ranked groups called tiers. Each tier represents a certain level of quality or preference. Typically, the tiers are labelled with letters like S, A, B, C, D and items are placed into the tiers based on how well they meet certain criteria.
Here‘s a simple example of a tiered list ranking some common fruits:
S Tier: Mango, Watermelon
A Tier: Strawberry, Pineapple
B Tier: Banana, Orange
C Tier: Grapefruit, Honeydew
D Tier: Cantaloupe, Papaya
Tiered lists originated in gaming communities as a way to rank characters based on their combat strength. They‘ve since expanded into a general format for comparing items of any type. Their visual nature makes it easy to see both the overall ranking and where each item stacks up.
Setting Up the Environment
To build our tiered list maker, we‘ll be using Python 3. We‘ll also leverage a few popular libraries:
- Pillow for generating images of our tiered lists
- Pandas for handling our tiered list data in a structured way
- PyInputPlus for getting validated user input
You can install these dependencies using pip:
pip3 install Pillow pandas pyinputplus
We‘ll be developing this as a command-line tool, so no need for any web frameworks or UI libraries. Any code editor will work, though one with good Python support like VS Code or PyCharm is recommended.
Designing the Data Model
Let‘s think about how we want to represent a tiered list in code. At its core, a tiered list is:
- A set of labels for the tiers (S, A, B, C, D by default)
- A mapping of items to tiers
- Metadata like the list title, creation date, etc.
We could define a TieredList class to encapsulate this:
from dataclasses import dataclass
from typing import List, Dict
TIERS = [‘S‘, ‘A‘, ‘B‘, ‘C‘, ‘D‘]
@dataclass
class TieredList:
title: str
tiers: List[str] = TIERS
items: Dict[str, str] = field(default_factory=dict)
Here we‘re using the dataclasses module to define a class with typed fields. The tiers field defaults to the standard S/A/B/C/D labels. The items field is a dict that will map item names to their assigned tier.
Dataclasses are a concise way to represent structured data in Python. By using typing, we get better editor support and self-documenting code.
The List Manager
Next let‘s create a ListManager class that will handle the core operations:
from typing import Dict, List
import pandas as pd
class ListManager:
def __init__(self):
self.lists: Dict[str, TieredList] = {}
def create(self, title: str):
if title in self.lists:
raise ValueError(f"List ‘{title}‘ already exists")
self.lists[title] = TieredList(title)
print(f"Created new list: {title}")
def add_item(self, list_name: str, item: str, tier: str):
tiered_list = self.lists.get(list_name)
if not tiered_list:
raise ValueError(f"No list named {list_name}")
if tier not in tiered_list.tiers:
raise ValueError(f"Invalid tier: {tier}")
tiered_list.items[item] = tier
print(f"Added ‘{item}‘ to {tier} tier in {list_name}")
def view(self, list_name: str) -> pd.DataFrame:
tiered_list = self.lists.get(list_name)
if not tiered_list:
raise ValueError(f"No list named {list_name}")
data: Dict[str, List[str]] = {tier: [] for tier in TIERS}
for item, tier in tiered_list.items.items():
data[tier].append(item)
return pd.DataFrame(data)
The ListManager maintains a dictionary of TieredList objects. It exposes methods to:
- create a new empty list with a given title
- add an item to a specific tier in a list
- view a list as a Pandas DataFrame with items grouped by tier
The view method converts the TieredList into a DataFrame. This allows for easy display of the list data in a tabular format.
Using a class here gives us flexibility to add more operations later. It also encapsulates the tiered list data and manipulation logic.
Getting User Input
Now let‘s set up a simple command-line interface using PyInputPlus:
import pyinputplus as pyip
def prompt_new_list():
title = pyip.inputStr("Enter a title for the new list: ")
return title
def prompt_add_item(tiers: List[str]):
item_name = pyip.inputStr("Enter the item to add: ")
tier = pyip.inputMenu(tiers, numbered=True)
return item_name, tier
def prompt_action():
return pyip.inputMenu([‘New List‘, ‘Add Item‘, ‘View List‘, ‘Quit‘], numbered=True)
PyInputPlus has several helpful functions for getting and validating user input.
- inputStr prompts the user to enter a string.
- inputMenu presents a list of options and has the user choose one.
These prompts will be used in the main application loop to get info from the user on what actions to take and details for those actions.
The Main App
Now we‘re ready to put it all together into the main application:
def main():
print("Welcome to TierMaker!")
manager = ListManager()
while True:
action = prompt_action()
if action == ‘Quit‘:
print("Goodbye!")
break
if action == ‘New List‘:
title = prompt_new_list()
manager.create(title)
elif action == ‘Add Item‘:
list_name = pyip.inputMenu(manager.lists.keys())
item_name, tier = prompt_add_item(TIERS)
manager.add_item(list_name, item_name, tier)
elif action == ‘View List‘:
list_name = pyip.inputMenu(manager.lists.keys())
print(manager.view(list_name))
if __name__ == ‘__main__‘:
main()
The main function is the entry point to our app. It:
- Creates a new ListManager
- Prompts the user to choose an action
- Executes the appropriate subflow for the chosen action
- Loops until the user chooses to quit
This provides a simple but functional interface for interacting with our tiered list maker. Users can create new lists, add items to a list, and view the contents of a list.
Generating List Images
As a final touch, let‘s add the ability to render a tiered list as an image. We can use Pillow to create the graphic and Pandas to prepare the data:
from PIL import Image, ImageDraw, ImageFont
def render_list(tiers: pd.DataFrame, title: str):
num_tiers = len(tiers.columns)
num_items = tiers.apply(lambda x: len(x.dropna())).max()
item_height = 100
tier_width = 300
padding = 10
font = ImageFont.truetype(‘Arial.ttf‘, size=24)
title_font = ImageFont.truetype(‘Arial.ttf‘, size=36)
img_width = (tier_width + padding) * num_tiers
img_height = item_height * (num_items + 1) # +1 for tier labels
img = Image.new(‘RGB‘, (img_width, img_height), color=‘white‘)
draw = ImageDraw.Draw(img)
# Draw tier labels
for i, tier in enumerate(tiers.columns):
x = i * (tier_width + padding)
y = 0
draw.rectangle((x, y, x + tier_width, y + item_height), fill=‘lightblue‘)
draw.text((x + tier_width/2, y + item_height/2), tier, font=font, fill=‘black‘, anchor=‘mm‘)
# Draw items
for i, tier in enumerate(tiers.columns):
x = i * (tier_width + padding)
for j, item in enumerate(tiers[tier].dropna()):
y = (j+1) * item_height
draw.text((x + tier_width/2, y + item_height/2), item, font=font, fill=‘black‘, anchor=‘mm‘)
# Draw title
draw.text((img_width/2, padding), title, font=title_font, fill=‘black‘, anchor=‘ma‘)
img.save(f"{title}.png")
print(f"Rendered list to {title}.png")
def view_and_render(manager: ListManager):
list_name = pyip.inputMenu(manager.lists.keys())
tier_list = manager.lists[list_name]
df = manager.view(list_name)
print(df)
render_list(df, tier_list.title)
The render_list function takes a tiered list DataFrame and a title, and generates a PNG image of the list. It uses Pillow to:
- Create a blank image with dynamic dimensions based on the number of tiers and items
- Draw labeled rectangles for each tier
- Write the item names into the appropriate tier box
- Add a title to the image
- Save the result as a PNG
The view_and_render function prompts the user to select a list, displays it as a DataFrame, and then calls render_list to generate an image of it.
To use this feature, we just need to add a new menu option in our main loop:
elif action == ‘Render List‘:
view_and_render(manager)
Serialization and Persistence
As a bonus feature, we could add support for saving and loading tiered lists using json serialization. This would allow users to persist their lists between sessions.
First we need to make our TieredList and ListManager classes serializable:
import json
class TieredList:
...
def to_dict(self):
return {
‘title‘: self.title,
‘tiers‘: self.tiers,
‘items‘: self.items
}
@classmethod
def from_dict(cls, data):
return cls(**data)
class ListManager:
...
def save(self, filename):
data = {name: tl.to_dict() for name, tl in self.lists.items()}
with open(filename, ‘w‘) as f:
json.dump(data, f)
def load(self, filename):
with open(filename, ‘r‘) as f:
data = json.load(f)
self.lists = {name: TieredList.from_dict(tl) for name, tl in data.items()}
We add to_dict and from_dict methods to TieredList to convert between the object and a dictionary representation.
We add save and load methods to ListManager that write the lists to a JSON file and read them back, using the to_dict/from_dict methods.
Finally we add a couple new actions to the main loop:
elif action == ‘Save Lists‘:
filename = pyip.inputStr("Enter filename: ")
manager.save(filename)
print(f"Saved lists to {filename}")
elif action == ‘Load Lists‘:
filename = pyip.inputStr("Enter filename: ")
manager.load(filename)
print(f"Loaded lists from {filename}")
And we‘re all set! Users can now save their lists to a file and load them back later.
Wrap Up
With that, we have a fully functional tiered list maker. Users can create lists, populate them with categorized items, view and render lists, and save and load data. The code is structured in a way that would be fairly easy to extend with new features.
Some potential improvements:
- A GUI or web interface in addition to the CLI
- Support for images in list items
- Allowing custom tier labels
- Built-in item suggestions based on a database or API
- Shareable links for lists
Tiered lists offer a fun and intuitive way to categorize any collection of comparable items. With Python, Pillow, and Pandas, we were able to put together a capable tiered list maker in a couple hundred lines of code. Feel free to use this as a starting point for your own tiered list projects. Happy ranking!