Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/corner-grocer/llms.txt

Use this file to discover all available pages before exploring further.

Enhancements Two and Three built on the Python rewrite by adding data-manipulation capabilities and persistent storage, in that order. Each enhancement introduced new files, new functions, and new menu options without modifying or removing anything already working. A user who knew the five-option menu from Enhancement One would find all five operations unchanged in the nine-option final version.

Why it matters

The original frequency dictionary was populated in file-read order and iterated alphabetically only because print_all_frequencies called sorted() on the keys at print time. Users who needed to see which items sold most often had no way to reorder the list without reading every line and mentally sorting it themselves. A partial-name search was impossible without scanning the full output. Adding explicit sorting and search operations transformed the program from a raw reporter into a practical inventory tool.

What was added

Three new functions were placed in grocery_utils.py:
FunctionPurpose
sort_alpha(freq_data)Prints all items in A–Z order by name
sort_by_freq(freq_data)Prints items from highest to lowest purchase count
search_item(freq_data, item_name)Looks up one item (case-insensitive) and prints its count or a not-found message
grocery_utils.py in full:
####################################################################
#  grocery_utils.py
#  Contains helper functions for sorting and searching
####################################################################

####################################################################
#  Sort items alphabetically
#  Prints each item in A–Z order with its frequency count
####################################################################
def sort_alpha(freq_data):
    for item in sorted(freq_data):
        print(f"{item}: {freq_data[item]}")

####################################################################
#  Sort items by highest frequency
#  Sorts from most to least sold then prints each item and count
####################################################################
def sort_by_freq(freq_data):
    sorted_items = sorted(
        freq_data.items(),
        key=lambda x: x[1],
        reverse=True
    )
    for item, freq in sorted_items:
        print(f"{item}: {freq}")

####################################################################
#  Search for a specific item
#  Checks dictionary and prints frequency or not found message
####################################################################
def search_item(freq_data, item_name):
    item = item_name.lower()
    if item in freq_data:
        print(f"{item}: {freq_data[item]}")
    else:
        print("Item not found.")

Modular design choice

The three functions were placed in grocery_utils.py rather than inline in the menu’s if/elif chain. The main file imports the module with import grocery_utils as utils and calls utils.sort_alpha(grocery.frequency), utils.sort_by_freq(grocery.frequency), and utils.search_item(grocery.frequency, query). This keeps the menu loop focused on user interaction — reading input, validating it, calling the right function, printing results — while the data-processing logic stays in one testable location that can be updated without touching the menu code.

Normalization improvement

Item names are lowercased at the point of ingestion in process_input_file (item = line.strip().lower()), so Apple, apple, and APPLE all map to the same "apple" key. search_item applies the same .lower() call on the user’s query before checking the dictionary. In the browser demo, common singular and plural forms (apple / apples) were also unified so that the same physical product could not accumulate separate totals under slightly different spellings. The menu grew from five to seven options (the original five plus the two new sort options and search, with the old exit renumbered to 7):
1. Look up item frequency
2. Print all items and frequencies
3. Print frequency histogram
4. Sort items alphabetically (A–Z)
5. Sort items by frequency (high → low)
6. Search for a specific item
7. Exit program

Enhancement Three: SQLite database

Why it matters

In every version up to and including Enhancement Two, all data existed only while the program was running. Starting the program a second time meant re-reading the input file from scratch. Any manual additions made through the menu were gone when the program exited. SQLite solved this by replacing the in-memory defaultdict with a database file that persists on disk between sessions, without requiring a separate database server or any additional installation beyond the Python standard library’s built-in sqlite3 module.

What was added

All database logic was placed in db_handler.py. The module exposes seven functions:
FunctionPurpose
connect_to_db(db_name)Opens a connection to the SQLite file, creating it if absent
create_table(conn)Creates the grocery_frequency table if it does not exist
insert_or_update_item(conn, item)Upserts one item record
get_all_frequencies(conn)Returns all items sorted alphabetically
get_frequency_for_item(conn, item)Returns the count for one specific item
get_frequencies_sorted_by_count(conn)Returns all items sorted by frequency descending
clear_all_data(conn)Deletes all rows from the table
A CornerGrocerApp wrapper class in corner_grocer_main.py owns the connection lifetime and delegates to db_handler functions for every data operation, keeping the menu loop free of direct SQL calls.

