Program

import streamlit as st
import pandas as pd
import random
from difflib import SequenceMatcher

try:
    data = pd.read_csv("Chem.csv")
except FileNotFoundError:
    st.warning("Chem.csv not found. Please upload the CSV file.")
    uploaded = st.file_uploader("Upload Chem.csv", type="csv")
    if uploaded:
        data = pd.read_csv(uploaded)
    else:
        st.stop()

name_col = "Element_Name"
symbol_col = "Symbol"

def is_close(answer, correct, threshold=0.8):
    ratio = SequenceMatcher(None, answer.lower().strip(), correct.lower().strip()).ratio()
    return ratio >= threshold

if "questions" not in st.session_state:
    st.session_state.questions = []
if "index" not in st.session_state:
    st.session_state.index = 0
if "score" not in st.session_state:
    st.session_state.score = 0
if "results" not in st.session_state:
    st.session_state.results = []
if "started" not in st.session_state:
    st.session_state.started = False

st.title("Chemistry Quiz")
st.write("Test your knowledge of chemical elements and symbols.")

if not st.session_state.started:
    num = st.number_input("How many questions would you like?", min_value=1, max_value=118, value=5)
    if st.button("Start Quiz"):
        st.session_state.questions = data.sample(num).to_dict(orient="records")
        st.session_state.index = 0
        st.session_state.score = 0
        st.session_state.results = []
        st.session_state.started = True
        st.experimental_rerun()
else:
    i = st.session_state.index
    total = len(st.session_state.questions)

    if i < total:
        row = st.session_state.questions[i]

        ask_type = random.choice(["name_to_symbol", "symbol_to_name"])
        element = row[name_col]
        symbol = row[symbol_col]

        if ask_type == "name_to_symbol":
            prompt = f"What is the symbol for {element}?"
            correct = symbol
        else:
            prompt = f"What element has the symbol {symbol}?"
            correct = element

        st.subheader(f"Question {i+1} of {total}")
        st.write(prompt)

        answer = st.text_input("Your answer:", key=f"answer_{i}")

        if st.button("Submit Answer", key=f"submit_{i}"):
            if is_close(answer, correct):
                st.success("Correct.")
                st.session_state.score += 1
                st.session_state.results.append((True, correct))
            else:
                st.error(f"Incorrect. Correct answer: {correct}")
                st.session_state.results.append((False, correct))

            st.session_state.index += 1
            st.experimental_rerun()
    else:
        st.header("Quiz Complete")
        st.write(f"Your final score: {st.session_state.score}/{total}")

        if st.checkbox("Show all answers"):
            for idx, (correct_flag, value) in enumerate(st.session_state.results, 1):
                icon = "Correct" if correct_flag else "Incorrect"
                st.write(f"{idx}. {value} - {icon}")

        if st.button("Play Again"):
            st.session_state.started = False
            st.experimental_rerun()

This Python script implements an interactive Streamlit application that provides a user interface for a question-and-answer session based on external data.

1. Import Statements
  • import streamlit as st: Imports the Streamlit library, aliased as st, which is essential for building interactive web applications.

  • import pandas as pd: Imports the Pandas library, aliased as pd, used for data manipulation and analysis, particularly for reading CSV files.

  • import random: Imports the random module, used here to randomly select the type of question to ask.

  • from difflib import SequenceMatcher: Imports SequenceMatcher from the difflib module, utilized for comparing sequences and calculating string similarity.

2. Data Loading
  • The script attempts to load data from a CSV file named Chem.csv into a Pandas DataFrame called data.

  • It includes error handling: If Chem.csv is not found (FileNotFoundError):

    • A warning message is displayed using st.warning().

    • A st.file_uploader() widget is presented, allowing the user to upload the Chem.csv file directly.

    • If a file is uploaded, it's read into the data DataFrame.

    • If no file is uploaded and the initial file is not found, st.stop() halts the script execution.

3. Column Definitions
  • name_col = "Element_Name": Defines a variable name_col to store the string representing the column name for an item's full name.

  • symbol_col = "Symbol": Defines a variable symbol_col to store the string representing the column name for an item's short identifier.

