I'm working on a project for an Intro to C/Python class and am looking to improve the efficiency of the program. The program is a lottery simulation where the user inputs the number of tickets they want to buy, then generates tickets, and finally outputs the total winnings and net gain (usually loss).
This is my code (in Python, as required):
def main():
numb_tickets = int(input("How many tickets would you like to buy?\n"))
#Calculate Winnings
winnings = 0
for i in range(numb_tickets):
#For testing only, gives feedback progress of program
print(i," ",winnings)
#Creating winning ticket/your ticket, find number of matches
win_tic = getRandomTicket(MAX_VALUE, TIX_SIZE)
my_tic = getRandomTicket(MAX_VALUE, TIX_SIZE)
numb_win = numMatches(win_tic, my_tic)
#Add appropriate payout for number of matches
if numb_win == 6:
winnings += WIN_SIX
elif numb_win == 5:
winnings += WIN_FIVE
elif numb_win == 4:
winnings += WIN_FOUR
elif numb_win == 3:
winnings += WIN_THREE
#Calculate cost of purchasing tickets
cost_tics = numb_tickets * COST_TIC
if winnings >= cost_tics:
profit = winnings - cost_tics
print("You won $",winnings,", for a net earnings of $",profit,".", sep="")
elif winnings < cost_tics:
loss = cost_tics - winnings
print("You won $",winnings,", for a net loss of $",loss,".", sep="")
main()
Note: the getRandomTicket() and numMatches() functions were provided by the professor to generate a lottery ticket and check the number of matches it has, respectively.
My program works fine for smaller numbers of tickets, but when testing the required 1,000,000 tickets, takes a massive amount of time. It makes sense that the time increases rapidly as the range of the loop increases, but I don't know of a better way to loop this yet. Any input or suggestions are greatly appreciated.
4 Answers 4
Doing this in a loop means O(N)
time, where N = # of tickets
.
If tickets are independent of each other, you can break this into chunks and process them in parallel. But threading is an advanced topic that you aren't likely to deal with in an intro class.
It's more of a style preference, but instead of the paragraph with if and elif's, I would do
winnings += payouts_dictionary[numb_wins]
where
payouts_dictionary = {6: WIN_SIX, 5: WIN_FIVE, 4: WIN_FOUR, 3: WIN_THREE, 2: 0, 1: 0, 0: 0}
is defined as a constant before the loop.
-
\$\begingroup\$ I noticed Ankur has the same answer. However, I doubt using a dict will increase performance over a 4-deep if statement. I would be glad if someone could do a performance comparison. \$\endgroup\$toto2– toto22013年09月09日 19:49:16 +00:00Commented Sep 9, 2013 at 19:49
-
\$\begingroup\$ Just did a profiling on both the ways and it's strange that the nested if-else ran faster than dictionary. Any idea why this is happening? \$\endgroup\$Ankur Ankan– Ankur Ankan2013年09月09日 20:06:11 +00:00Commented Sep 9, 2013 at 20:06
-
\$\begingroup\$ It might be O(1) to fetch a dictionary entry, but there is some processing for transformimg the given key to an array index. The big-O notation only gives the asymptotic behavior. I'm not surprised that it is slower. \$\endgroup\$toto2– toto22013年09月09日 20:11:47 +00:00Commented Sep 9, 2013 at 20:11
If this is python 2, try using xrange instead of range in your for loop. This will allow the for loop to generate the next iteration value on demand rather than generating and storing them all in memory upon the initial call to range.
-
4\$\begingroup\$ It's not. You can see the use of Python 3-style
input
andprint
. \$\endgroup\$user2357112– user23571122013年09月09日 19:40:58 +00:00Commented Sep 9, 2013 at 19:40
A major optimization that you could do is to define a dictionary for WIN_SIX
, WIN_FIVE
etc. This will return the value to add in O(1)
rather than going into nested if else every time. Other than that you could combine all your statements like winnings += numb_win_dict[numMatches(getRandomTicket(MAX_VALUE, TIX_SIZE), getRandomTicket(MAX_VALUE, TIX_SIZE))]
but it will result in loss of readability of your code.
Final code would be like this:
numb_win_dict = { 3: WIN_SIX, 4: WIN_FOUR, 5: WIN_FIVE, 6: WIN_SIX}
def main():
numb_tickets = int(input("How many tickets would you like to buy?\n"))
#Calculate Winnings
winnings = 0
for i in range(numb_tickets):
#For testing only, gives feedback progress of program
print(i," ",winnings)
#Add appropriate payout for number of matches
winnings += numb_win_dict[numMatches(getRandomTicket(MAX_VALUE, TIX_SIZE), getRandomTicket(MAX_VALUE, TIX_SIZE))]
#Calculate cost of purchasing tickets
cost_tics = numb_tickets * COST_TIC
if winnings >= cost_tics:
profit = winnings - cost_tics
print("You won $",winnings,", for a net earnings of $",profit,".", sep="")
elif winnings < cost_tics:
loss = cost_tics - winnings
print("You won $",winnings,", for a net loss of $",loss,".", sep="")
main()
-
\$\begingroup\$ Your code fails for <= 2 matches. \$\endgroup\$user2357112– user23571122013年09月09日 19:44:00 +00:00Commented Sep 9, 2013 at 19:44
-
\$\begingroup\$ @user2357112: I haven't checked the code whether it's working or not. I have just rewritten the exact same code in the question in a compact form and have replaced the nested if-else with a dictionary. \$\endgroup\$Ankur Ankan– Ankur Ankan2013年09月09日 19:49:35 +00:00Commented Sep 9, 2013 at 19:49
-
\$\begingroup\$ I've fixed some bugs (for
numMatches
<= 3) (pending acceptance of edits). For small non-negative integers, just use an array instead of a dict. Also, I've restored thewin_tic
andmy_tic
variables because the variable names self-document the functionality ofnumMatches()
, and it keeps lines shorter, and doesn't hurt efficiency. \$\endgroup\$200_success– 200_success2013年09月09日 20:19:19 +00:00Commented Sep 9, 2013 at 20:19 -
\$\begingroup\$ @200_success: I agree that having the variables
win_tic
andmy_tic
improves the readability of the code but it does hurt efficiency a bit. \$\endgroup\$Ankur Ankan– Ankur Ankan2013年09月09日 20:39:08 +00:00Commented Sep 9, 2013 at 20:39 -
\$\begingroup\$ @AnkurAnkan this is not the exact same code as in the question, because dictionary throws a KeyError if the number of matches is not defined whereas the original
if...
sequence does not. I suggest usingwinnings += numb_win_dict.get(..., 0)
\$\endgroup\$Stuart– Stuart2013年09月09日 20:44:47 +00:00Commented Sep 9, 2013 at 20:44
print(i," ",winnings)
and see if the performance improves. Or you could print every 10000 byif i % 10000 == 0:
\$\endgroup\$print('You won ${}, for a net loss of ${}.'.format(winnings, loss))
. \$\endgroup\$