Applied Programming/Lists and Tuples/Python3

lists.py edit

This is the lists.py program using csv(comma separated values):

"""This program demonstrates file, list, and tuple processing.

It creates a temporary file, adds data to the file, and reads the file
into a list of tuples. It then provides a menu of options for displaying 
and searching the file data.

It will not run if the file already exists.

Input:
    None

Output:
    A temporary file and file contents.

References:
    https://www.tutorialspoint.com/python/python_tuples.htm

"""

import os
import sys
import csv

def create_file(filename):
    """Creates filename and adds temperature data to the file.

    Args:
        filename (string): Filename to create.

    Returns:
        None

    """
    with open(filename, 'w') as file:
        file.write('"C","F"\n')
        for c in range(0, 101, 10):
            f = c * 9 / 5 + 32
            file.write('"%.1f","%.1f"\n' % (c, f))


def read_file(filename):
    """Reads filename and returns a list of temperature tuples.

    Args:
        filename (string): Filename to open and read.

    Returns:
        A list of temperature tuples.
        
    Raises:
        ValueError: Invalid header format.
        ValueError: Invalid record format.
        ValueError: No data found.

    """
    with open(filename, 'r') as file:
        line = file.readline()
        if line.strip() != '"C","F"':
            raise ValueError("Invalid header format found in %s: %s" % 
                (filename, line))
        temperatures = []
        reader= csv.reader(file)
        array = list(reader)
        for row in array:
            if 'C' in row:
                continue
            row[0] = float(row[0])
            row[1] = float(row[1])
            temperatures.append(row)

        if len(row) != 2:
                raise ValueError("Invalid record format found in %s: %s" % 
                    (filename, row))
            
    if len(temperatures) == 0:
        raise ValueError("No data found in %s" % filename)
    return temperatures


def delete_file(filename):
    """Deletes filename.

    Args:
        filename (string): Filename to delete.

    Returns:
        None

    """
    os.remove(filename)


def get_choice():
    """Displays menu and gets choice.

    Args:
        None

    Returns:
        choice (int) or None

    """
    while True:
        try:
            print("Select from the following options or press <Enter> to quit:")
            print("1. Display table sorted by Celsius temperature.")
            print("2. Display table sorted by Fahrenheit temperature.")
            print("3. Search for Celsius temperature.")
            print("4. Search for Fahrenheit temperature.")
            choice = input()
            choice = int(choice)
            if 1 <= choice <= 4:
                print()
                return choice
            print("%s is not a valid choice.\n" % choice)
        except ValueError:
            if choice == "":
                return None
            print("%s is not a valid choice.\n" % choice)


def display_temperatures(temperatures, scale):
    """Displays temperatures sorted in scale order.
    
    Args:
        temperatures (list of Celsius, Fahrenheit tuples)
        scale (string): "Celsius" or "Fahrenheit"

    Returns:
        None

    """
    if scale == "Celsius":
        data = sorted(temperatures, key=lambda record: record[0])
        print("C\tF")
        first_column = 0
        second_column = 1
    elif scale == "Fahrenheit":
        data = sorted(temperatures, key=lambda record: record[1])
        print("F\tC")
        first_column = 1
        second_column = 0
    else:
        raise ValueError("scale must be Celsius or Fahrenheit")
    for record in data:
        print("%s\t%s" % (record[first_column], record[second_column]))        
    print()


def get_temperature(scale):
    """Gets Fahrenheit or Celsius temperature.
    
    Args:
        scale (string): "Celsius" or "Fahrenheit"

    Returns:
        temperature (float) or None

    """
    assert scale == "Celsius" or scale == "Fahrenheit"
    while True:
        try:
            print("Enter %s temperature:" % scale)
            temperature = input()
            temperature = float(temperature)
            print()
            return temperature
        except ValueError:
            print("%s is not a valid %s temperature\n" %
                (temperature, scale))
            return None


def search_temperatures(temperatures, scale):
    """Search temperatures for a scale temperature and display the nearest match.
    
    Args:
        temperatures (list of Celsius, Fahrenheit tuples)
        scale (string): "Celsius" or "Fahrenheit"

    Returns:
        None

    Raises:
        ValueError: If temperatures list is empty
        ValueError: If scale is invalid
        
    """
    if len(temperatures) <= 0:
        raise ValueError("temperatures list is empty")
    
    temperature = get_temperature(scale)
    if temperature == None:
        return

    if scale == "Celsius":
        search_element = 0
        display_element = 1
        other_scale = "Fahrenheit"
    elif scale == "Fahrenheit":
        search_element = 1
        display_element = 0
        other_scale = "Celsius"
    else:
        raise ValueError("scale must be Celsius or Fahrenheit")
        
    data = sorted(temperatures, key=lambda record: record[search_element])
    last = data[0]
    for record in data:
        if temperature < record[search_element]:
            break
        last = record

    if temperature < last[search_element]:
        print("%.1f° %s is less than %.1f° %s\n" % 
            (temperature, scale, last[display_element], other_scale))
    elif temperature == last[search_element]:
        print("%.1f° %s is %.1f° %s\n" % 
            (temperature, scale, last[display_element], other_scale))
    elif temperature > record[search_element]:
        print("%.1f° %s is greater than %.1f° %s\n" % 
            (temperature, scale, last[display_element], other_scale))
    else:
        print("%.1f° %s is between %.1f° %s and %.1f° %s\n" %
            (temperature, scale, 
            last[display_element], other_scale,
            record[display_element], other_scale))
    

def main():
    """Runs the main program logic."""

    try:
        filename = "~temperatures.txt"

        if os.path.isfile(filename):
            print("File '%s' already exists." % filename)
            os.remove(filename)
            exit(1)

        create_file(filename)
        temperatures = read_file(filename)
        delete_file(filename)
        
        while True:
            choice = get_choice()
            if choice == None:
                break
            elif choice == 1:
                display_temperatures(temperatures, "Celsius")
            elif choice == 2:
                display_temperatures(temperatures, "Fahrenheit")
            elif choice == 3:
                search_temperatures(temperatures, "Celsius")
            elif choice == 4:
                search_temperatures(temperatures, "Fahrenheit")
            else:
                raise ValueError("Invalid option selected")
    except:
        print("Unexpected error.")
        print("Error:", sys.exc_info()[1])
        print("File: ", sys.exc_info()[2].tb_frame.f_code.co_filename) 
        print("Line: ", sys.exc_info()[2].tb_lineno)


main()

And is the lists.py program using regex:

"""This program demonstrates file, list, and tuple processing.

It creates a temporary file, adds data to the file, and reads the file
into a list of tuples. It then provides a menu of options for displaying 
and searching the file data.

It will not run if the file already exists.

Input:
    None

Output:
    A temporary file and file contents.

References:
    https://www.tutorialspoint.com/python/python_tuples.htm

"""

import os
import re
import sys

def create_file(filename):
    """Creates filename and adds temperature data to the file.

    Args:
        filename (string): Filename to create.

    Returns:
        None

    """
    with open(filename, 'w') as file:
        file.write('"C","F"\n')
        for c in range(0, 101, 10):
            f = c * 9 / 5 + 32
            file.write('"%.1f","%.1f"\n' % (c, f))


def read_file(filename):
    """Reads filename and returns a list of temperature tuples.

    Args:
        filename (string): Filename to open and read.

    Returns:
        A list of temperature tuples.
        
    Raises:
        ValueError: Invalid header format.
        ValueError: Invalid record format.
        ValueError: No data found.

    """
    with open(filename, 'r') as file:
        line = file.readline()
        if line.strip() != '"C","F"':
            raise ValueError("Invalid header format found in %s: %s" % 
                (filename, line))
            
        temperatures = []
        for line in file:
            if 'C' in line:
                continue
            record = re.findall('"(.*?)"', line)
            if len(record) != 2:
                raise ValueError("Invalid record format found in %s: %s" % 
                    (filename, line))
                
            record[0] = float(record[0])
            record[1] = float(record[1])
            temperatures.append(record)
            
    if len(temperatures) == 0:
        raise ValueError("No data found in %s" % filename)
    return temperatures


