Веб-приложение SUVAT Calculator с использованием Python Streamlit

Я сделал калькулятор чтобы помочь бенгальским старшеклассникам в решении математических задач по уравнениям Ньютона линейного движения, также известным как уравнения СУВАТ. Я использую Python версии 3.8.3 и залитый потоком версия 0.80.0.

Изначально я использовал Bangla в качестве языка пользовательского интерфейса веб-приложения (Github Repo). Но здесь я показываю все на английском, чтобы получить надлежащий обзор и помощь:

import streamlit as st
import math

#############
# Functions #
#############

def _ask(variable):
    return st.number_input(f'{variable} :', step=None, format="%f")

############
# Main App #
############

st.markdown("<h1 style="text-align: center; color: #ff7903; font-family: Solaimanlipi"> SUVAT Calculator </h1>", unsafe_allow_html=True)

st.write('This calculator app will help to calculate the variables of the Newtonian equations of linear motion aka SUVAT equations.')

st.write('Select any 3 known-valued variables:')

option_s = st.checkbox('Displacement (s)')
option_u = st.checkbox('Initial Velocity (u)')
option_v = st.checkbox('Final Velocity (v)')
option_a = st.checkbox('Acceleration (a)')
option_t = st.checkbox('Time 
known_variables = option_s + option_u + option_v + option_a + option_t

if known_variables <3:
    st.write('Select at least 3 variables.')
elif known_variables == 3:
   st.write('Enter their values in the same unit system. Accordingly, this calculator will return the values of the remaining 2 variables. ')
elif known_variables >3:
    st.write('Select only 3 variables.')

if (option_s is False and option_u and option_v and option_a is False and option_t):    # ['Initial Velocity (u)', 'Final Velocity (v)', 'Time 
    u = ask('Initial Velocity (u)')
    v = ask('Final Velocity (v)')
    t = ask('Time 
    if st.button('Click here to calculate and check the necessary equations') is True:
        st.write("""        The equations which are used to determine the values:
        $$
    s = \frac{1}{2}(u+v)t, \quad a = \frac{v-u}{t}
    $$      """)
        s = 0.5*(u+v)*t
        try:
            a = (v-u)/t
        except ZeroDivisionError:
            st.write('Here $t=0$ is not allowed.')
            a = None
        st.write('Inserting your given values in these equations, we get: ')
        st.write('Displacement $(s) = \frac{1}{2}(u+v)t =$ ', s)
        st.write('Acceleration $(a) = \frac{v-u}{t} =$ ', a)

elif (option_s is False and option_u and option_v and option_a and option_t is False):  # ['Initial Velocity (u)', 'Final Velocity (v)', 'Acceleration (a)']
    u = ask('Initial Velocity (u)')
    v = ask('Final Velocity (v)')
    a = ask('Acceleration (a)')
    if st.button('Click here to calculate and check the necessary equations') is True:
        st.write("""        The equations which are used to determine the values:
        $$
    s = \frac{v^2-u^2}{2a}, \quad t = \frac{v-u}{a}
    $$      """)
        try:
            s = (v*v - u*u)/(2*a)
            t = (v - u)/a
        except ZeroDivisionError:
            st.write('Here $a=0$ is not allowed.')
            s = None
            t = None
        st.write('Inserting your given values in these equations, we get: ')
        st.write('Displacement $(s) = \frac{v^2-u^2}{2a} =$ ', s)
        st.write('Time $

elif (option_s is False and option_u and option_v is False  and option_a and option_t): # ['Initial Velocity (u)', 'Acceleration (a)', 'Time 
    u = ask('Initial Velocity (u)')
    a = ask('Acceleration (a)')
    t = ask('Time 
    if st.button('Click here to calculate and check the necessary equations') is True:
        st.write("""        The equations which are used to determine the values:
        $$
    s = ut + \frac{1}{2}at^2, \quad v = u + at
    $$      """)
        s = u*t + 0.5*a*t*t
        v = u + a*t
        st.write('Inserting your given values in these equations, we get: ')
        st.write('Displacement $(s) = ut + \frac{1}{2}at^2 =$ ', s)
        st.write('Final Velocity $(v) = u + at =$ ', v)

elif (option_s is False and option_u is False and option_v and option_a and option_t):  # ['Final Velocity (v)', 'Acceleration (a)', 'Time 
    v = ask('Final Velocity (v)')
    a = ask('Acceleration (a)')
    t = ask('Time 
    if st.button('Click here to calculate and check the necessary equations') is True:
        st.write("""        The equations which are used to determine the values:
        $$
    s = vt - \frac{1}{2}at^2, \quad u= v - at
    $$      """)
        s = v*t - 0.5*a*t*t
        u = v - a*t
        st.write('Inserting your given values in these equations, we get: ')
        st.write('Displacement $(s) = vt - \frac{1}{2}at^2 =$ ', s)
        st.write('Initial Velocity $(u) =u - at =$ ', u)

elif (option_s and option_u is False and option_v is False and option_a and option_t):  # ['Displacement (s)', 'Acceleration (a)', 'Time 
    s = ask('Displacement (s)')
    a = ask('Acceleration (a)')
    t = ask('Time 
    if st.button('Click here to calculate and check the necessary equations') is True:
        st.write("""        The equations which are used to determine the values:
        $$
    u = \frac{s}{t} - \frac{1}{2}at, \quad v = \frac{s}{t} + \frac{1}{2}at
    $$      """)
        try:
            u = (s - 0.5*a*t*t)/t
            v = (s + 0.5*a*t*t)/t
        except ZeroDivisionError:
            st.write('Here $t=0$ is not allowed.')
            u = None
            v = None
        st.write('Inserting your given values in these equations, we get: ')
        st.write('Initial Velocity $(u) = \frac{s}{t} - \frac{1}{2}at =$ ', u)
        st.write('Final Velocity $(v) = \frac{s}{t} + \frac{1}{2}at =$ ', v)

elif (option_s and option_u is False and option_v and option_a is False and option_t):  # ['Displacement (s)', 'Final Velocity (v)', 'Time 
    s = ask('Displacement (s)')
    v = ask('Final Velocity (v)')
    t = ask('Time 
    if st.button('Click here to calculate and check the necessary equations') is True:
        st.write("""        The equations which are used to determine the values:
        $$
    u = \frac{2s}{t} - v, \quad a = \frac{2(vt-s)}{t^2}
    $$      """)
        try:
            u = (2*s)/t - v
            a = 2*(v*t-s)/(t*t)
        except ZeroDivisionError:
            st.write('Here $t=0$ is not allowed.')
            u = None
            a = None
        st.write('Inserting your given values in these equations, we get: ')
        st.write('Initial Velocity $(u) = \frac{2s}{t} - v =$ ', u)
        st.write('Acceleration $(a)  = \frac{2(vt-s)}{t^2} =$ ', a)

elif (option_s and option_u is False and option_v and option_a and option_t is False):  # ['Displacement (s)', 'Final Velocity (v)', 'Acceleration (a)']
    s = ask('Displacement (s)')
    v = ask('Final Velocity (v)')
    a = ask('Acceleration (a)')
    if st.button('Click here to calculate and check the necessary equations') is True:
        st.write("""        The equations which are used to determine the values:
        $$
    u = \sqrt{v^2 -2as}, \quad t = \frac{v}{a} - \frac{\sqrt{v^2 - 2as}}{a}
    $$      """)
        try:
            u = math.sqrt(v*v - 2*a*s)
            t = v/a - math.sqrt(v*v - 2*a*s)/a
        except ZeroDivisionError:
            st.write('Here $a=0$ is not allowed.')
            t = None
        except ValueError:
            st.write('Here $v^2 < 2as$ is not allowed.')
            u = None
            t = None
        st.write('Inserting your given values in these equations, we get: ')
        st.write('Initial Velocity $(u) = \sqrt{v^2 -2as} =$ ', u)
        st.write('Time $

elif (option_s and option_u and option_v is False and option_a is False and option_t):  # ['Displacement (s)', 'Initial Velocity (u)', 'Time 
    s = ask('Displacement (s)')
    u = ask('Initial Velocity (u)')
    t = ask('Time 
    if st.button('Click here to calculate and check the necessary equations') is True:
        st.write("""        The equations which are used to determine the values:
        $$
    v = \frac{2s}{t}-u, \quad a = \frac{2(s-ut)}{t^2}
    $$      """)
        try:
            v = (2*s)/t - u
            a = 2*(s-u*t)/(t*t)
        except ZeroDivisionError:
            st.write('Here $t=0$ is not allowed.')
            v = None
            a = None
        st.write('Inserting your given values in these equations, we get: ')
        st.write('Final Velocity $(v) = \frac{2s}{t}-u =$ ', v)
        st.write('Acceleration $(a)= \frac{2(s-ut)}{t^2} =$ ', a)

elif (option_s and option_u and option_v is False and option_a and option_t is False):  # ['Displacement (s)', 'Initial Velocity (u)', 'Acceleration (a)']
    s = ask('Displacement (s)')
    u = ask('Initial Velocity (u)')
    a = ask('Acceleration (a)')
    if st.button('Click here to calculate and check the necessary equations') is True:
        st.write("""        The equations which are used to determine the values:
        $$
    v = \sqrt{u^2 + 2as}, \quad t = -\frac{u}{a} + \frac{\sqrt{u^2 + 2as}}{a}
    $$      """)
        try:
            v = math.sqrt(u*u + 2*a*s)
            t = -u/a + math.sqrt(u*u +2*a*s)/a
        except ZeroDivisionError:
            st.write('Here $a=0$ is not allowed.')
            t = None
        except ValueError:
            st.write('Here $v^2 < 2as$ is not allowed.')
            v = None
            t = None
        st.write('Inserting your given values in these equations, we get: ')
        st.write('Final Velocity $(v) = \sqrt{u^2 + 2as} =$ ', v)
        st.write('Time $

elif (option_s and option_u and option_v and option_a is False and option_t is False):  # ['Displacement (s)', 'Initial Velocity (u)', 'Final Velocity (v)']
    s = ask('Displacement (s)')
    u = ask('Initial Velocity (u)')
    v = ask('Final Velocity (v)')
    if st.button('Click here to calculate and check the necessary equations') is True:
        st.write("""        The equations which are used to determine the values:
        $$
    a = \frac{v^2 - u^2}{2s}, \quad t = \frac{2s}{u+v}
    $$      """)
        try:
            a = (v*v - u*u)/(2*s)
        except ZeroDivisionError:
            st.write('Here $s=0$ is not allowed.')
            a = None
        try:
            t = (2*s)/(u+v)
        except ZeroDivisionError:
            st.write('Here $u=v=0$ is not allowed.')
            t = None
        st.write('Inserting your given values in these equations, we get: ')
        st.write('Acceleration $(a) = \frac{v^2 - u^2}{2s} =$ ', a)
        st.write('Time $

Пример ввода-вывода:

#Input:
Displacement (s) : 120
Initial Velocity (u) : 10
Final Velocity (v) : 50

#Outpt:
Acceleration (a) = 10.0
Time 

Я программист начального уровня. Как вы можете видеть в моем коде, есть много условных операторов и повторений строк. Я думаю, что определение некоторых функций поможет. Но я не понимаю, как это сделать. Мне нужны советы и помощь от обозревателей, как:

  • Оптимизировать код
  • Уменьшите количество повторов
  • Сделайте структуру кода более компактной и эффективной
  • Активировать LaTeX ($...$) внутри labelс st.number_imput а также st.checkbox для лучшего взгляда

3 ответа
3

Группировать входные вызовы:

Как вы сказали, некоторый код повторяется. Ничего страшного, вы здесь, чтобы учиться 🙂

Первое, что я заметил, это кучу ask звонки в начале многих дел вашего elif. Я подумал: «Почему бы не связать их вместе?»

Вы можете сделать это, написав ask_multiple функция, которая принимает список подсказок и применяет ask к каждой подсказке. Например:

def ask_multiple(prompts):
    return [ask(prompt) for prompt in prompts]

Вот пример использования:

# ...
u, v, t = ask_multiple(['Initial Velocity (u)', 'Final Velocity (v)', 'Time 

Вы также можете обернуть эту функцию и иметь только один ask функция. Но тогда, если вам нужна только одна подсказка, было бы странно написать ask(["Prompt here >> "]) чтобы вы все еще могли различать строку и ввод списка:

def _ask(prompt):
    return st.number_input(f'{variable} :', step=None, format="%f")

def ask(prompts):
    if isinstance(prompts, str):
        return _ask(prompts)
    elif isinstance(prompts, (list, tuple)):
        return [_ask(prompt) for prompt in prompts]
    else:
        raise ValueError("Expected a string prompt or a list of prompts.")

Примеры использования:

u, v, t = ask(['Initial Velocity (u)', 'Final Velocity (v)', 'Time 
u = ask('Initial velocity (u)')

Если вы предпочитаете, вы также можете изменить свой ask функцию так, чтобы она вызывалась так:

ask('Initial Velocity (u)', 'Final Velocity (v)', 'Time 

вместо

ask(['Initial Velocity (u)', 'Final Velocity (v)', 'Time 

Вы можете добиться этого, используя *args обозначение:

def ask(*prompts):
    return [_ask(prompt) for prompt in prompts]

Теперь это ask всегда возвращает список значений. Если хотите, можете использовать if чтобы изменить это так, чтобы возвращалось только одно значение, если есть только одно приглашение.

  • Большое вам спасибо за ваши ответы. У вас есть что еще предложить? 🙂 Кроме того, что вы думаете о проблеме с этикеткой LaTeX, о которой я упоминал в конце?

    — раф

Объедините свои варианты

Кажется, у вас есть ряд переменных с одинаковыми именами и параметров кодирования. Представьте, что вы улучшили свой калькулятор, и теперь для него требуется 20 вариантов. Будет ли у вас переменная для каждого из них?

Возможно нет!

Здесь вы можете сделать несколько вещей. Самый простой способ — собрать все вместе в список:

options = [
    st.checkbox('Displacement (s)'),
    st.checkbox('Initial Velocity (u)'),
    st.checkbox('Final Velocity (v)'),
    st.checkbox('Acceleration (a)'),
    st.checkbox('Time 
]

Теперь все варианты вместе. А теперь поворот … вам нужно проиндексировать в options чтобы получить к ним доступ, и options[0] или же options[3] не выглядит описательным, поэтому вы можете использовать enum (документы) или что-то подобное, чтобы дать имена к индексам.

Другая возможность — сохранить их в словаре, чтобы «индексация» была доступна для чтения с самого начала:

options = {
    's': st.checkbox('Displacement (s)'),
    'u': st.checkbox('Initial Velocity (u)'),
    'v': st.checkbox('Final Velocity (v)'),
    'a': st.checkbox('Acceleration (a)'),
    't': st.checkbox('Time 
}

Это масштабируется немного лучше. Вы также можете сделать создание словаря более автоматическим, что также окажется полезным, если вы позже измените метод для чтения параметров из st.checkbox к чему-то другому:

opts = [
    ('s', 'Displacement'),
    ('u', 'Initial Velocity'),
    ('v', 'Final Velocity'),
    ('a', 'Acceleration'),
    ('t', 'Time'),
]
options = {letter: st.checkbox(f"{name} ({letter})") for letter, name in opts}

Если все становится по-настоящему необычным, вы мощь подумайте о создании простого класса, который загружает параметры, проверяет, верны ли определенные комбинации и т. д.

В связи с этим рассмотрите возможность добавления коротких вспомогательных функций, которые проверяют определенные условия, вместо того, чтобы писать все условия явно в if а также elifс. То есть преобразовать длинные / длинные наборы условий в короткие вспомогательные функции с помощью описательный name, чтобы ваши условные выражения было легче читать.

Имеет ли это смысл?

  • Почему вы разместили это как два отдельных ответа? Похоже, их можно хорошо сочетать.

    — мачта

  • @Mast не совсем уверен, как работает это сообщество, но у меня возникла идея, что разные люди могут затрагивать только некоторые вопросы и, следовательно, писать ответы только по этим частям. Разделение проблем также помогает людям соглашаться / не соглашаться с отзывами. Например, каждый может согласиться с тем, что я говорю о объединении входных вызовов, но категорически не согласен с тем, как я предлагаю объединение опций, или с тем, что я предлагаю в других частях кода OP.

    — РГО

  • В этом нет ничего плохого, но в наши дни мы не часто видим это. Учитывая, что оба ответа касаются объединения, я бы подумал, что они имеют больше смысла как один ответ вместе.

    — мачта

  • Может, мне не повезло с «именованием» ответов. Просто код здесь довольно большой, и после того, как я подумаю, я уверен, что придумаю другие приятные советы 🙂

    — РГО

  • 1

    Нет проблем, оба ответа имеют смысл.

    — мачта

Переписать длинные условные выражения

Когда у вас есть действительно длинные условные выражения, которые зависят от проверки состояния множества различных вещей, ваши условные выражения улучшатся, если вы сможете сделать их более описательными и / или короче.

Сделать их короче проще, и вам не нужно делать другие вещи, предлагаемые в других ответах. Например, вы можете просто сделать следующее:

# ask for the option_X inputs

options = [option_s, option_u, option_v, option_a, option_t]

if sum(options) < 3:
    st.write("...")
elif sum(options) == 3:
    st.write("...")
else:
    st.write("...")

if options == [False, True, True, False, True]:   # ['Initial Velocity (u)', 'Final Velocity (v)', 'Time 
    # ...
elif options == [False, True, True, True, False]:    # ['Initial Velocity (u)', 'Final Velocity (v)', 'Acceleration (a)']
    # ...
elif options == [False, True, False, True, True]:     # ['Initial Velocity (u)', 'Acceleration (a)', 'Time 
    # ...

И так далее. Обратите внимание, что условные выражения теперь намного короче, но не всегда легко понять сами по себе, поэтому комментарии, которые у вас есть, помогают вам вспомнить, что вы проверяете.

(Кстати, в Python 3.10+ вы, вероятно, захотите сделать это с помощью структурное сопоставление с образцом.)

Еще вы могли бы попробовать написать вспомогательные функции, имена и / или аргументы которых точно говорят вам, что вы тестируете.

Например, если вы определяете options таким образом (предложение из другого ответа):

opts = [
    ('s', 'Displacement'),
    ('u', 'Initial Velocity'),
    ('v', 'Final Velocity'),
    ('a', 'Acceleration'),
    ('t', 'Time'),
]
options = {letter: st.checkbox(f"{name} ({letter})") for letter, name in opts}

тогда вы можете написать эту вспомогательную функцию:

def check_options(options, values):
    for option in options:
        if option in values and not options[option]:
            return False
        elif option not in values and options[option]:
            return False
    return True

Эта функция принимает словарь options с установленными параметрами и список / кортеж с буквами параметров, которые вы хотите True. Он возвращается True если все они, и только они, True.

Например:

check_options(
  {
    's': True,
    'u': False,
  },
  ['s']
) # Returns True
check_options(
  {
    's': True,
    'u': True,
  },
  ['s']
) # Returns False
check_options(
  {
    's': False,
    'u': False,
  },
  ['s']
) # Returns False

Я написал функцию более подробно выше, это могло быть что-то вроде:

def check_options(options, values):
    for option, flag in options.items():
        if flag != (option in values):
            return False
    return True

Не торопитесь, чтобы переварить это.

Благодаря этой функции ваш if а также elif стали

if check_options(options, ['s', 'u', 'v']):
    # ...
elif check_options(options, ['u', 'v', 'a']):
    # ...

Теперь код очевиден, и вы знаете, какие варианты проверяете 🙂

Латекс

Кстати, если вы в конечном итоге создали параметры, как я упоминал выше, с dict:

opts = [
    ('s', 'Displacement'),
    ('u', 'Initial Velocity'),
    ('v', 'Final Velocity'),
    ('a', 'Acceleration'),
    ('t', 'Time'),
]
options = {letter: st.checkbox(f"{name} ({letter})") for letter, name in opts}

Тогда добавление LaTeX будет заключаться в добавлении $$ в строку f:

opts = [
    ('s', 'Displacement'),
    ('u', 'Initial Velocity'),
    ('v', 'Final Velocity'),
    ('a', 'Acceleration'),
    ('t', 'Time'),
]
options = {letter: st.checkbox(f"{name} (${letter}$)") for letter, name in opts}

Большое вам спасибо, с моей стороны, LaTeX не работает в f-строке внутри st.checkbox.

— раф

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *