Classes Answers

Argument Unpacking Exercises

Multi-valued Dictionary

This is the dict_from_tuple exercise in dictionaries.py. Edit the dictionaries.py file in the exercises directory to implement this exercise. To test it, run python test.py dict_from_tuple in your exercises directory.

Edit the function dict_from_tuple and modify it to accept a list of tuples of any length and return a dictionary which uses the first item of each tuple as keys and all subsequent items as values.

Note

This exercise is different from (but similar to) dict_from_truple, which you may have seen earlier.

Example usage:

>>> from dictionaries import dict_from_tuple
>>> dict_from_tuple([(1, 2, 3, 4), (5, 6, 7, 8)])
{1: (2, 3, 4), 5: (6, 7, 8)}
>>> dict_from_tuple([(1, 2, 3), (4, 5, 6), (7, 8, 9)])
{1: (2, 3), 4: (5, 6), 7: (8, 9)}

Answers

Using slicing:

def dict_from_tuple(input_list):
    """Turn multi-item tuples into a dictionary of multi-valued tuples."""
    new_dict = {}
    for items in input_list:
        new_dict[items[0]] = items[1:]
    return new_dict

Using tuple unpacking and * instead of slicing:

def dict_from_tuple(input_list):
    """Turn multi-item tuples into a dictionary of multi-valued tuples."""
    new_dict = {}
    for key, *values in input_list:
        new_dict[key] = tuple(values)
    return new_dict

Transpose

This is the transpose exercise in stars.py. Edit the stars.py file in the exercises directory to implement this exercise. To test it, run python test.py transpose in your exercises directory.

Make a function transpose that accepts a list of lists and returns the transpose of the list of lists.

Example usage:

>>> from stars import transpose
>>> transpose([[1, 2], [3, 4]])
[[1, 3], [2, 4]]
>>> matrix = [['a','b','c'],['d','e','f'],['g','h','i']]
>>> transpose(matrix)
[['a', 'd', 'g'], ['b', 'e', 'h'], ['c', 'f', 'i']]

Answers

def transpose(matrix):
    """Return a transposed version of given list of lists."""
    transposed = []
    for row in zip(*matrix):
        transposed.append(list(row))
    return transposed

Sum Each

Edit the sum_each function in the stars.py file so that it accepts any number of tuples of numbers and returns tuple of the sums of numbers in corresponding positions (sum of all first items, sum of all second items, etc.)

Example usage:

>>> from stars import sum_each
>>> sum_each((1, 2), (4, 5))
(5, 7)
>>> sum_each((1, 2), (4, 5), (7, 8), (1, 1))
(13, 16)
>>> sum_each((1, 2, 3), (4, 5, 6), (7, 8, 9), (1, 1, 0))
(13, 16, 18)

Answers

Making new tuples by adding one item on the end each time:

def sum_each(*number_tuples):
    """Return tuple with sums of each number in given tuples."""
    sums = ()
    for numbers in zip(*number_tuples):
        sums += (sum(numbers),)
    return sums

Maybe a bit more readable (that trailing comma is odd):

def sum_each(*number_tuples):
    """Return tuple with sums of each number in given tuples."""
    sums = ()
    for numbers in zip(*number_tuples):
        sums = (*sums, sum(numbers))
    return sums

Using a list and then converting it to a tuple afterward:

def sum_each(*number_tuples):
    """Return tuple with sums of each number in given tuples."""
    sums = []
    for numbers in zip(*number_tuples):
        sums.append(sum(numbers))
    return tuple(sums)

HTML Tag

This is the html_tag exercise in stars.py. Edit the stars.py file in the exercises directory to implement this exercise. To test it, run python test.py html_tag in your exercises directory.

Make a function that accepts a positional argument and keyword arguments and generates an HTML tag using them.

Example:

>>> from stars import html_tag
>>> html_tag("input", type="email", name="email", placeholder="E-mail address")
'<input type="email" name="email" placeholder="E-mail address">'
>>> img_tag = html_tag("img", src="https://placehold.it/10x10", alt="Placeholder")
>>> img_tag
'<img src="https://placehold.it/10x10" alt="Placeholder">'

Answers

Manual string building:

def html_tag(cmd, **kwargs):
    """Make an HTML tag from the given tag name and attributes."""
    tag = '<' + cmd
    for param, value in kwargs.items():
        tag += ' ' + param + '="' + value + '"'
    tag += '>'
    return tag

