Different result after using multiprocessing - python

guys:
I am new to the use of python multiprocessing. Recently my research needs calculation with many iterations. So I tried to use multiprocessing to speed it up. But when I wrote a small sample code, I found that the curve I got with multiprocessing is different from that without multiprocessing.
The code with multiprocessing:
import random
import matplotlib.pyplot as plt
import math
import numpy as np
import multiprocessing as mp
class Classic:
def __init__(self,position,type):
assert type == 'A' or type == 'B'
self.position = position
self.type = type
def getposition(self):
return self.position
def gettype (self):
return self.type
def setposition(self,pos):
self.position = pos
def settype (self,t):
self.type = t
def number_count(system):
counter = 0
for i in range(0,len(system)):
if system[i] !=0:
counter=counter+1
return counter
def time_evolution(system_temp,steps):
numberlist=np.zeros(steps)
number = number_count(system_temp)
for t in range(0,steps):
for i in range(0,len(system_temp)):
x = random.randint(0, len(system_temp)-2)
if system_temp[x]!=0 and system_temp[x+1]!=0:
p1 = system_temp[x]
p2 = system_temp[x+1]
p1_type = p1.gettype()
p2_type = p2.gettype()
exchange_check = random.randint(0,1)
if p1_type == p2_type:
system_temp[x]=0
system_temp[x+1]=0
number = number-2
elif exchange_check == 1:
type_temp = p1_type
p1.settype(p2_type)
p2.settype(type_temp)
elif system_temp[x]!=0 and system_temp[x+1]==0:
system_temp[x+1] = system_temp[x]
system_temp[x] =0
elif system_temp[x]==0 and system_temp[x+1]!=0:
system_temp[x]=system_temp[x+1]
system_temp[x+1]=0
numberlist[t]=numberlist[t]+number
return numberlist
if __name__ =='__main__':
pool = mp.Pool(8)
size = 10000
system_init = [0]*size
particle_num = 3000
repeat = 20
steps = 2000
res=[]
totalnum= np.zeros(steps)
randomlist = random.sample(range(1,100*repeat),repeat)
for i in range(0,particle_num):
pos = random.randint(0,size-1)
ran_num = random.randint (0,1)
if ran_num == 0:
temp_type = 'A'
else:
temp_type = 'B'
if system_init[pos] ==0:
system_init[pos] = Classic(pos,temp_type)
for k in range(0, repeat):
system_temp = system_init[:]
random.seed(randomlist[k])
res.append(pool.apply_async(time_evolution, args=(system_temp,steps,)))
pool.close()
pool.join()
for count in range(0,len(res)):
totalnum =totalnum+ np.array(res[count].get())
time=np.linspace(1,steps+1,steps)
time_sqrt=np.sqrt(8.0*math.pi*time)
density =totalnum/(repeat*size)
density_mod = np.multiply(time_sqrt,density)
#plt.loglog(time,density_mod)
#plt.savefig("modified_density_loglog.pdf")
#plt.close()
myfile=open('density_mod2.txt','w')
for element in density_mod:
myfile.write(str(element))
myfile.write('\n')
myfile.close()
And the code without multiprocessing is
import random
import matplotlib.pyplot as plt
import math
import numpy as np
class Classic:
def __init__(self,position,type):
assert type == 'A' or type == 'B'
self.position = position
self.type = type
def getposition(self):
return self.position
def gettype (self):
return self.type
def setposition(self,pos):
self.position = pos
def settype (self,t):
self.type = t
def number_count(system):
counter = 0
for i in range(0,len(system)):
if system[i] !=0:
counter=counter+1
return counter
def time_evolution(system_temp,steps):
numberlist=np.zeros(steps)
number = number_count(system_temp)
for t in range(0,steps):
for i in range(0,len(system_temp)):
x = random.randint(0, len(system_temp)-2)
if system_temp[x]!=0 and system_temp[x+1]!=0:
p1 = system_temp[x]
p2 = system_temp[x+1]
p1_type = p1.gettype()
p2_type = p2.gettype()
exchange_check = random.randint(0,1)
if p1_type == p2_type:
system_temp[x]=0
system_temp[x+1]=0
number = number-2
elif exchange_check == 1:
type_temp = p1_type
p1.settype(p2_type)
p2.settype(type_temp)
elif system_temp[x]!=0 and system_temp[x+1]==0:
system_temp[x+1] = system_temp[x]
system_temp[x] =0
elif system_temp[x]==0 and system_temp[x+1]!=0:
system_temp[x]=system_temp[x+1]
system_temp[x+1]=0
numberlist[t]=numberlist[t]+number
return numberlist
size = 10000
system_init = [0]*size
particle_num = 3000
repeat = 20
steps = 2000
res=[]
totalnum= np.zeros(steps)
randomlist = random.sample(range(1,100*repeat),repeat)
for i in range(0,particle_num):
pos = random.randint(0,size-1)
ran_num = random.randint (0,1)
if ran_num == 0:
temp_type = 'A'
else:
temp_type = 'B'
if system_init[pos] ==0:
system_init[pos] = Classic(pos,temp_type)
for k in range(0, repeat):
system_temp = system_init[:]
random.seed(randomlist[k])
res.append(time_evolution(system_temp,steps))
for count in range(0,len(res)):
totalnum +=res[count]
time=np.linspace(1,steps+1,steps)
time_sqrt=np.sqrt(8.0*math.pi*time)
density =totalnum/(repeat*size)
density_mod = np.multiply(time_sqrt,density)
myfile=open('density_mod3.txt','w')
for element in density_mod:
myfile.write(str(element))
myfile.write('\n')
myfile.close()
And the result is shown as
enter image description here
The blue curve is result with multiprocessing and the orange one is that without multiprocessing. I am not sure why this would happen. How to fix it?

