Check result using 4 operations based with Python - python

I'm struggling to make a Python program that can solve riddles such as:
get 23 using [1,2,3,4] and the 4 basic operations however you'd like.
I expect the program to output something such as
# 23 reached by 4*(2*3)-1
So far I've come up with the following approach as reduce input list by 1 item by checking every possible 2-combo that can be picked and every possible result you can get to.
With [1,2,3,4] you can pick:
[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]
With x and y you can get to:
(x+y),(x-y),(y-x),(x*y),(x/y),(y/x)
Then I'd store the operation computed so far in a variable, and run the 'reducing' function again onto every result it has returned, until the arrays are just 2 items long: then I can just run the x,y -> possible outcomes function.
My problem is this "recursive" approach isn't working at all, because my function ends as soon as I return an array.
If I input [1,2,3,4] I'd get
[(1+2),3,4] -> [3,3,4]
[(3+3),4] -> [6,4]
# [10,2,-2,24,1.5,0.6666666666666666]
My code so far:
from collections import Counter
def genOutputs(x,y,op=None):
results = []
if op == None:
op = str(y)
else:
op = "("+str(op)+")"
ops = ['+','-','*','/','rev/','rev-']
z = 0
#will do every operation to x and y now.
#op stores the last computated bit (of other functions)
while z < len(ops):
if z == 4:
try:
results.append(eval(str(y) + "/" + str(x)))
#yield eval(str(y) + "/" + str(x)), op + "/" + str(x)
except:
continue
elif z == 5:
results.append(eval(str(y) + "-" + str(x)))
#yield eval(str(y) + "-" + str(x)), op + "-" + str(x)
else:
try:
results.append(eval(str(x) + ops[z] + str(y)))
#yield eval(str(x) + ops[z] + str(y)), str(x) + ops[z] + op
except:
continue
z = z+1
return results
def pickTwo(array):
#returns an array with every 2-combo
#from input array
vomit = []
a,b = 0,1
while a < (len(array)-1):
choice = [array[a],array[b]]
vomit.append((choice,list((Counter(array) - Counter(choice)).elements())))
if b < (len(array)-1):
b = b+1
else:
b = a+2
a = a+1
return vomit
def reduceArray(array):
if len(array) == 2:
print("final",array)
return genOutputs(array[0],array[1])
else:
choices = pickTwo(array)
print(choices)
for choice in choices:
opsofchoices = genOutputs(choice[0][0],choice[0][1])
for each in opsofchoices:
newarray = list([each] + choice[1])
print(newarray)
return reduceArray(newarray)
reduceArray([1,2,3,4])

The largest issues when dealing with problems like this is handling operator precedence and parenthesis placement to produce every possible number from a given set. The easiest way to do this is to handle operations on a stack corresponding to the reverse polish notation of the infix notation. Once you do this, you can draw numbers and/or operations recursively until all n numbers and n-1 operations have been exhausted, and store the result. The below code generates all possible permutations of numbers (without replacement), operators (with replacement), and parentheses placement to generate every possible value. Note that this is highly inefficient since operators such as addition / multiplication commute so a + b equals b + a, so only one is necessary. Similarly by the associative property a + (b + c) equals (a + b) + c, but the below algorithm is meant to be a simple example, and as such does not make such optimizations.
def expr_perm(values, operations="+-*/", stack=[]):
solution = []
if len(stack) > 1:
for op in operations:
new_stack = list(stack)
new_stack.append("(" + new_stack.pop() + op + new_stack.pop() + ")")
solution += expr_perm(values, operations, new_stack)
if values:
for i, val in enumerate(values):
new_values = values[:i] + values[i+1:]
solution += expr_perm(new_values, operations, stack + [str(val)])
elif len(stack) == 1:
return stack
return solution
Usage:
result = expr_perm([4,5,6])
print("\n".join(result))

Related

How to increment and decrement with a string Python?