Building string with format and join (more idiomatic):

def html_tag(tag_name, **attributes):
    """Make an HTML tag from the given tag name and attributes."""
    attr_list = []
    for param, value in attributes.items():
        attr_list.append('{}="{}"'.format(param, value))
    return "<{tag} {attrs}>".format(tag=tag_name, attrs=" ".join(attr_list))

With f-strings:

def html_tag(tag_name, **attributes):
    attr_list = []
    for param, value in attributes.items():
        attr_list.append(f'{param}="{value}"')
    return f"<{tag_name} {' '.join(attr_list)}>"

Total Length

This is the total_length exercise in iteration.py.

Make a function total length that should calculate the total length of all given iterables.

This function should even work for iterables that don’t work with the built-in len (like zip, enumerate, etc.).

Example:

>>> from iteration import total_length
>>> total_length([1, 2, 3])
3
>>> total_length()
0
>>> total_length([1, 2, 3], [4, 5], iter([6, 7]))
7

Answers

def total_length(*iterables):
    total = 0
    for iterable in iterables:
        for item in iterable:
            total += 1
    return total

Tags Equal

Edit the tags_equal function in slices.py to accept two strings containing opening HTML tags and return True if they have the same attributes with the same values.

Some examples:

>>> from slices import tags_equal
>>> tags_equal("<img src=cats.jpg width=200 height=400>", "<IMG SRC=Cats.JPG height=400 width=200>")
True
>>> tags_equal("<img src=dogs.jpg width=999 height=400>", "<img src=dogs.jpg width=200 height=400>")
False
>>> tags_equal("<p>", "<P>")
True
>>> tags_equal("<b>", "<p>")
False

You can assume:

  1. Attributes don’t have double/single quotes around them

  2. Attributes don’t contain spaces (until you get bonus 3)

  3. Attribute names will not be repeated

  4. All attributes will have values

  5. Attributes have no extra whitespace around them (key=value and never key = value)

Keep in mind that:

  1. Attribute names and values matter, but ordered must be ignored

  2. Attributes are case-insensitive (you’ll need to normalize the case)

Answers

def tags_equal(tag1, tag2):
    """Return True if the given HTML open tags represent the same thing."""
    items1 = tag1[1:-1].split()
    name1 = items1[0].lower()
    attrs1 = sorted(a.lower() for a in items1[1:])
    items2 = tag2[1:-1].split()
    name2 = items2[0].lower()
    attrs2 = sorted(a.lower() for a in items2[1:])
    return name1 == name2 and attrs1 == attrs2
def tags_equal(tag1, tag2):
    """Return True if the given HTML open tags represent the same thing."""
    name1, *attrs1 = tag1[1:-1].lower().split()
    name2, *attrs2 = tag2[1:-1].lower().split()
    return (name1, sorted(attrs1)) == (name2, sorted(attrs2))
def parse_tag(html_tag):
    """Return tuple of tag name and sorted attributes."""
    tag_name, *attributes = html_tag[1:-1].lower().split()
    return tag_name, sorted(attributes)


def tags_equal(tag1, tag2):
    """Return True if the given HTML open tags represent the same thing."""
    return parse_tag(tag1) == parse_tag(tag2)

Class Exercises

Bank Account

This is the BankAccount exercise in classes.py. To test it, run python test.py BankAccount from the exercises directory.

Make a BankAccount class that allows depositing and withdrawing money:

>>> from classes import BankAccount
>>> trey_account = BankAccount(20)
>>> trey_account.balance
20
>>> trey_account.deposit(100)
>>> trey_account.balance
120
>>> trey_account.withdraw(40)
>>> trey_account.balance
80
>>> trey_account
BankAccount(balance=80)

Your BankAccount class should also support transfers:

>>> mary_account = BankAccount(100)
>>> dana_account = BankAccount()
>>> mary_account.transfer(dana_account, 20)
>>> mary_account.balance
80
>>> dana_account.balance
20

Answers

class BankAccount:

    """Bank account including an account balance."""

    def __init__(self, balance=0):
        self.balance = balance

    def __repr__(self):
        return f"{type(self).__name__}(balance={self.balance})"

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        self.balance -= amount

    def transfer(self, other_account, amount):
        self.withdraw(amount)
        other_account.deposit(amount)

Record Transactions

This is the BankAccount exercise in classes.py. To test it, run python test.py BankAccount from the exercises directory.

Note