My guess is that you don't initialize the random number generator correctly. You have to do that "inside" the spawned processes.
Check the following simple example:
import random
import multiprocessing as mp
def rand_test_1():
print(random.randint(0, 100))
return None
def rand_test_2(seed):
random.seed(seed)
print(random.randint(0, 100))
return None
if __name__ == '__main__':
repeat = 3
randomlist = random.sample(range(1, 100 * repeat), repeat)
print('Classic:')
for k in range(repeat):
random.seed(randomlist[k])
rand_test_1()
print('\nMultiprocessing version 1:')
with mp.Pool() as pool:
for k in range(repeat):
random.seed(randomlist[k])
pool.apply_async(rand_test_1, args=tuple())
pool.close()
pool.join()
print('\nMultiprocessing version 2:')
with mp.Pool() as pool:
for k in range(repeat):
pool.apply_async(rand_test_2, args=(randomlist[k],))
pool.close()
pool.join()
The results look like:
Classic:
32
78
6
Multiprocessing version 1:
84
43
90
Multiprocessing version 2:
32
78
6
You are using the multiprocessing version 1, I think you should use version 2.
One other point which has nothing to do with your problem: My impression is that it might be a good idea to use .map/.starmap (see here) instead of .apply_async:
...
with mp.Pool() as pool:
res = list(pool.map(rand_test_2, randomlist))

Related

Hash table problem for education perposes

