Я сделал калькулятор чтобы помочь бенгальским старшеклассникам в решении математических задач по уравнениям Ньютона линейного движения, также известным как уравнения СУВАТ. Я использую 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 ответа
Группировать входные вызовы:
Как вы сказали, некоторый код повторяется. Ничего страшного, вы здесь, чтобы учиться 🙂
Первое, что я заметил, это кучу 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
чтобы изменить это так, чтобы возвращалось только одно значение, если есть только одно приглашение.
Объедините свои варианты
Кажется, у вас есть ряд переменных с одинаковыми именами и параметров кодирования. Представьте, что вы улучшили свой калькулятор, и теперь для него требуется 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
.
— раф
Большое вам спасибо за ваши ответы. У вас есть что еще предложить? 🙂 Кроме того, что вы думаете о проблеме с этикеткой LaTeX, о которой я упоминал в конце?
— раф