To test these changes, you need to modify the BankAccountTests class in classes_test.py to comment out the @unittest.skip lines with “Comment this line for transactions exercise.”.

Modify our class to record transactions for deposits and withdrawals.

A transactions attribute should be added to our object that maintains a list of all transactions in our account. Each transaction should be a tuple containing an action name (as a string), the amount the account changed, and the account balance after the change.

Example usage:

>>> from classes import BankAccount
>>> my_account = BankAccount(10)
>>> my_account.deposit(100)
>>> my_account.withdraw(40)
>>> my_account.deposit(95)
>>> my_account.transactions
[('OPEN', 10, 10), ('DEPOSIT', 100, 110), ('WITHDRAWAL', -40, 70), ('DEPOSIT', 95, 165)]

Answers

class BankAccount:

    """Bank account that records transactions."""

    def __init__(self, balance=0):
        self.balance = balance
        self.transactions = [("OPEN", balance, balance)]

    def __repr__(self):
        return f"{type(self).__name__}(balance={self.balance})"

    def deposit(self, amount):
        self.balance += amount
        self.transactions.append(("DEPOSIT", amount, self.balance))

    def withdraw(self, amount):
        self.balance -= amount
        self.transactions.append(("WITHDRAWAL", -amount, self.balance))

    def transfer(self, other_account, amount):
        self.withdraw(amount)
        other_account.deposit(amount)

Month

This is the Month exercise in classes.py. Edit the classes.py file in the exercises directory to implement this exercise. To test it, run python test.py Month from the exercises directory.

Make a Month class that has year and month attributes. This class should have two string representations and a first method that returns a datetime.date object representing the first day of the given month.

Example:

>>> from classes import Month
>>> python2_eol_month = Month(year=2020, month=1)
>>> python2_eol_month
Month(2020, 1)
>>> str(python2_eol_month)
'2020-01'
>>> print(python2_eol_month)
2020-01
>>> python2_eol_month.first()
datetime.date(2020, 1, 1)

Answers

from datetime import date


class Month:

    """Class representing an entire month."""

    def __init__(self, year, month):
        self.year, self.month = year, month

    def __repr__(self):
        return f"{type(self).__name__}({self.year}, {self.month})"

    def __str__(self):
        return f"{self.year}-{self.month:02d}"

    def first(self):
        return date(self.year, self.month, 1)

Row

This is the Row exercise in classes.py.

Edit the classes.py file in the exercises directory to implement this exercise. To test it, run python test.py Row from the exercises directory.

Make a Row class that accepts any keyword arguments given to it and stores these arguments as attributes.

>>> from classes import Row
>>> row = Row(a=1, b=2)
>>> row.a
1
>>> row.b
2

Answers

class Row:

    """Row class that stores all given arguments as attributes."""

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

    def __repr__(self):
        attrs = ", ".join(
            f"{key}={value}"
            for key, value in self.__dict__.items()
        )
        return f"{type(self).__name__}({attrs})"

Or using __dict__ to initialize and to get a nice string representation:

class Row:

    """Row class that stores all given arguments as attributes."""

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def __repr__(self):
        attrs = ", ".join(
            f"{key}={value}"
            for key, value in self.__dict__.items()
        )
        return f"{type(self).__name__}({attrs})"

Ice Cream Flavor

This is the Flavor exercise in classes.py. Edit the classes.py file in the exercises directory to implement this exercise. To test it, run python test.py Flavor from the exercises directory.

Make a Flavor class that has name, ingredients, and has_dairy attributes. The name attribute should be a required string. The ingredients attribute should be an optional iterable. The has_dairy attribute should be an optional boolean value (defaulting to True). Instances of the Flavor class should also have a nice string representation.

It should work like this:

>>> from classes import Flavor
>>> vanilla = Flavor("vanilla")
>>> vanilla.name
'vanilla'
>>> vanilla.has_dairy
True
>>> vanilla.ingredients
[]
>>> vanilla
Flavor(name='vanilla', ingredients=[], has_dairy=True)
>>> mint_chip = Flavor(
...     name="mint chip",
...     ingredients=["sugar", "mint", "cashews", "coconut milk"],
...     has_dairy=False,
... )
>>> mint_chip.ingredients
['sugar', 'mint', 'cashews', 'coconut milk']
>>> mint_chip.has_dairy
False

Answers