The problem is this:
It is required to create and use the hash table structure in a problem
large number of keys.
Here are the steps:
Creating one million (1,000,000) visits to a department store
and Credit Card Payment.
From the very large number of different cards, a relatively small subset is created
as follows. Credit cards for visits will have
sixteen (16) specific fixed digits eg 1234567890123456,
but in four (4) of the sixteen (16) random positions they will also have
four characters: X, Y, Z, W in random order.
eg 12Y45W789012Z4X6
In the other places the prices are the initial ones.
I have written the codes. Is is supposed to run super fast but it runs super slowly and I don't know why... Currently, I am running my code for 10,000 cards. Could you help me? Please excuse my poor english...
The code is bellow:
import string
import random
import time
random.seed(1059442)
global max_load_factor
max_load_factor = 0.6
def printGreaterThan2(num):
while True:
if num % 2 == 1:
isPrime = True
for x in range(3,int(num**0.5),2):
if num % x == 0:
isPrime = False
break
if isPrime:
return num
num += 1
N = printGreaterThan2(1000)
arr = [ [] for _ in range(N)]
arr = [ None for _ in range(N)]
def CreatNewItem():
letters = "WXZY"
days = ["Mon", "Tue", "Wed" , "Thu" , "Fri", "Sat"]
s = ''
count = 0
num = ['1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6']
list_a = []
while(count!=4):
a = random.randint(0,15)
b = random.choice(letters)
if b not in num and a not in list_a:
num[a] = b
count = count + 1
list_a.append(a)
s = ''.join(num)
d = random.randint(0,5)
day = days[d]
money = random.randint(10,100)
a = [s,day,money]
return a
def hash(key, tablesize):
sum = 0
for pos in range(len(key)):
sum = sum + ord(key[pos])
hash = sum % tablesize
return hash
#--------------------------------------
def rehash(oldhash , tablesize):
rehash = ( oldhash + 1 ) % tablesize
return rehash
#--------------------------------------
def put2 (arr,a,N,lenght,collitions):
if float(lenght)/float(N) >= max_load_factor:
(arr,N,collitions) = Resize(arr,N,lenght,collitions)
key = a[0]
i = hash(key,N)
j =0
while (True):
if arr[i] is None:
arr[i] = a
lenght = lenght + 1
break
elif arr[i][0] == key:
arr[i][2] = arr[i][2] + a[2]
arr[i][1] = arr[i][1] + a[1]
break
else:
if j == 0 :
collitions = collitions +1
j = 1
i = rehash(i,N)
return (lenght,N,arr,collitions )
#----------------------------------------
def Resize(arr,N,lenght,collitions):
print("resize")
N = printGreaterThan2(2*N)
collitions = 0
arr2 = [ [] for _ in range(N)]
arr2 = [ None for _ in range(N)]
for p in arr:
if p is not None:
(lenght,N,arr2,collitions)=put2(arr2,p,N,lenght,collitions)
return (arr2,N,collitions)
#-----------------------------------------
l = 0
cards = []
collitions = 0
t0 = time.time()
i=0
while i!=10000:
b = CreatNewItem()
(l,N,arr,collitions) = put2(arr,b,N,l,collitions)
i=i+1
t1 = time.time() - t0
print('\ntime is {:0.20f}'.format(t1))

Slow performance in agent based model python

I originally posted this on code-review (hence the lengthy code) but failed to get an answer.
My model is based on this game https://en.wikipedia.org/wiki/Ultimatum_game . I won't go into the intuition behind it but generally speaking it functions as follows:
The game consists of a n x n lattice on which an agent is placed at each node.
During each time step, each player on each node plays against a random neighbour by playing a particular strategy.
Each of their strategies (a value between 1-9) has a propensity attached to it (which is randomly assigned and is just some number). The propensity then in turn determines the probability of playing that strategy. The probability is calculated as the propensity of that strategy over the sum of the propensities of all strategies.
If a game results in a positive payoff, then the payoffs from that game get added to the propensities for those strategies.
These propensities then determine the probabilities for their strategies in the next time step, and so on.
The simulation ends after time step N is reached.
For games with large lattices and large time steps, my code runs really really slowly. I ran cProfiler to check where the bottleneck(s) are, and as I suspected the update_probabilitiesand play_rounds functions seem to be taking up a lot time. I want to be able to run the game with gridsize of about 40x40 for about 100000+ time steps, but right now that is not happening.
So what would be a more efficient way to calculate and update the probabilities/propensities of each player in the grid? I've considered implementing NumPy arrays but I am not sure if it would be worth the hassle here?
import numpy as np
import random
from random import randint
from numpy.random import choice
from numpy.random import multinomial
import cProfile
mew = 0.001
error = 0.05
def create_grid(row, col):
return [[0 for j in range(col)] for i in range(row)]
def create_random_propensities():
propensities = {}
pre_propensities = [random.uniform(0, 1) for i in range(9)]
a = np.sum(pre_propensities)
for i in range(1, 10):
propensities[i] = (pre_propensities[i - 1]/a) * 10 # normalize sum of propensities to 10
return propensities
class Proposer:
def __init__(self):
self.propensities = create_random_propensities()
self.probabilites = []
self.demand = 0 # the amount the proposer demands for themselves
def pick_strat(self, n_trials): # gets strategy, an integer in the interval [1, 9]
results = multinomial(n_trials, self.probabilites)
i, = np.where(results == max(results))
if len(i) > 1:
return choice(i) + 1
else:
return i[0] + 1
def calculate_probability(self, dict_data, index, total_sum): # calculates probability for particular strat, taking propensity
return dict_data[index]/total_sum # of that strat as input
def calculate_sum(self, dict_data):
return sum(dict_data.values())
def initialize(self):
init_sum = self.calculate_sum(self.propensities)
for strategy in range(1, 10):
self.probabilites.append(self.calculate_probability(self.propensities, strategy, init_sum))
self.demand = self.pick_strat(1)
def update_strategy(self):
self.demand = self.pick_strat(1)
def update_probablities(self):
for i in range(9):
self.propensities[1 + i] *= 1 - mew
pensity_sum = self.calculate_sum(self.propensities)
for i in range(9):
self.probabilites[i] = self.calculate_probability(self.propensities, 1 + i, pensity_sum)
def update(self):
self.update_probablities()
self.update_strategy()
class Responder: # methods same as proposer class, can skip-over
def __init__(self):
self.propensities = create_random_propensities()
self.probabilites = []
self.max_thresh = 0 # the maximum demand they are willing to accept
def pick_strat(self, n_trials):
results = multinomial(n_trials, self.probabilites)
i, = np.where(results == max(results))
if len(i) > 1:
return choice(i) + 1
else:
return i[0] + 1
def calculate_probability(self, dict_data, index, total_sum):
return dict_data[index]/total_sum
def calculate_sum(self, dict_data):
return sum(dict_data.values())
def initialize(self):
init_sum = self.calculate_sum(self.propensities)
for strategy in range(1, 10):
self.probabilites.append(self.calculate_probability(self.propensities, strategy, init_sum))
self.max_thresh = self.pick_strat(1)
def update_strategy(self):
self.max_thresh = self.pick_strat(1)
def update_probablities(self):
for i in range(9):
self.propensities[1 + i] *= 1 - mew # stops sum of propensites from growing without bound
pensity_sum = self.calculate_sum(self.propensities)
for i in range(9):
self.probabilites[i] = self.calculate_probability(self.propensities, 1 + i, pensity_sum)
def update(self):
self.update_probablities()
self.update_strategy()
class Agent:
def __init__(self):
self.prop_side = Proposer()
self.resp_side = Responder()
self.prop_side.initialize()
self.resp_side.initialize()
def update_all(self):
self.prop_side.update()
self.resp_side.update()
class Grid:
def __init__(self, rowsize, colsize):
self.rowsize = rowsize
self.colsize = colsize
def make_lattice(self):
return [[Agent() for j in range(self.colsize)] for i in range(self.rowsize)]
#staticmethod
def von_neumann_neighbourhood(array, row, col, wrapped=True): # gets up, bottom, left, right neighbours of some node
neighbours = set([])
if row + 1 <= len(array) - 1:
neighbours.add(array[row + 1][col])
if row - 1 >= 0:
neighbours.add(array[row - 1][col])
if col + 1 <= len(array[0]) - 1:
neighbours.add(array[row][col + 1])
if col - 1 >= 0:
neighbours.add(array[row][col - 1])
#if wrapped is on, conditions for out of bound points
if row - 1 < 0 and wrapped == True:
neighbours.add(array[-1][col])
if col - 1 < 0 and wrapped == True:
neighbours.add(array[row][-1])
if row + 1 > len(array) - 1 and wrapped == True:
neighbours.add(array[0][col])
if col + 1 > len(array[0]) - 1 and wrapped == True:
neighbours.add(array[row][0])
return neighbours
def get_error_term(pay, strategy):
index_strat_2, index_strat_8 = 2, 8
if strategy == 1:
return (1 - (error/2)) * pay, error/2 * pay, index_strat_2
if strategy == 9:
return (1 - (error/2)) * pay, error/2 * pay, index_strat_8
else:
return (1 - error) * pay, error/2 * pay, 0
class Games:
def __init__(self, n_rows, n_cols, n_rounds):
self.rounds = n_rounds
self.rows = n_rows
self.cols = n_cols
self.lattice = Grid(self.rows, self.cols).make_lattice()
self.lookup_table = np.full((self.rows, self.cols), False, dtype=bool) # if player on grid has updated their strat, set to True
def reset_look_tab(self):
self.lookup_table = np.full((self.rows, self.cols), False, dtype=bool)
def run_game(self):
n = 0
while n < self.rounds:
for r in range(self.rows):
for c in range(self.cols):
if n != 0:
self.lattice[r][c].update_all()
self.lookup_table[r][c] = True
self.play_rounds(self.lattice, r, c)
self.reset_look_tab()
n += 1
def play_rounds(self, grid, row, col):
neighbours = Grid.von_neumann_neighbourhood(grid, row, col)
neighbour = random.sample(neighbours, 1).pop()
neighbour_index = [(ix, iy) for ix, row in enumerate(self.lattice) for iy, i in enumerate(row) if i == neighbour]
if self.lookup_table[neighbour_index[0][0]][neighbour_index[0][1]] == False: # see if neighbour has already updated their strat
neighbour.update_all()
player = grid[row][col]
coin_toss = randint(0, 1) # which player acts as proposer or responder in game
if coin_toss == 1:
if player.prop_side.demand <= neighbour.resp_side.max_thresh: # postive payoff
payoff, adjacent_payoff, index = get_error_term(player.prop_side.demand, player.prop_side.demand)
if player.prop_side.demand == 1 or player.prop_side.demand == 9: # extreme strategies get bonus payoffs
player.prop_side.propensities[player.prop_side.demand] += payoff
player.prop_side.propensities[index] += adjacent_payoff
else:
player.prop_side.propensities[player.prop_side.demand] += payoff
player.prop_side.propensities[player.prop_side.demand - 1] += adjacent_payoff
player.prop_side.propensities[player.prop_side.demand + 1] += adjacent_payoff
else:
return 0 # if demand > max thresh -> both get zero
if coin_toss != 1:
if neighbour.prop_side.demand <= player.resp_side.max_thresh:
payoff, adjacent_payoff, index = get_error_term(10 - neighbour.prop_side.demand, player.resp_side.max_thresh)
if player.resp_side.max_thresh == 1 or player.resp_side.max_thresh == 9:
player.resp_side.propensities[player.resp_side.max_thresh] += payoff
player.resp_side.propensities[index] += adjacent_payoff
else:
player.resp_side.propensities[player.resp_side.max_thresh] += payoff
player.resp_side.propensities[player.resp_side.max_thresh - 1] += adjacent_payoff
player.resp_side.propensities[player.resp_side.max_thresh + 1] += adjacent_payoff
else:
return 0
#pr = cProfile.Profile()
#pr.enable()
my_game = Games(10, 10, 2000) # (rowsize, colsize, n_steps)
my_game.run_game()
#pr.disable()
#pr.print_stats(sort='time')
(For those who might be wondering, the get_error_term just returns the propensities for strategies that are next to strategies that receive a positive payoff, for example if the strategy 8 works, then 7 and 9's propensities also get adjusted upwards and this is calculated by said function. And the first for loop inside update_probabilities just makes sure that the sum of propensities don't grow without bound).