def delete_file(filename):
    """Deletes filename.

    Args:
        filename (string): Filename to delete.

    Returns:
        None

    """
    os.remove(filename)


def get_choice():
    """Displays menu and gets choice.

    Args:
        None

    Returns:
        choice (int) or None

    """
    while True:
        try:
            print("Select from the following options or press <Enter> to quit:")
            print("1. Display table sorted by Celsius temperature.")
            print("2. Display table sorted by Fahrenheit temperature.")
            print("3. Search for Celsius temperature.")
            print("4. Search for Fahrenheit temperature.")
            choice = input()
            choice = int(choice)
            if 1 <= choice <= 4:
                print()
                return choice
            print("%s is not a valid choice.\n" % choice)
        except ValueError:
            if choice == "":
                return None
            print("%s is not a valid choice.\n" % choice)


def display_temperatures(temperatures, scale):
    """Displays temperatures sorted in scale order.
    
    Args:
        temperatures (list of Celsius, Fahrenheit tuples)
        scale (string): "Celsius" or "Fahrenheit"

    Returns:
        None

    """
    if scale == "Celsius":
        data = sorted(temperatures, key=lambda record: record[0])
        print("C\tF")
        first_column = 0
        second_column = 1
    elif scale == "Fahrenheit":
        data = sorted(temperatures, key=lambda record: record[1])
        print("F\tC")
        first_column = 1
        second_column = 0
    else:
        raise ValueError("scale must be Celsius or Fahrenheit")
    for record in data:
        print("%s\t%s" % (record[first_column], record[second_column]))        
    print()


def get_temperature(scale):
    """Gets Fahrenheit or Celsius temperature.
    
    Args:
        scale (string): "Celsius" or "Fahrenheit"

    Returns:
        temperature (float) or None

    """
    assert scale == "Celsius" or scale == "Fahrenheit"
    while True:
        try:
            print("Enter %s temperature:" % scale)
            temperature = input()
            temperature = float(temperature)
            print()
            return temperature
        except ValueError:
            print("%s is not a valid %s temperature\n" %
                (temperature, scale))
            return None


def search_temperatures(temperatures, scale):
    """Search temperatures for a scale temperature and display the nearest match.
    
    Args:
        temperatures (list of Celsius, Fahrenheit tuples)
        scale (string): "Celsius" or "Fahrenheit"

    Returns:
        None

    Raises:
        ValueError: If temperatures list is empty
        ValueError: If scale is invalid
        
    """
    if len(temperatures) <= 0:
        raise ValueError("temperatures list is empty")
    
    temperature = get_temperature(scale)
    if temperature == None:
        return

    if scale == "Celsius":
        search_element = 0
        display_element = 1
        other_scale = "Fahrenheit"
    elif scale == "Fahrenheit":
        search_element = 1
        display_element = 0
        other_scale = "Celsius"
    else:
        raise ValueError("scale must be Celsius or Fahrenheit")
        
    data = sorted(temperatures, key=lambda record: record[search_element])
    last = data[0]
    for record in data:
        if temperature < record[search_element]:
            break
        last = record

    if temperature < last[search_element]:
        print("%.1f° %s is less than %.1f° %s\n" % 
            (temperature, scale, last[display_element], other_scale))
    elif temperature == last[search_element]:
        print("%.1f° %s is %.1f° %s\n" % 
            (temperature, scale, last[display_element], other_scale))
    elif temperature > record[search_element]:
        print("%.1f° %s is greater than %.1f° %s\n" % 
            (temperature, scale, last[display_element], other_scale))
    else:
        print("%.1f° %s is between %.1f° %s and %.1f° %s\n" %
            (temperature, scale, 
            last[display_element], other_scale,
            record[display_element], other_scale))
    