class Flavor:

    """Flavor of ice cream."""

    def __init__(self, name, ingredients=[], has_dairy=True):
        self.name = name
        self.ingredients = list(ingredients)
        self.has_dairy = has_dairy

    def __repr__(self):
        return (
            f"{type(self).__name__}(name={repr(self.name)},"
            f" ingredients={self.ingredients},"
            f" has_dairy={self.has_dairy})"
        )

Ice Cream Size

This is the Size exercise in classes.py. Edit the classes.py file in the exercises directory to implement this exercise. To test it, run python test.py Size from the exercises directory.

Make a Size class that has quantity, unit, and price attributes. Instances of the Size class should also have two string representations (as shown below).

It should work like this:

>>> from classes import Size
>>> one_quart = Size(quantity=1, unit="quart", price="$9")
>>> one_quart
Size(quantity=1, unit='quart', price='$9')
>>> print(one_quart)
1 quart
>>> two_scoops = Size(quantity=2, unit="scoop", price="$3")
>>> two_scoops
Size(quantity=2, unit='scoop', price='$3')
>>> print(two_scoops)
2 scoops

Answers

class Size:

    """Ice cream size."""

    def __init__(self, quantity, unit, price):
        self.quantity = quantity
        self.unit = unit
        self.price = price

    def __str__(self):
        units = self.unit if self.quantity == 1 else f"{self.unit}s"
        return f"{self.quantity} {units}"

    def __repr__(self):
        return (
            f"{type(self).__name__}(quantity={repr(self.quantity)},"
            f" unit={repr(self.unit)}, price={repr(self.price)})"
        )

Ice Cream

This is the IceCream exercise in classes.py. Edit the classes.py file in the exercises directory to implement this exercise. To test it, run python test.py IceCream from the exercises directory.

Make an IceCream class that has flavor and size attributes.

It should work like this:

>>> from classes import IceCream
>>> one_quart = Size(quantity=1, unit="quart", price="$9")
>>> vanilla = Flavor("vanilla")
>>> quart_of_vanilla = IceCream(flavor=vanilla, size=one_quart)
>>> print(quart_of_vanilla)
1 quart of vanilla
>>> print(IceCream(flavor=vanilla, size=Size(quantity=2, unit="scoop", price="$3")))
2 scoops of vanilla

Answers

class Flavor:

    """Flavor of ice cream."""

    def __init__(self, name, ingredients=[], has_dairy=True):
        self.name = name
        self.ingredients = list(ingredients)
        self.has_dairy = has_dairy

    def __repr__(self):
        return (
            f"{type(self).__name__}(name={repr(self.name)},"
            f" ingredients={self.ingredients},"
            f" has_dairy={self.has_dairy})"
        )


class Size:

    """Ice cream size."""

    def __init__(self, quantity, unit, price):
        self.quantity = quantity
        self.unit = unit
        self.price = price

    def __str__(self):
        units = self.unit if self.quantity == 1 else f"{self.unit}s"
        return f"{self.quantity} {units}"

    def __repr__(self):
        return (
            f"{type(self).__name__}(quantity={repr(self.quantity)},"
            f" unit={repr(self.unit)}, price={repr(self.price)})"
        )


class IceCream:

    """Ice cream to be ordered in our ice cream shop."""


    def __init__(self, flavor, size):
        self.flavor = flavor
        self.size = size

    def __str__(self):
        return f"{self.size} of {self.flavor.name}"

Email Server

This is the IMAPChecker exercise in classes.py.

The below code includes a get_connection function which returns a mail server object and three other functions that accept a mail server object as their first argument.

from email.parser import Parser
from imaplib import IMAP4_SSL

def get_connection(host, username, password):
    """Initialize IMAP server and login"""
    server = IMAP4_SSL(host)
    server.login(username, password)
    server.select("inbox")
    return server

def close_connection(server):
    server.close()
    server.logout()

def get_message_uids(server):
    """Return unique identifiers for each message"""
    return server.uid("search", None, "ALL")[1][0].split()

def get_message(server, uid):
    """Get email message identified by given UID"""
    result, data = server.uid("fetch", uid, "(RFC822)")
    (_, message_text), _ = data
    message = Parser().parsestr(message_text)
    return message

Here’s an example usage of that code:

server = get_connection(host, username, password)
messages = [
    message
    for uid in get_message_uids(server)
]
close_connection(server)

I’d like you to refactor that code to use a class instead. It should work just like this:

server = IMAPChecker(host)
server.authenticate(username, password)
messages = [
    message
    for uid in server.get_message_uids()
]
server.quit()

Answers

from email.parser import Parser
from imaplib import IMAP4_SSL

class IMAPChecker:
    def __init__(self, host):
        """Initialize IMAP email server with given host"""
        self.server = IMAP4_SSL(host)

    def authenticate(self, username, password):
        """Authenticate with email server"""
        self.server.login(username, password)
        self.server.select("inbox")

    def quit(self):
        self.server.close()
        self.server.logout()

    def get_message_uids(self):
        """Return unique identifiers for each message"""
        return self.server.uid("search", None, "ALL")[1][0].split()

    def get_message(self, uid):
        """Get email message identified by given UID"""
        result, data = self.server.uid("fetch", uid, "(RFC822)")
        (_, message_text), _ = data
        message = Parser().parsestr(message_text)
        return message

Dataclass Exercises

Ice Cream DataClasses

Rewrite the Size, Flavor, and IceCream classes in classes.py using data classes.

You can test each of these classes by running tests for Size, Flavor, and IceCream.

Answers

from dataclasses import dataclass, field


@dataclass
class Flavor:

    """Flavor of ice cream."""

    name: str
    ingredients: list = field(default_factory=tuple)
    has_dairy: bool = True

    def __post_init__(self):
        """Copy given ingredients iterable."""
        self.ingredients = list(self.ingredients)


@dataclass
class Size:

    """Ice cream size."""

    quantity: int
    unit: str
    price: str

    def __str__(self):
        units = self.unit if self.quantity == 1 else f"{self.unit}s"
        return f"{self.quantity} {units}"


@dataclass
class IceCream:

    """Ice cream to be ordered in our ice cream shop."""

    flavor: Flavor
    size: Size

    def __str__(self):
        return f"{self.size} of {self.flavor.name}"

Inheritance Exercises

Minimum Balance

This is the MinimumBalanceAccount exercise in classes.py.

Create a class MinimumBalanceAccount which subclasses BankAccount. This new class should raise an exception whenever the user attempts to withdraw so much money that their account goes below 0.

>>> from bank_account import MinimumBalanceAccount
>>> my_account = MinimumBalanceAccount()
Account opened.
Account balance is $0.
>>> my_account.deposit(100)
$100 deposited.
Account balance is $100.
>>> my_account.withdraw(200)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "bank_account.py", line 45, in withdraw
    raise ValueError("Balance cannot be less than $0")
ValueError: Balance cannot be less than $0

Answers

class BankAccount:

    """Base Bank account including an account balance."""

    def __init__(self, balance=0):
        self.balance = balance

    def __repr__(self):
        return f"{type(self).__name__}(balance={self.balance})"

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        self.balance -= amount


class MinimumBalanceAccount(BankAccount):

    """Bank account which does not allow balance to drop below zero."""

    def withdraw(self, amount):
        if self.balance - amount < 0:
            raise ValueError("Balance cannot be less than $0")
        return super().withdraw(amount)

Alphabetic String

This is the AlphaString exercise in classes.py.

Make a custom string object AlphaString which only allows strings consisting entirely of alphabetic characters (A-Z).

Hint

Inherit from collections.UserString to implement this class.

Answers

from collections import UserString


class AlphaString(UserString):

    """Class that allows only alphabetic characters in the string."""

    def __init__(self, string):
        if not string.isalpha():
            raise ValueError("AlphaString only allows alphabetic characters")
        super().__init__(string)

Doubly-Linked Node

This is the DoublyLinkedNode exercise in classes.py.

The Node class represents a node with multiple child nodes attached.

Printing a node displays the “ancestry” of the node, starting with the root or base node, separated by / between the nodes, and ending with the node requested.

Example of creating a root note and child nodes:

>>> root = Node('A')
>>> child1 = root.make_child('1')
>>> grandchild1 = child1.make_child('a')
>>> grandchild2 = child1.make_child('b')
>>> child2 = root.make_child('2')

Examples of the string representations of these nodes:

>>> print(child1)
A / 1
>>> print(grandchild1)
A / 1 / a
>>> child1.name = '9'
>>> print(grandchild2)
A / 9 / b
>>> print(child2)
A / 2
>>> grandchild1.name
'a'

More examples:

>>> print(Node("Universe")
          .make_child("Milky Way")
          .make_child("Solar System")
          .make_child("Earth")
... )
Universe / Milky Way / Solar System / Earth