4. Similarity Comparison Function
  • def is_close(answer, correct, threshold=0.8):

    • This function compares a user's answer with the correct answer.

    • It converts both strings to lowercase and removes leading/trailing whitespace using .lower().strip().

    • SequenceMatcher(None, ..., ...).ratio() calculates the similarity ratio between the two strings.

    • It returns True if the calculated ratio is greater than or equal to the specified threshold (defaulting to 0.80.8), indicating a sufficiently close match, otherwise False.

5. Session State Initialization
  • This section initializes various variables within Streamlit's st.session_state. Session state variables persist across rerun of the app.

  • st.session_state.questions = []: Stores the list of selected data items to be used in the session.

  • st.session_state.index = 0: Tracks the current question number or index within the questions list.

  • st.session_state.score = 0: Keeps track of the user's correct answers.

  • st.session_state.results = []: Stores a history of whether each question was answered correctly and the correct answer.

  • st.session_state.started = False: A boolean flag indicating whether the interactive session has begun.

6. Application Title and Introduction
  • st.title("Chemistry Quiz"): Sets the main title of the Streamlit application.

  • st.write("Test your knowledge of chemical elements and symbols."): Displays a brief introductory text below the title.

7. Starting the Session (if not st.session_state.started)
  • This block executes when the session has not yet started.

  • num = st.number_input(...): Allows the user to input the desired number of questions, with a minimum of 11, maximum of 118118, and a default value of 55 questions.

  • if st.button("Start Quiz"):

    • When the "Start Quiz" button is pressed:

      • data.sample(num).to_dict(...): Randomly selects num rows from the loaded data DataFrame and converts them into a list of dictionaries, storing them in st.session_state.questions.

      • Resets index, score, and results to their initial values.

      • Sets st.session_state.started = True to transition to the active session state.

      • st.experimental_rerun(): Forces the Streamlit application to rerun immediately, displaying the first question.

8. Active Session Logic (else block when st.session_state.started is True)
  • i = st.session_state.index: Retrieves the current question index.

  • total = len(st.session_state.questions): Gets the total number of questions.

    8.1. Displaying Current Question (if i < total)
    • Executes as long as there are questions remaining.

    • row = st.session_state.questions[i]: Retrieves the current data item.

    • ask_type = random.choice(...): Randomly decides whether to ask for the identifier given the name, or the name given the identifier.

    • element = row[name_col] and symbol = row[symbol_col]: Extracts the name and identifier from the current data item.

    • Based on ask_type, it constructs the prompt string and determines the correct answer.

    • st.subheader(f"Question {i+1} of {total}"): Displays the current question number.

    • st.write(prompt): Shows the actual question to the user.

    • answer = st.text_input("Your answer:", key=f"answer_{i}"): Provides a text input field for the user to type their answer. The key ensures unique widget identification.

    8.2. Submitting Answer (if st.button("Submit Answer", key=f"submit_{i}"))
    • When the "Submit Answer" button is clicked:

      • is_close(answer, correct): Compares the user's input with the correct answer using the defined similarity function.

      • st.success("Correct.") or st.error(...): Provides visual feedback (green for correct, red for incorrect along with the correct answer).

      • st.session_state.score += 1: Increments the score if the answer was correct.

      • st.session_state.results.append(...): Records the outcome (correct/incorrect) and the correct answer for later review.

      • st.session_state.index += 1: Moves to the next question.

      • st.experimental_rerun(): Reruns the app to display the next question or the completion screen.

9. Session Completion (else block when i >= total)
  • This block executes once all questions have been answered.

  • st.header("Quiz Complete"): Displays a completion header.

  • st.write(f"Your final score: {st.session_state.score}/{total}"): Shows the user's total score.

  • if st.checkbox("Show all answers"):

    • If the "Show all answers" checkbox is selected, it iterates through st.session_state.results.

    • For each result, it displays the item's correct value and indicates whether the user's answer was correct or incorrect.

  • if st.button("Play Again"):

    • When the "Play Again" button is pressed, it resets st.session_state.started to False.

    • st.experimental_rerun(): Reruns the app, returning the user to the initial start screen to begin a new session.