def main():
    """Runs the main program logic."""

    try:
        filename = "~temperatures.txt"

        if os.path.isfile(filename):
            print("File '%s' already exists." % filename)
            exit(1)

        create_file(filename)
        temperatures = read_file(filename)
        delete_file(filename)
        
        while True:
            choice = get_choice()
            if choice == None:
                break
            elif choice == 1:
                display_temperatures(temperatures, "Celsius")
            elif choice == 2:
                display_temperatures(temperatures, "Fahrenheit")
            elif choice == 3:
                search_temperatures(temperatures, "Celsius")
            elif choice == 4:
                search_temperatures(temperatures, "Fahrenheit")
            else:
                raise ValueError("Invalid option selected")
    except:
        print("Unexpected error.")
        print("Error:", sys.exc_info()[1])
        print("File: ", sys.exc_info()[2].tb_frame.f_code.co_filename) 
        print("Line: ", sys.exc_info()[2].tb_lineno)


main()

test_lists.py edit

"""This file tests the lists example program using PyTest.

Run "pytest" in this folder to automatically run these tests.

Expected output:
    x passed in 0.xx seconds

References:
    * https://realpython.com/python-testing/
    * http://docs.pytest.org/en/latest/getting-started.html

"""

import pytest
import lists


def test_get_choice_returns_valid_input():
    input_values = ['1']

    def input():
        return input_values.pop()

    lists.input = input
    assert lists.get_choice() == 1


def test_get_choice_returns_ignores_non_input():
    input_values = [1, True, '1']

    def input():
        return input_values.pop()

    lists.input = input
    assert lists.get_choice() == 1


def test_display_temperatures_displays_valid_input_c(capsys):
    input_table = [[0.0, 32.0], [10.0, 50.0], [20.0, 68.0], [30.0, 86.0], [40.0, 104.0], [50.0, 122.0], [60.0, 140.0], [70.0, 158.0], [80.0, 176.0], [90.0, 194.0], [100.0, 212.0]]
    lists.display_temperatures(input_table, "Celsius")
    captured = capsys.readouterr()
    assert "0.0\t32.0" in captured.out


def test_display_temperatures_displays_valid_input_f(capsys):
    input_table = [[0.0, 32.0], [10.0, 50.0], [20.0, 68.0], [30.0, 86.0], [40.0, 104.0], [50.0, 122.0], [60.0, 140.0], [70.0, 158.0], [80.0, 176.0], [90.0, 194.0], [100.0, 212.0]]
    lists.display_temperatures(input_table, "Fahrenheit")
    captured = capsys.readouterr()
    assert "32.0\t0.0" in captured.out


def test_search_temperatures_returns_valid_output_c(capsys):
    input_table = [[0.0, 32.0], [10.0, 50.0], [20.0, 68.0], [30.0, 86.0], [40.0, 104.0], [50.0, 122.0], [60.0, 140.0], [70.0, 158.0], [80.0, 176.0], [90.0, 194.0], [100.0, 212.0]]
    lists.search_temperatures(input_table, "Celsius")
    captured = capsys.readouterr()
    assert "1.0° Celsius is between 32.0° Fahrenheit and 50.0° Fahrenheit" in captured.out


def test_search_temperatures_returns_valid_output_f(capsys):
    input_table = [[0.0, 32.0], [10.0, 50.0], [20.0, 68.0], [30.0, 86.0], [40.0, 104.0], [50.0, 122.0], [60.0, 140.0], [70.0, 158.0], [80.0, 176.0], [90.0, 194.0], [100.0, 212.0]]
    lists.search_temperatures(input_table, "Fahrenheit")
    captured = capsys.readouterr()
    assert "1.0° Fahrenheit is less than 0.0° Celsius" in captured.out

Try It edit

Copy and paste the code above into one of the following free online development environments or use your own Python3 compiler / interpreter / IDE.

See Also edit