Python Game of Life, only alive cells is in the corners

I'm currently working on making a Game of Life program (UNI related, beginner course), by using nested lists.
However I can't seem to get the update() method to work properly, I've no clue what's wrong. The generation of the first board is okay, but the update leaves only the cornercells alive, and the rest dead.
All methodcalls in this class originates from other .py files, which works well.
from random import randint
from cell import *
class Spillebrett:
def __init__(self, rows, columns):
self.genNumber = 0
self._rows = rows
self._columns = columns
self._grid = []
for i in range(self._rows):
self._grid.append([])
for j in range(self._columns):
self._grid[i].append(cell())
self.generate()
def drawBoard(self):
for i in self._grid:
print(" ".join(map(str, i)))
print()
#Method updates genNumber, checks if cells are alive or dead and updates the board accordingly
#Currently only yield board with corner-cells alive
def updateBoard(self):
self.genNumber += 1
toLive = []
toDie = []
for x, row in enumerate(self._grid):
for y, cell in enumerate(rad):
if cell.areAlive() is True:
counter = len(self.findNeighbour(x, y))
if counter < 2 or counter > 3:
toDie.append(cell)
elif counter == 2 or counter == 3:
toLive.append(cell)
elif cell.areAlive() is False:
counter = len(self.findNeighbour(x, y))
if counter == 3:
toLive.append(cell)
for i in toDie:
i.setDead()
for i in toLive:
i.setAlive()
return self.genNumber
#Code given by Uni
def generate(self):
for i in range(self._rows):
for j in range(self._columns):
rand = randint(0, 3)
if rand == 3:
self._grid[i][j].setAlive()
#Code given by Uni
def findNeighbour(self, row, column):
neighbourList = []
for i in range(-1, 2):
for j in range(-1, 2):
neighbourRow = rad + i
neighbourcolumn = column + j
if(neighbourRow == rad and neighbourcolumn == column) is not True:
if(neighbourRow < 0 or neighbourcolumn < 0 or neighbourRow >
self._rows - 1 or neighbourcolumn > self._columns - 1) is not True:
neighbourList.append(self._grid[neighbourRow][neighbourcolumn])
return neighbourList
def findAllAlive(self):
self._alive = 0
for i in range(self._rows):
for j in range(self._columns):
if self._grid[i][j].areAlive() is True:
self._alive += 1
return self._alive

How can I change multithreading with multiprocessing in the code (Python)

I wrote a program which implements caesar-cipher with threads and queues with python. I would like to change all the threading work with multiprocessing in my code and I have no idea how to do it. I would appreciate if you could explain where & how to start the implementation. Here is the code:
import threading
import Queue
import sys
import string
lock = threading.Lock()
def do_work(in_queue, out_queue, shift):
while True:
lock.acquire()
item = in_queue.get()
result = caesar(item, shift)
out_queue.put(result)
in_queue.task_done()
lock.release()
def caesar(plaintext, shift):
plaintext = plaintext.upper()
alphabet = string.ascii_uppercase
shifted_alphabet = alphabet[shift:] + alphabet[:shift]
table = string.maketrans(alphabet, shifted_alphabet)
return plaintext.translate(table)
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Duzgun giriniz: '<filename>.py s n l'")
sys.exit(0)
else:
s = int(sys.argv[1])
n = int(sys.argv[2])
l = int(sys.argv[3])
work = Queue.Queue()
results = Queue.Queue()
myfile=open('metin.txt','r')
text_data=myfile.read() # <=== here load file
index=0
for i in xrange(n):
t = threading.Thread(target=do_work, args=(work, results, s))
t.daemon = True
t.start()
for i in range(0, len(text_data), l):
work.put(text_data[index:index + l])
index += l
work.join()
index=0
output_file=open("crypted"+ "_"+ str(s)+"_"+str(n)+"_"+str(l)+".txt", "w")
for i in range(0, len(text_data), l):
output_file.write(results.get())
index += l
sys.exit()
You can save yourself some code and move to the standard multiprocessing.Pool implementation.
import multiprocessing
import sys
import string
import itertools
# for non-forking systems like Windows
def worker(args):
# args: (text, shift)
return caesar(*args)
# for forking systems like linux
def forking_worker(args):
# args: ((start_index, end_index), shift)
return caesar(text_data[args[0][0]:args[0][1], args[1])
def caesar(plaintext, shift):
plaintext = plaintext.upper()
alphabet = string.ascii_uppercase
shifted_alphabet = alphabet[shift:] + alphabet[:shift]
table = string.maketrans(alphabet, shifted_alphabet)
return plaintext.translate(table)
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Duzgun giriniz: '<filename>.py s n l'")
sys.exit(0)
else:
s = int(sys.argv[1])
n = int(sys.argv[2])
l = int(sys.argv[3])
pool = multiprocessing.Pool() # todo: change number of cpus...
with open('metin.txt') as myfile:
text_data=myfile.read() # <=== here load file
# on a forking system so only pass index, not text to child
result = pool.map(forking_worker,
zip(((index, index + l)
for index in range(0, len(text_data), l)),
itertools.cycle([s])))
with open("crypted"+ "_"+ str(s)+"_"+str(n)+"_"+str(l)+".txt", "w") as output_file:
output_file.writelines(result)

How do I make an elif command which occurs in a for loop only register for certain instances of an item within the list?

Below is part of my code. Basically what this does is when run it creates credit cards with a random number and a given currency code and credit card limit. The Credit Card is a class and the money it stores is also a class (Which I have not included here for brevity and because I think they are not relevant to my question.) What happens in my code, however, is that my cancel statement works out fine but if I have two credit cards in a list and I try to cancel the second one it will again print out NO_SUCH_CARD. The card gets deleted anyway if its in the list. My guess is that his happens because the for loop iterates through the list and it first detects a card whose number is different than the given one, which is why it prints no such card, but I have no idea how to fix this. Help would be appreciated.
PATH = 'saved_cards.txt'
creditcard_list = []
import decimal
import ast
import collections
import os
import random
def currency_dictionary():
'''Initialized at the start of the program. It finds and reads a currency.txt
file and stores those in a dictionary'''
final_dict = collections.defaultdict(list)
with open("currency.txt", 'r') as f:
currency_lines = f.readlines()
for item in currency_lines:
m = item.split(' ')
final_dict[m[0]] = [int(m[1]), decimal.Decimal(m[2])]
return final_dict
class Money():
def __init__(self, money_amount: decimal, currency_code: str):
self.m = decimal.Decimal(money_amount)
self.c = str(currency_code)
self.d = currency_dictionary()
def __repr__(self):
return 'Money({}, {})'.format(self.m, self.c)
def __eq__(self, other):
if type(other) != Money:
return False
elif self.c == other.c:
return self.m == other.m
elif self.c != other.c:
dictionary_key1 = self.d[self.c]
decimal_point1 = dictionary_key1[0]
conversion_factor1 = dictionary_key1[1]
x = self.m / conversion_factor1
dictionary_key2 = self.d[other.c]
decimal_point = dictionary_key2[0]
conversion_factor = dictionary_key2[1]
y = other.m / conversion_factor
return x == y
def __ne__(self, other):
if type(other) != Money:
return True
elif self.c == other.c:
return self.m != other.m
elif self.c != other.c:
dictionary_key1 = self.d[self.c]
decimal_point1 = dictionary_key1[0]
conversion_factor1 = dictionary_key1[1]
x = self.m / conversion_factor1
dictionary_key2 = self.d[other.c]
decimal_point = dictionary_key2[0]
conversion_factor = dictionary_key2[1]
y = other.m / conversion_factor
return x != y
def __add__(self, other):
if self.c == other.c:
return Money((self.m + other.m), self.c)
elif self.c != other.c:
dictionary_key1 = self.d[self.c]
decimal_point1 = dictionary_key1[0]
conversion_factor1 = dictionary_key1[1]
x = self.m / conversion_factor1
dictionary_key2 = self.d[other.c]
decimal_point = dictionary_key2[0]
conversion_factor2 = dictionary_key2[1]
y = other.m / conversion_factor2
total = x + y
return Money((total * conversion_factor1), self.c)
def __sub__(self, other):
if self.c == other.c:
return Money((self.m - other.m), self.c)
elif self.c != other.c:
dictionary_key1 = self.d[self.c]
decimal_point1 = dictionary_key1[0]
conversion_factor1 = dictionary_key1[1]
x = self.m / conversion_factor1
dictionary_key2 = self.d[other.c]
decimal_point = dictionary_key2[0]
conversion_factor2 = dictionary_key2[1]
y = other.m / conversion_factor2
total = x - y
return Money((total * conversion_factor1), self.c)
class Credit_Card():
def __init__(self, card_number, money_amount: Money, card_limit: int):
if type(money_amount) != Money or type(card_limit) != int:
raise TypeError('one of the types of the parameters entered is not valid')
self.number = card_number
self.amount = money_amount
self.limit = card_limit
def __repr__(self):
return 'Card#{}({}, {})'.format(self.number, self.amount, self.limit)
def user_interface():
boolean = True
while boolean:
temp_list = []
command = input()
if command.split()[0] == 'ISSUE':
if len(command.split()) == 3:
x = "%0.5d" % random.randint(0,99999)
currency_code = command.split()[1]
card_limit = int(command.split()[2])
if card_limit < 0:
print("NEGATIVE_LIMIT")
elif not currency_dictionary()[currency_code]:
print("NO_SUCH_CURRENCY")
else:
for card in creditcard_list:
temp_list.append(card.number)
if x not in temp_list and currency_dictionary()[currency_code]:
creditcard_list.append(Credit_Card(x, Money(0, currency_code), card_limit))
print('ISSUED', x)
print(creditcard_list)
else:
print("INVALID_ISSUE_COMMAND")
elif command.split()[0] == 'CANCEL':
templist2 = []
if len(command.split()) == 2:
card_number = command.split()[1]
for card in creditcard_list:
templist2.append(card)
for i, card in enumerate(templist2):
if card_number not in templist2[i].number:
print('NO_SUCH_CARD')
elif templist2[i].amount.m != 0:
print('NONZERO_BALANCE')
elif templist2[i].number == command.split()[1] and card.amount.m == 0:
del creditcard_list[i]
print('CANCELLED', card_number)
print(creditcard_list)
elif command.split()[0] == 'PURCHASE':
if len(command.split()) == 4:
card_number = command.split()[1]
currency_code = command.split()[2]
amount = int(command.split()[3])
if currency_code not in currency_dictionary().keys():
print('NO_SUCH_CURRENCY')
elif amount < 0:
print('NONPOSITIVE_AMOUNT')
else:
for i, card in enumerate(creditcard_list):
if card.number == card_number and 0 <= amount <= card.limit :
x = card.amount + Money(amount, currency_code)
creditcard_list[i] = Credit_Card(card.number, x, card.limit)
elif creditcard_list[i].number != card_number:
print('NO_SUCH_CARD')
elif amount > creditcard_list[i].limit:
print('OVER_LIMIT')
elif command.split(0) == 'PAYMENT':
print(creditcard_list)
if __name__ == '__main__':
user_interface()
My output for the cancel command basically looks like this, and I'm pretty sure once I figure this out I'll be able to deal with the rest. Bold is input, non-bold is output.
**ISSUE USD 5000**
ISSUED 50695
[Card#50695(Money(0, USD), 5000)]
**ISSUE RON 3000**
ISSUED 25282
[Card#50695(Money(0, USD), 5000), Card#25282(Money(0, RON), 3000)]
**CANCEL 25282**
[Card#50695(Money(0, USD), 5000), Card#25282(Money(0, RON), 3000)]
*NO_SUCH_CARD*
CANCELLED 25282
[Card#50695(Money(0, USD), 5000)]
Note that the lists are only printed out for me to keep track of what cards are currently in the main list, and I will eventually remove those print statements. I have italicized the output I'm having a problem with.
The problem appears to be that you're modifying creditcard_list at the same time you are iterating through it. You should first create a temporary copy of the list to iterate through while later removing items from the actual list.
Edit: Ahh, it's printing "NO_SUCH_CARD" on the first card in the list! Not on the second. So in your example, you loop through a list of two; it first visits card #50695, which is not equal to 2582, so it prints "NO_SUCH_CARD". Then it visits 25282, deletes it, and prints "CANCELED". It's doing exactly what you wrote it to do. Instead, just loop through, deleting the card if you find it and silently skipping any card that doesn't match. In the end, if the card is not found print "NO_SUCH_CARD"
Edit2: Here's an example:
found = False
for card in templist2:
if card_number == card.number:
if card.amount.m != 0:
print('NONZERO_BALANCE')
else:
del creditcard_list[i]
print('CANCELLED', card_number)
found = True
break
if not found:
print('NO_SUCH_CARD')

Categories

Resources