>>> red_panda = (
...     Node("Animalia")
...     .make_child("Chordata")
...     .make_child("Mammalia")
...     .make_child("Carnivora")
...     .make_child("Ailuridae")
...     .make_child("Ailurus")
...     .make_child("A. fulgens")
... )
>>> print(red_panda)
Animalia / Chordata / Mammalia / Carnivora / Ailuridae / Ailurus / A. fulgens

I’d like you to make a DoublyLinkedNode class that extends the Node class. The method leaves returns all the children of the DoublyLinkedNode, or if there are no children, it returns a list containing only itself.

>>> from classes import DoublyLinkedNode
>>> root = DoublyLinkedNode('A')
>>> child1 = root.make_child('1')
>>> grandchild1 = child1.make_child('a')
>>> grandchild2 = child1.make_child('b')
>>> child2 = root.make_child('2')
>>> [node.name for node in root.leaves()]
['a', 'b', '2']
>>> [node.name for node in child1.leaves()]
['a', 'b']
>>> [node.name for node in child2.leaves()]
['2']

Answers

class Node:
    """Nodes for use in making hierarchies or trees."""

    def __init__(self, name, *, ancestors=[]):
        self.ancestors = list(ancestors)
        self.name = name

    def ancestors_and_self(self):
        """Return iterable with our ordered ancestors and our own node."""
        return [*self.ancestors, self]

    def make_child(self, *args, **kwargs):
        """Create and return a child node of the current node."""
        return type(self)(*args, ancestors=self.ancestors_and_self(), **kwargs)

    def __str__(self):
        """Return a slash-delimited ancestors hierarchy for this node."""
        return " / ".join([
            node.name
            for node in [*self.ancestors, self]
        ])

    def __repr__(self):
        return self.name


class DoublyLinkedNode(Node):

    """Class with Nodes that are doubly-linked"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.children = []

    def make_child(self, *args, **kwargs):
        """Create and return a child node of the current node."""
        node = super().make_child(*args, **kwargs)
        self.children.append(node)
        return node

    def leaves(self):
        """Return all leaves of the current node and all descendent nodes."""
        if self.is_leaf():
            return [self]
        else:
            return [
                node
                for child in self.children
                for node in child.leaves()
            ]

    def is_leaf(self):
        """Return True if this node is a leaf (node which has no children)."""
        return not self.children

Last Updated Dictionary

This is the LastUpdatedDictionary exercise in classes.py.

Make a class LastUpdatedDictionary that maintains its items in last-updated order.

Normally dictionaries maintain their items in insertion order. When a key is inserted, it’s added to the end, but when a key is updated nothing is moved or rearranged.

Unlike a typical dictionary, LastUpdatedDictionary objects should move keys to the end of the dictionary whenever they’re updated.

Example:

>>> d = LastUpdatedDictionary({'a': 1, 'b': 2, 'c': 3})
>>> d
LastUpdatedDictionary([('a', 1), ('b', 2), ('c', 3)])
>>> d['b'] = 1
>>> d
LastUpdatedDictionary([('a', 1), ('c', 3), ('b', 1)])

Hint

Consider inheriting from the collections.OrderedDict class which maintains its values in insertion order.

Answers

from collections import OrderedDict


class LastUpdatedDictionary(OrderedDict):
    def __setitem__(self, key, value):
        self.pop(key, None)
        super().__setitem__(key, value)

OrderedCounter

This is the OrderedCounter exercise in classes.py.

Make a class OrderedCounter that acts like collections.Counter except that it maintains its items in last-updated order. You can think of this as a mashup between collections.Counter and LastUpdatedDictionary.

Example:

>>> d = OrderedCounter('hello there')
>>> d
OrderedCounter([('a', 1), ('b', 2), ('c', 3)])
>>> d['b'] = 1
>>> d
OrderedCounter([('a', 1), ('c', 3), ('b', 1)])

Hint

Consider inheriting from the collections.OrderedDict class which maintains its values in insertion order.

Answers

from collections import Counter, OrderedDict


class LastUpdatedDictionary(OrderedDict):
    def __setitem__(self, key, value):
        self.pop(key, None)
        super().__setitem__(key, value)


class OrderedCounter(Counter, LastUpdatedDictionary):
    """Counter that maintains items in update order."""
    def __repr__(self):
        return f"{type(self).__name__}({LastUpdatedDictionary(self)})"