Schema

create_table defines the following structure:
CREATE TABLE IF NOT EXISTS grocery_frequency (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    item_name TEXT NOT NULL UNIQUE,
    frequency INTEGER NOT NULL
);
The UNIQUE constraint on item_name ensures that each grocery item has exactly one row. This constraint is what makes the upsert pattern in insert_or_update_item reliable: a SELECT confirms whether the row exists, and the result determines whether to UPDATE or INSERT.

Upsert logic

insert_or_update_item uses a check-then-act pattern with parameterized queries:
def insert_or_update_item(conn, item):
    try:
        cursor = conn.cursor()
        cursor.execute(
            "SELECT frequency FROM grocery_frequency WHERE item_name = ?;",
            (item,)
        )
        result = cursor.fetchone()
        if result:
            new_count = result[0] + 1
            cursor.execute(
                "UPDATE grocery_frequency SET frequency = ? WHERE item_name = ?;",
                (new_count, item)
            )
        else:
            cursor.execute(
                "INSERT INTO grocery_frequency (item_name, frequency) VALUES (?, 1);",
                (item,)
            )
        conn.commit()
    except sqlite3.Error as e:
        print(f"Error inserting/updating item '{item}': {e}")
If the item already exists, its current frequency is fetched, incremented by one in Python, and written back. If it does not exist, a new row is inserted with frequency = 1. Both branches commit immediately, keeping the database consistent after every individual item update. The ? placeholders in every query prevent SQL injection by passing values through the sqlite3 parameter binding layer rather than string formatting.

Frequency sorting with tie-breaking

get_frequencies_sorted_by_count uses a compound ORDER BY clause to produce a deterministic sort even when multiple items share the same count:
cursor.execute("""
    SELECT item_name, frequency
    FROM grocery_frequency
    ORDER BY frequency DESC, item_name ASC;
""")
Items with the highest purchase counts appear first. When two items have identical frequencies, the secondary item_name ASC clause sorts them alphabetically, so the output order is stable and predictable regardless of insertion order. Two save options were added, bringing the menu to nine entries:
1. Look up item frequency
2. Print all items and frequencies
3. Print frequency histogram
4. Sort items alphabetically (A–Z)
5. Sort items by frequency (high → low)
6. Search for a specific item
7. Save frequency list to frequency_list.dat
8. Save histogram to frequency_histogram.dat
9. Exit program
Options 7 and 8 write .dat files using with open(...) context managers and report success or a descriptive error message. Options 1 through 6 are identical in behavior to their Enhancement Two equivalents, now reading from the database instead of the in-memory dictionary.

Backward compatibility

Every enhancement was strictly additive. The table below shows how the original three C++ operations map through all four versions:
C++ menu optionEnh. One (Python)Enh. TwoEnh. Three
Look up item frequencyOption 1Option 1Option 1
Print all items and frequenciesOption 2Option 2Option 2
Print frequency histogramOption 3Option 3Option 3
(new) Save frequency backupOption 4Option 7
(new) Save histogram backupOption 5Option 8
(new) Sort A–ZOption 4Option 4
(new) Sort by frequencyOption 5Option 5
(new) SearchOption 6Option 6
ExitOption 6Option 7Option 9
Enhancement Two replaced the save backup options (Options 4 and 5 from Enhancement One) with the new sort and search operations, which took positions 4, 5, and 6. The save backup operations returned in Enhancement Three at positions 7 and 8, restored alongside the existing sort and search options. Exit shifted from Option 6 in Enhancement One, to Option 7 in Enhancement Two, and to Option 9 in Enhancement Three as the menu grew.
Developers extending Corner Grocer should follow the same additive pattern. New data-processing operations belong in grocery_utils.py (for in-memory transforms) or db_handler.py (for database queries). New menu entries should be appended above the exit option, and the exit option’s number should be incremented accordingly. Existing option numbers and behavior should remain unchanged so that users familiar with the current menu are not surprised.

Build docs developers (and LLMs) love