To clarify before beginning: I'm aware there are similar topics, but nothing that has really offered any direct help. Also: this is a class project; so I'm not looking for anyone to code my project for me. Tips and advice is what I'm looking for. (I would go to my professor for this kind of thing, but he can't be bothered to check his e-mail.)
This program is meant to take a user supplied seed, generate a key based on the integer, then to generate a 95 x 95 matrix in which all printable ascii characters are available for encryption/decryption purposes. (Key is all alpha, and capitalized)
The kicker: all the cells must be randomized. See image below:
Randomized Vigenere Matrix
I will post my code below (Python is certainly not my forte, though I will definitely take constructive criticisms.):
import random
class Vigenere(object):
def __init__(self, seed):
random.seed(seed)
#string containing all valid characters
self.symbols= """!"#$%&'()*+,-./0123456789:;?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] ^_`abcdefghijklmnopqrstuvwxyz{|}~"""
self.eTable = [[0 for i in range(len(self.symbols))] for i in range(len(self.symbols))]
self.dTable = [[0 for i in range(len(self.symbols))] for i in range(len(self.symbols))]
self.keyWord = ""
self.message = ""
self.ciphertext = ""
self.keywordFromSeed(seed)
def setMessage(self,message):
self.message = message
#Generate psuedorandom keyword from seed
def keywordFromSeed(self,seed):
Letters = []
while seed > 0:
Letters.insert(0,chr((seed % 100) % 26 + 65))
seed = seed // 100
self.keyWord = "".join(Letters)
self.buildVigenere()
#Contructs a 95 x 95 matrix filled randomly
def buildVigenere(self):
n = len(self.symbols)
#Build the vigenere matrix
for i in range(n):
temp = self.symbols
for j in range(n):
r = random.randrange(len(temp))
self.eTable[i][j] = temp[r]
#This line below does not fill entire matrix. Why?
self.dTable[j][(ord(temp[r])-32)] = chr(i+32)
temp = temp.replace(temp[r],'')
def encrypt(self):
for i in range(len(self.message)):
mi = i
ki = i % len(self.keyWord)
self.ciphertext = self.ciphertext + self.eRetrieve(ki,mi)
def decrypt(self):
for i in range(len(self.message)):
emi = i
ki = i % len(self.keyWord)
char = self.dRetrieve(ki,emi)
self.ciphertext = self.ciphertext + char
print(self.ciphertext)
def eRetrieve(self,ki,mi):
row = ord(self.message[mi]) - 32
col = ord(self.keyWord[ki]) - 32
print(row, col)
return self.eTable[row][col]
def dRetrieve(self,ki,emi):
n = len(self.symbols)
whichRow = ord(self.keyWord[ki]) - 32
whichCol = ord(self.message[emi]) - 32
return(self.dTable[whichRow][whichCol])
And just in case it helps, here's my main.py:
import argparse
import randomized_vigenere as rv
def main():
#Parse parameters
parser = argparse.ArgumentParser()
parser.add_argument("-m", "--mode", dest="mode", default = "encrypt", help="Encrypt or Decrypt")
parser.add_argument("-i", "--inputfile", dest="inputFile", default = "inputFile.txt", help="Input Name")
parser.add_argument("-o", "--outputfile", dest="outputFile", default = "outputFile.txt", help="Output Name")
parser.add_argument("-s", "--seed", dest="seed", default =7487383487438734, help="Integer seed")
args = parser.parse_args()
#Set seed and generate keyword
seed = args.seed
#Construct Matrix
f = open(args.inputFile,'r')
message = f.read()
Matrix = rv.Vigenere(seed)
Matrix.setMessage(message)
if(args.mode == 'encrypt'):
Matrix.encrypt()
Matrix.setMessage(Matrix.ciphertext)
Matrix.decrypt()
else:
Matrix.decrypt()
o = open(args.outputFile,'w')
o.write(str(Matrix.ciphertext))
if __name__ == '__main__':
main()
I've just been using the default seed: 7487383487438734
My Plaintext: ABCdefXYZ
I am going to answer this question:
#This line below does not fill entire matrix. Why?
I think it is your current question. If I am not mistaken, this should be the first line of your question, not a simple comment in function:
def buildVigenere(self):
n = len(self.symbols)
#Build the vigenere matrix
for i in range(n):
temp = self.symbols
for j in range(n):
r = random.randrange(len(temp))
self.eTable[i][j] = temp[r]
#This line below does not fill entire matrix. Why?
self.dTable[j][(ord(temp[r])-32)] = chr(i+32)
temp = temp.replace(temp[r],'')
First thing I did was to build a small self-contained example like this:
import random
def buildVigenere():
symbols= """!"#$%&'()*+,-./0123456789:;?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] ^_`abcdefghijklmnopqrstuvwxyz{|}~"""
n = len(symbols)
eTable = [[0 for i in range(len(symbols))] for i in range(len(symbols))]
dTable = [[0 for i in range(len(symbols))] for i in range(len(symbols))]
#Build the vigenere matrix
for i in range(n):
temp = symbols
for j in range(n):
r = random.randrange(len(temp))
eTable[i][j] = temp[r]
print (r, len(temp), j, len(symbols), temp[r])
#This line below does not fill entire matrix. Why?
print (ord(temp[r])-32)
dTable[j][(ord(temp[r])-32)] = chr(i+32)
temp = temp.replace(temp[r],'')
print dTable
buildVigenere()
You really should learn to do that if you want answers here, and more generally be a successful programmer. Finding where the problem is and being able to reproduce it in a simple case is often the key.
Here I get an error:
Exception "unhandled IndexError"
list assignment index out of range
I add some print statements(see above) and I found that the error come from the fact that < > = are missing in the symbols string.
Why don't you use chr to build your string symbols?
And for buildVigenere, you can make a much simpler version by using
random.shuffle(x)
I was able to get it working. I'll post my implementation below:
main.py
import argparse
import randomized_vigenere as rv
def main():
#Parse parameters
parser = argparse.ArgumentParser()
parser.add_argument("-m", "--mode", dest="mode", default = "encrypt", help="Encrypt or Decrypt")
parser.add_argument("-i", "--inputfile", dest="inputFile", default = "inputFile.txt", help="Input Name")
parser.add_argument("-o", "--outputfile", dest="outputFile", default = "outputFile.txt", help="Output Name")
parser.add_argument("-s", "--seed", dest="seed", default =7487383487438734, help="Integer seed")
args = parser.parse_args()
#Set seed and generate keyword
seed = args.seed
#Construct Matrix
f = open(args.inputFile,'r')
message = f.read()
Matrix = rv.Vigenere(seed)
Matrix.setMessage(message)
if(args.mode == 'encrypt'):
Matrix.encrypt()
else:
Matrix.decrypt()
o = open(args.outputFile,'w')
o.write(str(Matrix.ciphertext))
print("Seed used:",Matrix.seed)
print("Key Generated:",Matrix.keyWord)
print("Original Message:",Matrix.message)
print("Decoded Message:",Matrix.ciphertext)
if __name__ == '__main__':
main()
randomized_vigenere.py
(Be sure to add '<', '=', and '>' to the symbol list. For some reason they keep getting removed from this post.)
import random
class Vigenere(object):
#Initialize Vigenere object.
#Sets the random seed based on integer passed to the object
#Establishes all valid symbols
#Generates empty matrix which will contain values for encryption/decryption
#Generates a keyword based on the integer passed to the object
def __init__(self, seed):
random.seed(seed)
self.seed = seed
self.symbols= """ !"#$%&'()*+,-./0123456789:;?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"""
self.Table = [[0 for i in range(len(self.symbols))] for i in range(len(self.symbols))]
self.keyWord = ""
self.message = ""
self.ciphertext = ""
self.keywordFromSeed(seed)
#Sets the plaintext that will be encrypted/decrypted
def setMessage(self,message):
self.message = message
#Generate psuedorandom keyword from seed
def keywordFromSeed(self,seed):
Letters = []
while seed > 0:
Letters.insert(0,chr((seed % 100) % 26 + 65))
seed = seed // 100
self.keyWord = "".join(Letters)
self.buildVigenere()
#Contructs a 95 x 95 matrix filled randomly with no repeats within same line
def buildVigenere(self):
random.seed(self.seed)
temp = list(self.symbols)
random.shuffle(temp)
temp = ''.join(temp)
for sym in temp:
random.seed(self.seed)
myList = []
for i in range(len(temp)):
r = random.randrange(len(temp))
if r not in myList:
myList.append(r)
else:
while(r in myList):
r = random.randrange(len(temp))
myList.append(r)
while(self.Table[i][r] != 0):
r = (r + 1) % len(temp)
self.Table[i][r] = sym
#Encryption function that iterates through both the message and the keyword
#and grabs values from Table based on the ordinal value of the current
#character being pointed to be the iterator
def encrypt(self):
for i in range(len(self.message)):
mi = i
ki = i % len(self.keyWord)
self.ciphertext = self.ciphertext + self.eRetrieve(ki,mi)
def eRetrieve(self,ki,mi):
row = ord(self.message[mi]) - 32
col = ord(self.keyWord[ki]) - 32
return self.Table[row][col]
#Decryption function that iterates through both the message and the keyword
#and grabs values from Table based on the ordinal value of the current
#keyWord character being pointed to be the iterator, then traversing the
#row that corresponds to that value. While traversing that row, once there
#is a match of the message value being searched for, take the iterator value
#and convert it to an ascii character. This is the decrypted character
def decrypt(self):
self.ciphertext = ""
for i in range(len(self.message)):
emi = i
ki = i % len(self.keyWord)
self.ciphertext = self.ciphertext + self.dRetrieve(ki,emi)
def dRetrieve(self,ki,emi):
n = len(self.symbols)
whichRow = ord(self.keyWord[ki]) - 32
for i in range(n):
if self.Table[i][whichRow] == self.message[emi]:
decryptChar = chr(i + 32)
return(decryptChar)
Thank you to #Eric-Levieil for assistance
Related
I just got into python a few weeks ago and I've been learning the language using the PyCharm software.
I was hoping to use the following code for a project:
https://github.com/eohomegrownapps/markov-music
I installed the required library, and inserted a midi file in the mentioned folder (the input folder). I'm using the following midi file:
http://www.bachcentral.com/invent/invent1.mid
Once I run the code, nothing appears in the 'compositions' folder. Can anyone tell me what I'm doing wrong? I'll post a screenshot of the code below.
Thanks a million in advance
Updated Screenshot
import random
import midi
import datetime
class Markov(object):
"""docstring for Markov"""
def __init__(self, order=2):
super(Markov, self).__init__()
self.order = order
self.chain = {}
def add(self, key, value):
if self.chain.has_key(key):
self.chain[key].append(value)
else:
self.chain[key] = [value]
def load(self, midifile):
pattern = midi.read_midifile(midifile)
# track = pattern[1]
for track in pattern:
noteslist = []
curoffset = 0
for i in track:
if i.name == "Note On" and i.data[1] != 0:
note = (i.data[0], i.data[1], i.tick + curoffset)
# note = (i.data[0],i.data[1],i.tick)
noteslist.append(note)
curoffset = 0
else:
curoffset += i.tick
if len(noteslist) > self.order:
for j in range(self.order, len(noteslist)):
t = tuple(noteslist[j - self.order:j])
print t
print noteslist[j]
self.add(t, noteslist[j])
else:
print "Corpus too short"
def generate(self, length, filename):
pattern = midi.Pattern()
# Instantiate a MIDI Track (contains a list of MIDI events)
track = midi.Track()
# Append the track to the pattern
pattern.append(track)
tick = 0
currenttuple = random.choice(self.chain.keys())
prevnote = False
for i in range(0, self.order):
if prevnote != False:
on = midi.NoteOnEvent(tick=tick, velocity=0, pitch=prevnote)
track.append(on)
on = midi.NoteOnEvent(tick=0, velocity=currenttuple[i][1], pitch=currenttuple[i][0])
track.append(on)
tick = currenttuple[i][2]
prevnote = currenttuple[i][0]
result = random.choice(self.chain[currenttuple])
# print currenttuple
for i in range(1, length):
for j in range(0, self.order):
if prevnote != False:
if tick > 5000:
tick = 5000
on = midi.NoteOnEvent(tick=tick, velocity=0, pitch=prevnote)
track.append(on)
on = midi.NoteOnEvent(tick=0, velocity=currenttuple[j][1], pitch=currenttuple[j][0])
track.append(on)
tick = currenttuple[j][2]
prevnote = currenttuple[j][0]
currenttuple = list(currenttuple)
currenttuple.pop(0)
currenttuple.append(result)
currenttuple = tuple(currenttuple)
if self.chain.has_key(currenttuple):
result = random.choice(self.chain[currenttuple])
else:
result = random.choice(self.chain[random.choice(self.chain.keys())])
# Add the end of track event, append it to the track
eot = midi.EndOfTrackEvent(tick=1)
track.append(eot)
# Print out the pattern
print pattern
# Save the pattern to disk
midi.write_midifile(filename + ".mid", pattern)
if __name__ == '__main__':
directory = "compositions"
musicdir = "input"
musicdir += "/"
logname = directory + "/" + "{:%Y-%m-%d-%H:%M:%S}_genmusic".format(datetime.datetime.now())
m = Markov(3)
print "Loading music"
inp = raw_input('Name of midi file to load or g to generate: ')
while inp != "g":
try:
m.load(musicdir + inp)
except Exception as e:
print "File not found or corrupt"
inp = raw_input('Name of midi file to load or g to generate: ')
print "Done"
print m.chain
m.generate(1000, logname)
When I run my .py file, it quickly shows a blank black screen then disappears and does nothing. The script works fine in the editor though! The full code is down below, I know it could use some improvement but I'm just looking for answers to the blank screen for now. :) (The script is a simple genetic algorithm btw)
#!/usr/bin/python3
from fuzzywuzzy import fuzz
import random
import string
def error_msg():
print('\nSomethine went wrong!\nMake sure you typed the information correctly!')
mainF()
def mainF():
try:
while 7 == 7:
print()
stri = input('Enter string here: ')
gene = input('Enter number of generations here: ')
agen = input('Enter number of agents here: ')
muta = input('Enter chance of mutation here (0 - 1): ')
thre = input('Enter threshold here: ')
if stri == '' or gene == '' or agen == '' or thre == '' or muta == '' or stri.isdigit() or gene.isalpha() or agen.isalpha() or thre.isalpha() or muta.isalpha():
print('\nSomethine went wrong!\nMake sure you typed the information correctly!')
else:
ga(stri, len(stri), agen, gene, thre, muta)
except:
error_msg()
class Agent:
def __init__(self, length):
self.string = ''.join(random.choice(string.ascii_letters) for _ in range(length))
self.fitness = -1
def __str__(self):
return 'String: ' + str(self.string) + ', Fitness: ' + str(self.fitness) + '.'
def init_agents(population_p, length):
try:
population = int(population_p)
return [Agent(length) for _ in range(population)]
except:
error_msg()
def fitness(agents, in_str_p):
try:
in_str = in_str_p
for agent in agents:
agent.fitness = fuzz.ratio(agent.string, in_str)
return agents
except:
error_msg()
def selection(agents):
try:
agents = sorted(agents, key=lambda agent: agent.fitness, reverse=True)
print('\n'.join(map(str, agents)))
agents = agents[:int(0.2 * len(agents))]
return agents
except:
error_msg()
def crossover(agents, in_str_len_p, population_p):
try:
population = int(population_p)
in_str_len = in_str_len_p
offspring = []
for _ in range((population - len(agents)) // 2):
parent1 = random.choice(agents)
parent2 = random.choice(agents)
child1 = Agent(in_str_len)
child2 = Agent(in_str_len)
split = random.randint(0, in_str_len)
child1.string = parent1.string[0:split] + parent2.string[split:in_str_len]
child2.string = parent2.string[0:split] + parent1.string[split:in_str_len]
offspring.append(child1)
offspring.append(child2)
agents.extend(offspring)
return agents
except:
error_msg()
def mutation(agents, in_str_len_p, mutation_chance_p):
try:
mutation_chance = float(mutation_chance_p)
in_str_len = in_str_len_p
for agent in agents:
for idx, param in enumerate(agent.string):
if random.uniform(0.0, 1.0) <= mutation_chance:
agent.string = agent.string[0:idx] + random.choice(string.ascii_letters) + agent.string[idx+1:in_str_len]
return agents
except:
error_msg()
def ga(in_str_p, in_str_len_p, population_p, generations_p, threshold_p, mutation_chance_p):
mutation_chance = mutation_chance_p
threshold = int(threshold_p)
population = population_p
generations = int(generations_p)
in_str = in_str_p
in_str_len = in_str_len_p
agents = init_agents(population, in_str_len)
for generation in range(generations):
print('Generation: ' + str(generation))
agents = fitness(agents, in_str)
agents = selection(agents)
agents = crossover(agents, in_str_len, population)
agents = mutation(agents, in_str_len, mutation_chance)
if any(agent.fitness >= threshold for agent in agents):
print('Threshold met!')
mainF()
if __name__ == '__main__':
mainF()
I am trying to understand how to implement a genetic algorithm and wrote a simple string guess. I am having trouble understanding why this solution is not working.
I believe that my problem is in my populating my new generations? The newest generations do not seem to have improved fitness values. I am also not sure if I am doing the crossover and mutation rates correctly. Any help would be really appreciated!
POP_SIZE = 300;
CROSSOVER_RATE = 0.7;
MUTATION_RATE = 0.01
GENESET = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"
target = "Hello World"
RAND_NUM = random.random()
def generateBasePopulation(population_size):
population = dict()
for _ in range(POP_SIZE):
gene = generateParent(len(target))
population[gene] = 0
return population
def generateNewPopulation(population, population_size):
newPopulation = dict()
while(len(newPopulation) <= POP_SIZE):
child_one, child_two = crossover(child_one, child_two)
child_one = mutate(child_one)
child_two = mutate(child_two)
newPopulation[child] = 0
newPopulation[child_two] = 0
return newPopulation
def assignFitness(population):
for x in population:
population[x] = getFitness(x)
def generateParent(length):
genes = list("")
for i in range(0,length):
random_gene = random.choice(GENESET)
genes.append(random_gene)
return(''.join(genes))
def getFitness(candidate):
fitness = 0
for i in range(0, len(candidate) - 1):
if target[i] == candidate[i]:
fitness += 1
return(fitness)
def mutate(parent):
gene_index_to_mutate = random.randint(0, len(parent) - 1)
mutation_value = random.choice(GENESET)
genes = list(parent)
genes[gene_index_to_mutate] = mutation_value
return(''.join(genes))
def crossover(parentA, parentB):
if(RAND_NUM < CROSSOVER_RATE):
random_index = random.randint(0, len(target))
parentASlice = parentA[:random_index]
parentBSlice = parentB[random_index:]
return (parentASlice + parentBSlice), (parentBSlice + parentASlice)
return parentA, parentB
def chooseChild(population):
fitnessSum = sum(population.values())
pick = random.uniform(0, fitnessSum)
current = 0
for pop in population:
current += population[pop]
if current >= pick:
return pop
def main():
population = generateBasePopulation(POP_SIZE)
targetNotFound = True
while(targetNotFound):
assignFitness(population)
if target in population:
print("target found!")
targetNotFound = False
if(targetNotFound):
tempPopulation = generateNewPopulation(population, POP_SIZE)
population.clear()
population = tempPopulation
There are some issues with the generateNewPopulation function.
child_one and child_two are referenced before assignment
You need two individuals from the population to perform the crossover. There are several selection algorithms, but just to give an idea you could start with a form of tournament selection:
def extractFromPopulation(population):
best = random.choice(list(population.keys()))
for _ in range(4):
gene = random.choice(list(population.keys()))
if population[gene] > population[best]:
best = gene
return best
Here the selection pressure (range(4)) is fixed. It's one of the parameters you've to tune in a real case.
Now we have:
def generateNewPopulation(population, population_size):
newPopulation = dict()
while len(newPopulation) <= POP_SIZE:
child_one = extractFromPopulation(population)
child_two = extractFromPopulation(population)
# ...
The code still doesn't work because
new individuals aren't inserted in newPopulation
Just indent the two lines:
newPopulation[child] = 0
newPopulation[child_two] = 0
(they must be part of the while loop)
The revised generateNewPopulation function follows:
def generateNewPopulation(population, population_size):
newPopulation = dict()
while len(newPopulation) <= POP_SIZE:
child_one = extractFromPopulation(population)
child_two = extractFromPopulation(population)
child_one, child_two = crossover(child_one, child_two)
child_one = mutate(child_one)
child_two = mutate(child_two)
newPopulation[child_one] = 0
newPopulation[child_two] = 0
return newPopulation
The crossover function cannot be based on a fixed RAND_NUM value
Delete the RAND_NUM = random.random() assignment and change the crossover function to use a new random value at each call:
def crossover(parentA, parentB):
if random.random() < CROSSOVER_RATE:
random_index = random.randint(0, len(target))
parentASlice = parentA[:random_index]
parentBSlice = parentB[random_index:]
return (parentASlice + parentBSlice), (parentBSlice + parentASlice)
return parentA, parentB
Also the code doesn't correctly perform single point crossover since schemata of the second parent aren't preserved.
You could change many details to improve performance but, as a starting example, it's probably enough as it is (...it works).
Average number of generations to find a solution is about 158 (average on 200 runs).
EDIT (thanks to alexis for the comment)
MUTATION_RATE is unused and a mutation always happens. The mutate function should be something like:
def mutate(parent):
if random.random() < MUTATION_RATE:
gene_index_to_mutate = random.randint(0, len(parent) - 1)
mutation_value = random.choice(GENESET)
genes = list(parent)
genes[gene_index_to_mutate] = mutation_value
return ''.join(genes)
return parent
This fix is particularly important if you keep the roulette wheel selection algorithm (chooseChild often doesn't converge without the fix).
I've been playing around with sympy and decided to make an arbitrary equations solver since my finance class was getting a little dreary. I wrote a basic framework and started playing with some examples, but some work and some don't for some reason.
from sympy import *
import sympy.mpmath as const
OUT_OF_BOUNDS = "Integer out of bounds."
INVALID_INTEGER = "Invalid Integer."
INVALID_FLOAT = "Invalid Float."
CANT_SOLVE_VARIABLES = "Unable to Solve for More than One Variable."
CANT_SOLVE_DONE = "Already Solved. Nothing to do."
# time value of money equation: FV = PV(1 + i)**n
# FV = future value
# PV = present value
# i = growth rate per perioid
# n = number of periods
FV, PV, i, n = symbols('FV PV i n')
time_value_money_discrete = Eq(FV, PV*(1+i)**n)
time_value_money_continuous = Eq(FV, PV*const.e**(i*n))
def get_sym_num(prompt, fail_prompt):
while(True):
try:
s = input(prompt)
if s == "":
return None
f = sympify(s)
return f
except:
print(fail_prompt)
continue
equations_supported = [['Time Value of Money (discrete)', [FV, PV, i, n], time_value_money_discrete],
['Time Value of Money (continuous)',[FV, PV, i, n], time_value_money_continuous]]
EQUATION_NAME = 0
EQUATION_PARAMS = 1
EQUATION_EXPR = 2
if __name__ == "__main__":
while(True):
print()
for i, v in enumerate(equations_supported):
print("{}: {}".format(i, v[EQUATION_NAME]))
try:
process = input("What equation do you want to solve? ")
if process == "" or process == "exit":
break
process = int(process)
except:
print(INVALID_INTEGER)
continue
if process < 0 or process >= len(equations_supported):
print(OUT_OF_BOUNDS)
continue
params = [None]*len(equations_supported[process][EQUATION_PARAMS])
for i, p in enumerate(equations_supported[process][EQUATION_PARAMS]):
params[i] = get_sym_num("What is {}? ".format(p), INVALID_FLOAT)
if params.count(None) > 1:
print(CANT_SOLVE_VARIABLES)
continue
if params.count(None) == 0:
print(CANT_SOLVE_DONE)
continue
curr_expr = equations_supported[process][EQUATION_EXPR]
for i, p in enumerate(params):
if p != None:
curr_expr = curr_expr.subs(equations_supported[process][EQUATION_PARAMS][i], params[i])
print(solve(curr_expr, equations_supported[process][EQUATION_PARAMS][params.index(None)]))
This is the code I have so far. I guess I can strip it down to a basic example if need be, but I was also wondering if there was a better way to implement this sort of system. After I have this down, I want to be able to add arbitrary equations and solve them after inputting all but one parameter.
For example, if I put in (for equation 0), FV = 1000, PV = 500, i = .02, n is empty I get 35.0027887811465 which is the correct answer. If I redo it and change FV to 4000, it returns an empty list as the answer.
Another example, when I input an FV, PV, and an n, the program seems to hang. When I input small numbers, I got RootOf() answers instead of a simple decimal.
Can anyone help me?
Side note: I'm using SymPy 0.7.6 and Python 3.5.1 which I'm pretty sure are the latest
This is a floating point accuracy issue. solve by default plugs solutions into the original equation and evaluates them (using floating point arithmetic) in order to sort out false solutions. You can disable this by setting check=False. For example, for Hugh Bothwell's code
for fv in range(1870, 1875, 1):
sols = sp.solve(eq.subs({FV:fv}), check=False)
print("{}: {}".format(fv, sols))
which gives
1870: [66.6116466112007]
1871: [66.6386438584579]
1872: [66.6656266802551]
1873: [66.6925950919998]
1874: [66.7195491090752]
I don't have an answer, but I do have a much simpler demonstration case ;-)
import sympy as sp
FV, n = sp.symbols("FV n")
eq = sp.Eq(FV, sp.S("500 * 1.02 ** n"))
# see where it breaks
for fv in range(1870, 1875, 1):
sols = sp.solve(eq.subs({FV:fv}))
print("{}: {}".format(fv, sols))
which produces
1870: [66.6116466112007]
1871: [66.6386438584579]
1872: []
1873: []
1874: []
At a guess this is where the accuracy breaks down enough that it can't find a verifiable solution for n?
Also, while poking at this I did a fairly extensive rewrite which you may find useful. It does pretty much the same as your code but in a much more loosely-coupled fashion.
import sympy as sp
class Equation:
def __init__(self, label, equality_str, eq="=="):
self.label = label
# parse the equality
lhs, rhs = equality_str.split(eq)
self.equality = sp.Eq(sp.sympify(lhs), sp.sympify(rhs))
# index free variables by name
self.vars = {var.name: var for var in self.equality.free_symbols}
def prompt_for_values(self):
# show variables to be entered
var_names = sorted(self.vars, key=str.lower)
print("\nFree variables are: " + ", ".join(var_names))
print("Enter a value for all but one (press Enter to skip):")
# prompt for values by name
var_values = {}
for name in var_names:
value = input("Value of {}: ".format(name)).strip()
if value:
var_values[name] = sp.sympify(value)
# convert names to Sympy variable references
return {self.vars[name]:value for name,value in var_values.items()}
def solve(self):
values = self.prompt_for_values()
solutions = sp.solve(self.equality.subs(values))
# remove complex answers
solutions = [sol.evalf() for sol in solutions if sol.is_real]
return solutions
def __str__(self):
return str(self.equality)
# Define some equations!
equations = [
Equation("Time value of money (discrete)", "FV == PV * (1 + i) ** n"),
Equation("Time value of money (continuous)", "FV == PV * exp(i * n)" )
]
# Create menu
menu_lo = 1
menu_hi = len(equations) + 1
menu_prompt = "\n".join(
[""]
+ ["{}: {}".format(i, eq.label) for i, eq in enumerate(equations, 1)]
+ ["{}: Exit".format(menu_hi)]
+ ["? "]
)
def get_int(prompt, lo=None, hi=None):
while True:
try:
value = int(input(prompt))
if (lo is None or lo <= value) and (hi is None or value <= hi):
return value
except ValueError:
pass
def main():
while True:
choice = get_int(menu_prompt, menu_lo, menu_hi)
if choice == menu_hi:
print("Goodbye!")
break
else:
solutions = equations[choice - 1].solve()
num = len(solutions)
if num == 0:
print("No solutions found")
elif num == 1:
print("1 solution found: " + str(solutions[0]))
else:
print("{} solutions found:".format(num))
for sol in solutions:
print(sol)
if __name__ == "__main__":
main()
So I have my hangman almost working however, it is only placing the first correct guess on the first line, and not continuing if I guess the second letter correctly. Also, no matter what letter I guess, it always draws the head on the third letter guessed. I have it narrowed down to my getGuessLocationInPuzzle function, where my index is always printing out zero even if I have guessed my second letter. I've been staring at this code for 2 and 1/2 hours, and can't solve it. Please help!
''' Import Statements '''
from graphics import *
import copy
import random
import string
def drawGallows(window):
gallows = Rectangle(Point(300,280),Point(200,300))
gallows.draw(window)
gallows = Line(Point(250,300),Point(250,100))
gallows.draw(window)
gallows = Line(Point(250,100),Point(120,100))
gallows.draw(window)
gallows = Line(Point(120,100),Point(120,120))
gallows.draw(window)
def getPuzzle():
fh = open("puzzle.txt","r")
fileList = fh.read()
files = string.split(fileList)
puzzle = random.choice(files)
return puzzle
def drawPuzzle(length, window):
lines = Line(Point(15,275),Point(25,275))
for index in range(length):
newline = copy.copy(lines)
newline.move(20,0)
newline.draw(window)
def getGuess(entry, window):
letter = entry.getText()
return letter
def getValidGuess(Validguesses, entry, window):
guess = getGuess(entry, window)
for index in Validguesses:
if guess == index:
return guess
def createTextOverlay(guess):
textstring = ""
num = ord(guess)
for index in range(num - ord("a")):
textstring = textstring + " "
textstring= textstring + guess
for index in range(25-(ord(guess)-ord("a"))):
textstring = textstring+" "
overlay = Text(Point(150, 375), textstring)
overlay.setFace("courier")
overlay.setFill("red")
return overlay
def updateGallows(numWrongGuesses, window):
if numWrongGuesses==1:
head = Circle(Point(120,140),20)
head.draw(window)
if numWrongGuesses==2:
body = Line(Point(120,220),Point(120,160))
body.draw(window)
if numWrongGuesses==3:
arms1 = Line(Point(100,200),Point(120,160))
arms1.draw(window)
if numWrongGuesses==4:
arms2 = Line(Point(120,160),Point(140,200))
arms2.draw(window)
if numWrongGuesses==5:
leg1 = Line(Point(120,220),Point(100,250))
leg1.draw(window)
if numWrongGuesses==6:
leg2 = Line(Poin(120,220),Point(140,250))
leg2.draw(window)
def updatePuzzleDisplay(locations, letter, window):
# For each value in the locations list, print out the letter above the
# appropriate blank space.
# For each value in locations list, create a text object.
locationonspaces = 35
for index in locations:
locationonspaces = locationonspaces + 5
letterthatisguessed = Text(Point(locationonspaces, 265 ), letter)
letterthatisguessed.draw(window)
# Create text object
def getGuessLocationInPuzzle(puzzle, guess):
word = len(puzzle)
locations = []
for index in range(word):
if puzzle[index]==guess:
locations.append(index)
print guess
print index
return locations
def main():
window = GraphWin("Hangman Final", 400, 400)
drawGallows(window)
lettersToGuess = Text(Point(150, 375), "abcdefghijklmnopqrstuvwxyz")
lettersToGuess.setFace("courier")
lettersToGuess.draw(window)
guessInput = Entry(Point(250, 325), 10)
guessInput.draw(window)
puzzle = getPuzzle()
print puzzle
length = len(puzzle)
drawPuzzle(length,window)
guess = getGuess(guessInput,window)
window.getMouse()
Validguesses = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
numWrongGuesses=0
while True:
guess = getValidGuess (Validguesses,guessInput,window)
#print guess
window.getMouse
locations = getGuessLocationInPuzzle(puzzle,guess)
# print locations
if locations == []:
overlay = createTextOverlay(guess)
overlay.draw(window)
updateGallows(numWrongGuesses,window)
numWrongGuesses = numWrongGuesses + 1
else:
updatePuzzleDisplay(locations, guess, window)
overlay = createTextOverlay(guess)
overlay.draw(window)
if guess !=0:
Validguesses.remove(guess)
window.getMouse()
window.getMouse()
main()