Hope you all are doing well in these times.
here's my code:
def ab(n):
first = 0
last = -1
endprod = n[first] + n[last]
endprod2 = n[first+1] + n[last-1]
endprod3 = n[first+2] + n[last-2]
endprod4 = n[first+3] + n[last-3]
endprod5 = n[first+4] + n[last-4]
endprod100 = endprod[::-1] + endprod2[::-1] + endprod3[::-1]+ endprod4[::-1]+ endprod5[::-1]
return endprod100
I was able do to it, however mine isn't a loop. Is there a way to convert my code into a for loop. So, increment by 1 and decrement by 1.
Thanks,
Try this:
def ab(n):
result = ''
for j in range(len(n) // 2):
result += n[-j-1] + n[j]
if len(n) % 2 == 1:
result += n[len(n) // 2]
return result
You also need the part
if len(n) % 2 == 1:
result += n[len(n) // 2]
because your input string might have an odd number of characters
Examples:
>>> ab('0123456789')
'9081726354'
>>> ab('01234956789')
'90817263549'
If you want to reuse your original logic:
def ab(n):
result = ''
first = 0
last = -1
for j in range(len(n) // 2):
result += n[last-j] + n[first+j]
if len(n) % 2 == 1:
result += n[len(n) // 2]
return result
You could also recurse it:
def ab(s):
if len(s)>2:
return s[-1]+s[0]+ab(s[1:-1])
else:
return s
But the last part of Riccardo's answer fits your question more closely.
you need to split your string for your loop, means first you broke your string to half then build your string, you could use zip to iterate over multiple iteratable. something like this:
def ab(s):
out = ""
for v0,v1 in zip(s[:len(s)//2 -1 :-1], s[:len(s)//2 ]):
out += v0 + v1
return out
the better version you should write without loop.
like this:
out = "".join(map(lambda x: "".join(x), zip(s[:len(s)//2 -1 :-1], s[:len(s)//2 ])))
There are already a lot of good answers that are probably clearer, easier to read and much better suited for learning purposes than what I'm about to write. BUT... something I wanted to bring up (maybe just telling myself, because I tend to forget it) is that sometimes destroying the original argument can facilitate this sort of things.
In this case, you could keep "poping" left and right, shrinking your string until you exhaust it
def swap_destr(my_str):
result = ""
while len(my_str) > 1:
result += my_str[-1] # Last char
result += my_str[0] # First char
my_str = my_str[1:-1] # Shrink it!
return result + my_str # + my_str just in case there 1 char left
print(swap_destr("0123456789"))
print(swap_destr("123456789"))
# Outputs:
# 9081726354
# 918273645
This is also a good problem to see (and play with) recursion:
def swap_recur(my_str):
if len(my_str) <= 1:
return my_str
return my_str[-1] + my_str[0] + swap_recur(my_str[1:-1])
print(swap_recur("0123456789"))
print(swap_recur("123456789"))
# Outputs:
# 9081726354
# 918273645

How to make this breadth first search faster?

I have a search algorithm that looks for combinations of add and multiply functions to reach a certain range of number from a certain range of numbers. It is searching for the shortest program, a program being a something like AAMMA where the initial number is added, added, multiplied, multiplied, add where the ending number is in the range r to s. It has to work for every number in the starting range p to q.
The input is a and m, what you are adding and multiplying by(num+a), (num*m) for each function. What I am doing is trying every combination of functions until I find one that works, stopping that branch if it gets too big. If I find "program" that works I try the program on all of the other numbers in the starting range. It does this until either it finds no branches that don't reach the range without going over.
I know the search isn't super typical, but I don't think there is a possibility for duplicates so I didn't include a found list.
It works for smaller ranges and inputs like
Problem3("1 2 2 3 10 20")
but for larger ranges, it just takes forever my test case is
Problem3("8 13 28 91 375383947 679472915")
which I haven't even seen complete. What is my best approach from here, multithreading(hope not), making my inner functions faster somehow or just scraping this approach.
def Problem3(s):
a,m,p,q,r,s = list(map(int, s.split(" ")))
print(str(a) + "-C-" + str(m) + " processor")
print("Input guarenteed between " + str(p) + " and " + str(q))
print("Output is real number between " + str(r) + " and " + str(s))
open_set = queue.Queue()
# curr path depth
open_set.put([p, "", 0])
while not open_set.empty():
subroot = open_set.get()
multiCurr = subroot[0] * m
addCurr = subroot[0] + a
depth = subroot[2] + 1
if r <= addCurr <= s:
truePath = True
#If we find a working path, we need to check if it works for the other things
path = subroot[1] + "A"
for x in range(p, q+1):
for op in path:
if op == "A":
x += a
if op == "M":
x *= m
if r <= x <= s:
pass
else:
truePath = False
break
if truePath:
print("Found " + path + " at depth " + str(depth) + " with starting number " + str(p) + ", output " + str())
if r <= multiCurr <= s:
truePath = True
path = subroot[1] + "M"
for x in range(p, q+1):
for op in path:
if op == "A":
x += a
if op == "M":
x *= m
if r <= x <= s:
pass
else:
truePath = False
break
if truePath:
print("Found " + path + " at depth " + str(depth) + " with starting number " + str(p) + ", output " + str())
if addCurr > s and multiCurr > s:
pass
elif multiCurr > s:
open_set.put([addCurr, subroot[1] + "A", depth])
elif addCurr > s:
open_set.put([multiCurr, subroot[1] + "M", depth])
else:
open_set.put([multiCurr, subroot[1] + "M", depth])
open_set.put([addCurr, subroot[1] + "A", depth])
You don't need to test every value in the range(p, q + 1) sequence. You only need to test for p and q. If it works for the lowest and the highest number, it'll work for all the values in between, because the problem has been reduced to just multiplication and addition. You really only need to test the progress of program(q), keeping it below s, until you have created the shortest program that puts program(p) at or above r.
However, this isn't really a great problem for breath-first search; your second example would require testing 17.6 trillion possible states; the shortest solution is 44 characters long, so a breath-first search would explore 2 ** 44 states, so 17,592,186,044,416 to be exact! Even using a compiled programming language like C would take a long, long time to find the solution using such a search. Instead, you can just generate the string using a bit of math.
You can calculate the maximum number of multiplications needed here with int(math.log(s // q, m)), that's the number of times you can multiply with m when starting at q and still stay below s. You can't ever use more multiplications! With math.ceil(math.log(r / p, m)) you can find the minimum number of multiplications that would put p at or above r. To minimise the program length, pick the lower value of those two numbers.
Then, start fitting in A additions, before each M multiplication. Do so by taking i as the number of M characters that are to follow, then dividing both r and s by m ** i. These inform the number a additions to p and q that together with the subsequent multiplications bring it closest to r and s; the difference with the current p and q let you calculate the minimum number of A characters you can insert here to keep within the [r, s] range. For p, round up, for q, round down.
Repeat this procedure for every subsequent M operation, updating the p and q values with the results each time:
import math
def problem3(s):
a, m, p, q, r, s = map(int, s.split())
p_mult = math.ceil(math.log(math.ceil(r / p), m))
q_mult = int(math.log(s // q, m))
mult = min(p_mult, q_mult)
program = []
for i in range(mult, -1, -1):
p_additions = math.ceil((math.ceil(r / (m ** i)) - p) / a)
q_additions = ((s // (m ** i)) - q) // a
additions = min(p_additions, q_additions)
program += [additions * 'A']
if i:
p, q = (p + (additions * a)) * m, (q + (additions * a)) * m
program += ['M']
return ''.join(program)
This is a closed-form solution, no search needed. The result is guaranteed to be the shortest:
>>> problem3("1 2 2 3 10 20")
'AMM'
>>> problem3("8 13 28 91 375383947 679472915")
'AAAAAAMAAMAAAAAAAAAAAMAAAAAMAAAMAAAAMAAAAAAA'

Binary addition program in python

I am writing a binary addition program but am unsure as to why when the inputs start with a zero the output is incorect.The output is also incorrect when the program has to add zeros to the start of one of the inputs to make them the same length.
a = input('Enter first binary number\t')
b = input('Enter second binary number\t')
carry = 0
answer = ""
length = (max(len(a),len(b))) - min(len(a),len(b))
if b > a:
a = length * '0' + a
elif a > b:
b = length * '0' + b
print(a)
print(b)
for i in range(len(a)-1, -1, -1):
x = carry
if a[i] == '1': x += 1
else: x = 0
if b[i] == '1': x += 1
else: x = 0
if x % 2 == 1: answer = '1' + answer
else: answer = '0' + answer
if x < 2: carry = 0
else: carry = 1
if carry == 1: answer = '1' + answer
print(answer)
What an excellent opportunity to explore some Boolean Logic.
Adding binary like this can be done with two "half adders" and an "or"
First of all the "Half Adder" which is a XOR to give you a summed output and an AND to give you a carry.
[EDIT as per comments: python does have an XOR implemented as ^ but not as a "word" like and not or. I am leaving the answer as is, due to the fact it is explaining the Boolean logic behind a binary add]
As python doesn't come with a XOR, we will have to code one.
XOR itself is two AND's (with reversed inputs) and an OR, as demonstrated by this:
This would result is a simple function, like this:
def xor(bit_a, bit_b):
A1 = bit_a and (not bit_b)
A2 = (not bit_a) and bit_b
return int(A1 or A2)
Others may want to write this as follows:
def xor(bit_a, bit_b):
return int(bit_a != bit_b)
which is very valid, but I am using the Boolean example here.
Then we code the "Half Adder" which has 2 inputs (bit_a, bit_b) and gives two outputs the XOR for sum and the AND for carry:
def half_adder(bit_a, bit_b):
return (xor(bit_a, bit_b), bit_a and bit_b)
so two "Half Adders" and an "OR" will make a "Full Adder" like this:
As you can see, it will have 3 inputs (bit_a, bit_b, carry) and two outputs (sum and carry). This will look like this in python:
def full_adder(bit_a, bit_b, carry=0):
sum1, carry1 = half_adder(bit_a, bit_b)
sum2, carry2 = half_adder(sum1, carry)
return (sum2, carry1 or carry2)
If you like to look at the Full Adder as one logic diagram, it would look like this:
Then we need to call this full adder, starting at the Least Significant Bit (LSB), with 0 as carry, and work our way to the Most Significant Bit (MSB) where we carry the carry as input to the next step, as indicated here for 4 bits:
This will result is something like this:
def binary_string_adder(bits_a, bits_b):
carry = 0
result = ''
for i in range(len(bits_a)-1 , -1, -1):
summ, carry = full_adder(int(bits_a[i]), int(bits_b[i]), carry)
result += str(summ)
result += str(carry)
return result[::-1]
As you see we need to reverse the result string, as we built it up "the wrong way".
Putting it all together as full working code:
# boolean binary string adder
def rjust_lenght(s1, s2, fill='0'):
l1, l2 = len(s1), len(s2)
if l1 > l2:
s2 = s2.rjust(l1, fill)
elif l2 > l1:
s1 = s1.rjust(l2, fill)
return (s1, s2)
def get_input():
bits_a = input('input your first binary string ')
bits_b = input('input your second binary string ')
return rjust_lenght(bits_a, bits_b)
def xor(bit_a, bit_b):
A1 = bit_a and (not bit_b)
A2 = (not bit_a) and bit_b
return int(A1 or A2)
def half_adder(bit_a, bit_b):
return (xor(bit_a, bit_b), bit_a and bit_b)
def full_adder(bit_a, bit_b, carry=0):
sum1, carry1 = half_adder(bit_a, bit_b)
sum2, carry2 = half_adder(sum1, carry)
return (sum2, carry1 or carry2)
def binary_string_adder(bits_a, bits_b):
carry = 0
result = ''
for i in range(len(bits_a)-1 , -1, -1):
summ, carry = full_adder(int(bits_a[i]), int(bits_b[i]), carry)
result += str(summ)
result += str(carry)
return result[::-1]
def main():
bits_a, bits_b = get_input()
print('1st string of bits is : {}, ({})'.format(bits_a, int(bits_a, 2)))
print('2nd string of bits is : {}, ({})'.format(bits_b, int(bits_b, 2)))
result = binary_string_adder(bits_a, bits_b)
print('summarized is : {}, ({})'.format(result, int(result, 2)))
if __name__ == '__main__':
main()
two internet sources used for the pictures:
https://www.electronics-tutorials.ws/combination/comb_7.html
https://www.allaboutcircuits.com/textbook/digital/chpt-7/the-exclusive-or-function-xor/
For fun, you can do this in three lines, of which two is actually getting the input:
bits_a = input('input your first binary string ')
bits_b = input('input your second binary string ')
print('{0:b}'.format(int(bits_a, 2) + int(bits_b, 2)))
And in your own code, you are throwing away a carry if on second/subsequent iteration one of the bits are 0, then you set x = 0 which contains the carry of the previous itteration.
this is how i managed to complete this, hope you find this useful.
#Binary multiplication program.
def binaryAddition(bin0, bin1):
c = 0
answer = ''
if len(bin0) > len(bin1):
bin1 = (len(bin0) - len(bin1))*"0" + bin1
elif len(bin1) > len(bin0):
bin0 = (len(bin1) - len(bin0))*"0" + bin0
#Goes through the binary strings and tells the computer what the anser should be.
for i in range(len(bin0)-1,-1,-1):
j = bin0[i]
k = bin1[i]
j, k = int(j), int(k)
if k + j + c == 0:
c = 0
answer = '0' + answer
elif k + j + c == 1:
c = 0
answer = '1' + answer
elif k + j + c == 2:
c = 1
answer = '0' + answer
elif k + j + c == 3:
c = 1
answer = '1' + answer
else:
print("There is something wrong. Make sure all the numbers are a '1' or a '0'. Try again.") #One of the numbers is not a 1 or a 0.
main()
return answer
def binaryMultiplication(bin0,bin1):
answer = '0'*8
if len(bin0) > len(bin1):
bin1 = (len(bin0) - len(bin1))*"0" + bin1
elif len(bin1) > len(bin0):
bin0 = (len(bin1) - len(bin0))*"0" + bin0
for i in range(len(bin0)-1,-1,-1):
if bin1[i] == '0':
num = '0'*len(answer)
elif bin1[i] == '1':
num = bin0 + '0'*((len(bin0)-1)-i)
answer = binaryAddition(num, answer)
print(answer)
def main():
try:
bin0, bin1 = input("Input both binary inputs separated by a space.\n").split(" ")
except:
print("Something went wrong. Perhaps there was not a space between you numbers.")
main()
binaryMultiplication(bin0,bin1)
choice = input("Do you want to go again?y/n\n").upper()
if choice == 'Y':
main()
else: input()
main()
The following adds integers i1 and i2 using bitwise logical operators (i1 and i2 are overwritten). It computes the bitwise sum by i1 xor i2 and the carry bit by (i1 & i2)<<1. It iterates until the shift register is empty. In general this will be a lot faster than bit-by-bit
while i2: # check shift register != 0
i1, i2 = i1^i2, (i1&i2) << 1 # update registers

How do you use a string to solve a math equation using python?

I'm trying to make a python program which takes in a user equation, for example: "168/24+8=11*3-16", and tries to make both sides of the equation equal to each other by removing any 2 characters from the user input. This is what I have so far:
def compute(side):
val = int(side[0])
x= val
y=0
z=None
for i in range(1, len(side)-1):
if side[i].isdigit():
x= (x*10)+ int(side[i])
if x == side[i].isdigit():
x= int(side[i])
else:
op = side[i]
if op=="+":
val += x
elif op == "-":
val -= x
elif op == "*":
val *= x
else:
val /= x
return print(val)
I have edited my compute function.
def evaluate(e):
side1 = ""
side2 = ""
equalsign = e.index("=")
side1= e[:equalsign - 1]
side2= e[:equalsign + 1]
if compute (side1) == compute(side2):
return True
else:
return False
def solve():
# use a for loop with in a for loop to compare all possible pairs
pass
def main():
e= input("Enter an equation: ")
evaluate(e)
main()
For the actual solve function I want to test all possible pairs for each side of the equation and with every pair removed check if the equation is equal to the other side. I was thinking of using a for loop that said:
for i in side1:
j= [:x]+[x+1:y]+[y+1:]
if compute(j)==compute(side2):
val= compute(j)
return val
How should I go about doing this? I'm getting a little confused on how to really approach this program.
Let's get to the preliminary issues.
e = raw_input("Enter an equation: ") # input is fine if you are using Python3.x
side1 = e[:equalsign] #note that a[start:end] does not include a[end]
side2 = e[equalsign + 1:] # not e[:equalsign + 1].
val = int(side[0]) # not val = side[0] which will make val a string
In the operations part, you are doing val += side # or -= / *= / /= .. remember side is a string
Edits:
Yeah, I'm still stuck up with Python 2.7 (use input if Python 3)
To solve for the value of each side, you could simply use eval(side1) # or eval(side2). There could be alternatives to using eval. (I am a novice myself). eval will also take care of PEMDAS.
Added edit to side1 expression.
Updated with code written so far.
def compute(side):
return eval(side)
def evaluate(e):
side1, side2 = e.split('=')
if compute(side1) == compute(side2):
return (True, e)
else:
return (False, 'Not Possible')
def solve(e):
for i in range(len(e)): # loop through user input
if e[i] in '=': # you dont want to remove the equal sign
continue
for j in range(i+1, len(e)): # loop from the next index, you dont want
if e[j] in '=': # to remove the same char
continue # you dont want to remove '=' or operators
new_exp = e[:i] + e[i+1:j] + e[j+1:] # e[i] and e[j] are the removed chars
#print e[i], e[j], new_exp # this is the new expression
s1, s2 = new_exp.split('=')
try:
if compute(s1) == compute(s2):
return (True, new_exp)
except:
continue
return (False, 'not possible')
def main():
e= raw_input("Enter an equation: ")
print evaluate(e.replace(' ', ''))
main()
This is what I have come up with so far (works for your example at least).
It assumes that operators are not to be removed
Final edit: Updated code taking into account #Chronical 's suggestions
Removed the try-except block in each loop and instead just use it after calculating each side
Here is code that does exactly what you want:
from itertools import combinations
def calc(term):
try:
return eval(term)
except SyntaxError:
return None
def check(e):
sides = e.split("=")
if len(sides) != 2:
return False
return calc(sides[0]) == calc(sides[1])
equation = "168/24+8 = 11*3-16".replace(" ", "")
for (a, b) in combinations(range(len(equation)), 2):
equ = equation[:a] + equation[a+1:b] + equation[b+1:]
if check(equ):
print equ
Core tricks:
use eval() for evaluation. If you use this for anything, please be aware of the security implications of this trick.
use itertools.combinations to create all possible pairs of characters to remove
Do not try to handle = too specially – just catch it in check()

Iterating and indexing

I am currently stuck with this program. I am attempting to determine the molecular weight of a compound given the molecular equation (only Cs, Hs, and Os). I also am unsure of how to correctly format [index +1], as I am trying to determine what the next character after "x" is to see if it is a number or another molecule
def main():
C1 = 0
H1 = 0
O1 = 0
num = 0
chemicalFormula = input("Enter the chemical formula, or enter key to quit: ")
while True:
cformula = list(chemicalFormula)
for index, x in enumerate(cformula):
if x == 'C':
if cformula[index + 1] == 'H' or cformula[index + 1] == 'O':
C1 += 1
else:
for index, y in range(index + 1, 1000000000):
if cformula[index + 1] != 'H' or cformula[index + 1] != 'O':
num = int(y)
num = num*10 + int(cformula[index + 1])
else:
C1 += num
break
this is the error I keep getting
Enter the chemical formula, or enter key to quit: C2
File "/Users/ykasznik/Documents/ykasznikp7.py", line 46, in main
for index, y in range(index + 1, 1000000000):
TypeError: 'int' object is not iterable
>>>
You should change this line
for index, y in range(index + 1, 1000000000):
to
for y in range(index + 1, 1000000000):
The answers provided here focus on two different aspects of solving your problem:
A very specific solution to your error (int is not iterable), by correcting some code.
A bit bigger perspective of how to handle your code.
Regarding 1, a comment to your question noted the issue: the syntax of tuple-unpacking in your inner loop.
An example of Tuple-unpacking would be
a,b = ['a','b']
Here, Python would take the first element of the right hand side (RHS) and assign it to the first name on the left hand side (LHS), the second element of RHS and assign it to the second name in the LHF.
Your inner loop that faults,
for index, y in range(index + 1, 1000000000),
is equivalent of trying to do
index, y = 1
Now, an integer is not a collection of elements, so this would not work.
Regarding 2, you should focus on the strategy of modularization, which basically means you write a function for each sub-problem. Python was almost born for this. (Note, this strategy does not necessarily mean writing Python-modules for each subproblem.)
In you case, your main goal can be divided into several sub-problems:
Getting the molecular sequences.
Split the sequences into individual sequences.
Splitting the sequence into its H, C, and O-elements.
Given the number of H, C and O-atoms, calculate the molecular weight.
Step 3 and 4 are excellent candidates for independent functions, as their core problem is isolated from the remaining context.
Here, I assume we only get 1 sequence at a time, and that they can be of the form:
CH4
CHHHH
CP4H3OH
Step 3:
def GetAtoms(sequence):
'''
Counts the number of C's, H's and O's in sequence and returns a dictionary.
Only works with a numeric suffices up to 9, e.g. C10H12 would not work.
'''
atoms = ['C','H','O'] # list of which atoms we want to count.
res = {atom:0 for atom in atoms}
last_c = None
for c in sequence:
if c in atoms:
res[c] += 1
last_c = c
elif c.isdigit() and last_c is not None:
res[last_c] += int(c) - 1
last_c = None
else:
last_c = None
return res
You can see, that regardless of how you obtain the sequence and how the molecular weight is calculated, this method works (under the preconditions). If you later need to extend the capabilities of how you obtain the atom-count, this can be altered without affecting the remaining logic.
Step 4:
def MolecularWeight(atoms):
return atoms['H']*1 + atoms['C']*8 + atoms['O']*18
Now your total logic could be this:
while True:
chemicalFormula = input("Enter the chemical formula, or enter key to quit: ")
if len(chemicalFormula) == 0:
break
print 'Molecular weight of', chemicalFormula, 'is', MolecularWeight(GetAtoms(chemicalFormula))
Here's my idea on how to solve the problem. Basically, you keep track of the current 'state' and iterate through each character exactly once, so you can't lose track of where you are or anything like that.
def getWeightFromChemical(chemical):
chemicals = {"C" : 6, "H" : 1, "O" : 8}
return chemicals.get(chemical, 0)
def chemicalWeight(chemicalFormula):
lastchemical = ""
currentnumber = ""
weight = 0
for c in chemicalFormula:
if str.isalpha(c): # prepare new chemical
if len(lastchemical) > 0:
weight += getWeightFromChemical(lastchemical)*int("1" if currentnumber == "" else currentnumber)
lastchemical = c
currentnumber = ""
elif str.isdigit(c): # build up number for previous chemical
currentnumber += c
# one last check
if len(lastchemical) > 0:
weight += getWeightFromChemical(lastchemical)*int("1" if currentnumber == "" else currentnumber)
return weight
By the way, can anyone see how to refactor this to not have that piece of code twice? It bugs me.
Change
for index, y in range(index + 1, 1000000000):
to
for index, y in enumerate(range(index + 1, 1000000000)):
Although you may consider renaming your outer loop or inner loop index for clarity
for index, x in enumerate(cformula):
if x == 'C':
if cformula[index + 1] == 'H' or cformula[index + 1] == 'O':
C1 += 1
else:
for index, y in range(index + 1, 1000000000):
This is a Really Bad Idea. You are overwriting the value of index from the outer loop with the value of index from the inner loop.
You should use a different name, say index2 for the inner loop.
Also, when you say for index, y in range(index + 1, 1000000000): you are acting as if you are expecting range() to produce a sequence of 2-tuples. But range always produces a sequence of ints.
Roger has suggested for y in range(index + 1, 1000000000): but I think you are intending to get the value of y from somewhere else (it's not clear where. Maybe you want to use the second argument of enumerate() to specify the value to start from, instead?
That is,
for index2, y in enumerate(whereeveryoumeanttogetyfrom, index + 1)
so that index2 equals index +1 on the first step through the loop, index +2 on the second, etc.
Range returns either a list of int, or an iterable of int, depending on which version of Python you are using. Attempting to assign that single int into two names causes Python to attempt to iterate through that int in automated tuple unpacking.
So, change the
for index, y in range(index + 1, y):
to
for y in range(index + 1, y):
Also, you use index + 1 repeatedly, but mostly to look up the next symbol in your cformula. Since that doesn't change over the course of your outer loop, just assign it its own name once, and keep using that name:
for index, x in enumerate(cformula):
next_index = index + 1
next_symbol = cformula[next_index]
if x == 'C':
if next_symbol == 'H' or next_symbol == 'O':
C1 += 1
else:
for y in range(next_index, 1000000000):
if next_symbol != 'H' or next_symbol != 'O':
num = y*10 + int(next_symbol)
else:
C1 += num
break
I've also refactored out some constants to make the code cleaner. Your inner loop as written was failing on tuple assignment, and would only be counting up the y. Also, your index would be reset again once you exited the inner loop, so you would be processing all of your digits repeatedly.
If you want to iterate over the substring after your current symbol, you could just use slice notation to get all of those characters: for subsequent in cformula[next_index:]
For example:
>>> chemical = 'CH3OOCH3'
>>> chemical[2:]
'3OOCH3'
>>> for x in chemical[2:]:
... print x
...
3
O
O
C
H
3

Categories

Resources