Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Do string-based math with pythonista. mostly because I don't want to buy a fancy string calculator app when I already have python available.
# for the pythonista ios app (which comes with sympy bundled)
# allows one to create a text file, for example in drafts, containing the string
# x - (9 * 5) = x / 2
# and then share it into this pythonista script (or copy it to clipboard and run the script)
# and get the answer (90) back.
# see comments at end for more examples of stuff that it can solve.
#
# I wrote this because it annoys me that there are paid apps that do ticker tape calculator kinds of
# things. Why should I pay extra to add up a list of numbers whilst still getting to see the numbers when
# I have a whole programming language available to me!
#
# SPECIAL BONUS FUNCTIONALITY:
# if you pass it a multiline string containing only numbers
# it assumes you just want to sum the lines, then does so.
#
# caveats:
# 1. it may run slow the first time because sympy is a big import; see the in-pythonista docs for sympy.
# 2. no error handling at all, no edge cases tested, this is a quick/dirty lazy thing.
#
# OPTION: I find it annoying to get fractions and things out of this script, so I've included a flag right
# at the beginning (SIMPLE_OUTPUT) to get simple python ints and floats out. This will convert
# fractions to floats and probably blow up with irrationals and stuff (no clue what it'll do with
# irrationals, actually. thisis for stuff like adding up your monthly expenses, not for taking square
# roots!). Set it to false to just get the raw sympy output.
SIMPLE_OUTPUT = True
from sympy.parsing.sympy_parser import parse_expr
from sympy.core.numbers import Number, Integer
from sympy import solve, Eq
import clipboard, appex
input = appex.get_text()
text = input if input else clipboard.get()
# bonus functionality
def convert_to_num(s):
s = s.strip()
try:
return int(s)
except:
try:
return float(s)
except:
return None
def check_if_simple_sum(s):
s = s.strip()
if '\n' in s:
lines = s.split()
try:
return sum([convert_to_num(x) for x in lines])
except:
return False
return False
# core functionality
def smush_multiline(s):
if '\n' in s:
return ''.join(s.split()).strip()
else:
return s
def convert_to_python_number(res):
if not SIMPLE_OUTPUT:
return res
if isinstance(res, Integer):
return int(res)
return float(res)
def extract_python_solutions(res):
if len(res) == 1:
return convert_to_python_number(res[0])
else:
return list(res)
def solve_single_string(s):
result = parse_expr(s)
if isinstance(result, Number):
return convert_to_python_number(result)
else:
return extract_python_solutions(solve(result))
def mathit(s):
simple_sum = check_if_simple_sum(s)
if simple_sum:
return simple_sum
s = smush_multiline(s)
if '=' in s:
terms = [x.strip() for x in s.split("=")]
equation = Eq(parse_expr(terms[0]), parse_expr(terms[1]))
return extract_python_solutions(solve(equation))
else:
return solve_single_string(s)
print(mathit(text))
# examples/tests
#simple = "3 + 5"
#eqzero = "x - 3"
#equation = "x + 3 = 5"
#twosided = 'x - (9 * 5) = x / 2'
#multiline = ''' 10 +
#20
#-5
#'''
#parens = 'x * (2 + 3) = 20'
#noparens = 'x * 2 + 3 = 20'
#simplesum = '''5
#10.2
#15
#'''
#print(mathit(simple)) # 8 (simple math)
#print(mathit(eqzero)) # 3 (solves for x, assuming by default that the other side of equation is 0)
#print(mathit(equation)) # 2 (solves for x with specified sided of equation)
#print(mathit(twosided)) # 90 (variable on both sides works)
#print(mathit(multiline)) # 25 (parses multiline input by concatenating the lines)
#print(mathit(parens)) # 4 (not 8.5, i.e., parens for associativity works)
#print(mathit(noparens)) # 8.5 (see previous; returns 17/2 if SIMPLE_OUTPUT is False)
#print(mathit(simplesum)) # 30.2 (just sum multiline strings when there are no operators or variables)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment