Python, "If if line is called on first time, do something else" - python

So the title is pretty self explanatory, but i'll go into more detail. I am creating a text dependent game and I will have millions of areas. And each time you enter a new area, you will be greeted with a one time only different reaction than if you came into the same place again later, and I need to find a way to to this:
if len(line) == 1:
do exclusive thing
else:
do normal thing
Sure, I could use a counter system like "a = 0" but then I would need to create a separate counter for every single area I create, and I don't want that.

You could just store a single dict to keep track of room visits, and probably even better to use a defaultdict
from collections import defaultdict
#Using a defaultdict means any key will default to 0
room_visits = defaultdict(int)
#Lets pretend you had previously visited the hallway, kitchen, and bedroom once each
room_visits['hallway'] += 1
room_visits['kitchen'] += 1
room_visits['bedroom'] += 1
#Now you find yourself in the kitchen again
current_room = 'kitchen'
#current_room = 'basement' #<-- uncomment this to try going to the basement next
#This could be the new logic:
if room_visits[current_room] == 0: #first time visiting the current room
print('It is my first time in the',current_room)
else:
print('I have been in the',current_room,room_visits[current_room],'time(s) before')
room_visits[current_room] += 1 #<-- then increment visits to this room

You need static var : What is the Python equivalent of static variables inside a function?
def static_var(varname, value):
def decorate(func):
setattr(func, varname, value)
return func
return decorate
#static_var("counter", 0)
def is_first_time():
is_first_time.counter += 1
return is_first_time.counter == 1
print(is_first_time())
print(is_first_time())
print(is_first_time())

Related

Looping through attributes within Class instances?

I am trying to simulate a baseball game to learn more about python and programming in general... I ran into an interesting learning point in programing... and was wondering if someone could explain this error...
import random
rosterHome = []
rosterAway = []
class Player:
def __init__(self, number, battingAverage):
self.number = number
self.battingAverage = battingAverage
class Game:
def __init__(self):
self.inning = 0
self.homeScore = 0
self.awayScore = 0
self.outs = 0
def createStats():
for i in range(40):
stats = random.random()
x = Player(i, stats)
rosterHome.append(x)
for y in range(40):
stats = random.random()
y = Player(i, stats)
rosterAway.append(y)
def startGame():
Game.createStats()
Game.inning = 0
Game.homeScore = 0
Game.awayScore = 0
Game.outs = 0
Game.playInning()
def playInning():
totalHits = 0
if Game.inning >= 10:
print('Game is Over')
return
while Game.outs < 3:
for i in rosterHome:
x = rosterHome[i]
if x.battingAverage > random.random():
totalHits += 1
player += 1
print('batter ', player, ' got a hit')
else:
Game.outs += 1
player += 1
print('batter ', player, ' got out')
print('there are ', Game.outs, ' outs.')
Game.startGame()
x = rosterHome[i]
TypeError: list indices must be integers or slices, not Player
TLDR:
List indices must be integers or slices
The interpreter says "Hey, I see you're trying to access an item in a List by its index, but indices should be of type integer, however, you passed a value of type Player"
In Python and most programming languages, to reference an item in a List/Array, one way would be by index. Lists are zero-indexed, so the first item is of index 0, the second index 1, and so on.
Given an Array
my_array = ["bread", "foo", "bar"]
my_array[0] # would give you "bread"
my_array[1] # would give you "foo"
my_array[2] # would give you "bar"
However in your case, if we trace back up from where the error occurred, right here:
x = rosterHome[i]
You want to ask, what is the value of i? above this line is a for loop, and i represents each value in a list called rosterHome. So what the heck is in rosterHome anyways?
Moving up into your createStats method where you populated the rosterHome list, we see that you're pushing an instance of Player into the rosterHome list.
x = Player(i, stats)
rosterHome.append(x)
So rosterHome really isn't a list of numbers but instead a list of Player instances. You might want to review and try again, maybe accessing the number property of the Player object instead.
The error happens because rosterHome is a list of instances of the Player class, so when you iterate on the list (for i in rosterHome) each element will be an instance of said class (i is a Player). If you want to access the number of each player you'll have to access the attribute number of your Player instances, but it seems like actually you want to find the player instance. This means, you don't even need to lookup the value in the table, just use the value of the for loop. I'll use a different naming of variables to improve readability:
while Game.outs < 3:
for player in rosterHome:
# x wanted to access a player, but we don't need to do that actually
if player.battingAverage > random.random():
# ...
else:
# ...
This part of the answer considers that you actually want to meet both requirements (number of outs and iterate players once):
player_index = 0
while Game.outs < 3 and player_index< len(rosterHome):
player = rosterHome[player_index]
if player.battingAverage > random.random():
# ...
else:
# ...
if Game.outs == 3:
# Reached 3 outs
else:
# No players left and game outs < 3

What could be a better alternative to this For?

I'm a beginner trying to write up a game about trading in Python 3.x.
I have two classes (Depot and Good). Instances of the class Good are stored in a dictionary inside depot.inv (the instance as Key, and the amount of it as Value). When the user is asked to write what does he want to take, he will write the name of the instance ('Iron' instead of ir). So I took a For loop and searched through everything the Depot had in it's inventory. If he finds that the reply of the user is = to the name of any instance (ir.name, for example), then the program gains access to the instance as a whole.
The question is, how can I do this without a For-Loop?
I imagine that searching the whole inventory each time a reply is made is not optimal, even less if it's a linear search.
class Depot:
def __init__ (self, name, inv, bank):
self.name = name
self.inv = inv
self.bank = bank
class Good:
def __init__(self, name, weight, rare):
self.name = name
self.weight = weight
self.rare = rare
ir = Good('Iron', 1, 0.1)
gd = Good('Gold', 0.4, 2)
sl = Good('Silver', 0.7, 6.3)
mars = Depot('Mars', {ir: 10500, gd: 800, sl: 6000}, 1000)
player = {ir: 100}
reply = input('What do you want to take?')
i, q = reply.split()
for k in mars.inv.keys(): #This is the crux of the problem
if i in k.name:
x = k
print('Got it')
if x in mars.inv:
if int(q) > mars.inv[x]:
print('We dont have that much.')
elif int(q) <= mars.inv[x]:
mars.inv[x] -= int(q)
if x in player:
player[x] += int(q)
elif i not in player:
player[x] = int(q)
Using list comprehension and next() allows you to only find the first occurance of the item you're looking for, and then stop. Breaking the for loop after finding the key you're looking for would achieve the same thing. Both are O(n) there isn't really that much room to optimize. A one liner would be
x = next((k for k in mars.inv.keys() if i in k.name), None)
I'd probably duplicate the name as the key of the dictionary and map it to a tuple of the other information:
{"Iron": (ir, 10500), "Gold": (gd, 800), "Silver": (sl, 6000}
Then you can do mars.inv["Iron"] to get the tuple of (ir, 10500), and you can extract whichever data you want from that.
Note though, this requires an exact match on the name. You're currently using in to do the check, which will prevent any optimizations. Since from your description, you're doing lookups based on exact names (ignoring case, since that can be easily accounted for), in is unnecessary.

Reset counter to zero if different day - Python

New to python, so I have this setup where I file gets created, and I have to add an extension number. The first file will have an extension number of 1 since being the first. A second file gets created and the extension number will increment, so it will be 2. So each files gets created, the extension number will increment.
Now, if it's a different day then that extension number will reset to 1, and it will increment if new files are created. So each day, the extension number needs to be reset to 1
def get_counter(date):
counter = 1
now = datetime.datetime.utcnow().strftime('%Y-%m-%d')
if date != now:
now = date
counter = 1
return counter
counter += 1
return counter
I have set up this function but it will not work because the now and counter variable will get overwritten. So will need these variables somewhere else. Just wondering if there is a work around this process or is there a python library that can handle this type of situation. Your suggestions will be appreciated!
You could assign the counter outside of that function and send it as a parameter, that way you don't overwrite it every single time you call your function, like so:
counter = 1
for file_to_be_writen in file_collection:
counter = get_counter(date, counter)
and leave your function like this:
def get_counter(date, counter):
now = datetime.datetime.utcnow().strftime('%Y-%m-%d')
if date == now:
counter += 1
return counter
return counter
When you need to preserve state across function calls that is a hint that you need a custom object. You could use global variables as well but encapsulating the state inside an object is usually better.
Here I implement a class Counter that takes care of everything. It has a __next__ method that returns the next number so the calling code only needs to call next(counter). It also has an __iter__ method so it can be used in for loops.
You need to provide a function to get the current (date_getter) time when creating an instance. Besides making the code more testable this allows you to decide if you want to use utc time, local time, the first day of the week so the counter resets each week, etc.
import datetime
class TimeArrowReversedError(Exception):
pass
class Counter:
def __init__(self, date_getter):
self._date_getter = date_getter
self._current_date = date_getter()
self._current_value = 0
def _check_date(self):
current_date = self._date_getter()
if self._current_date > current_date:
message = 'Time arrow got reversed. Abandon all hope.'
raise TimeArrowReversedError(message)
if self._current_date < current_date:
self._current_date = current_date
self._current_value = 0
def __next__(self):
self._check_date()
self._current_value += 1
return self._current_value
def __iter__(self):
return self
This is the code I used to test it. Note that I am using as date_getter a function that actually returns whatever date I want. I do not want to wait until 23:59 to run the test. Instead I tell the function which date to return (including going backwards in time) and see how the counter behaves.
current_date = datetime.date(year=2018, month=5, day=9)
get_current_date = lambda: current_date
counter = Counter(get_current_date)
n = next(counter)
assert n == 1
n = next(counter)
assert n == 2
for expected, result in zip([3, 4], counter):
assert expected == result
current_date = current_date + datetime.timedelta(days=1)
n = next(counter)
assert n == 1
n = next(counter)
assert n == 2
current_date = current_date - datetime.timedelta(days=2)
try:
n = next(counter)
except TimeArrowReversedError:
pass
else:
raise AssertionError('"TimeArrowReversedError" expected.')
Here is a more realistic way in which yo could use this class:
def create_counter():
return Counter(datetime.datetime.utcnow().date)
counter = create_counter()
Print the first couple of numbers:
print(next(counter))
print(next(counter))
Output:
1
2
Using a loop to add numbers to names in a list:
names = ['foo', 'bar']
for name, n in zip(names, counter):
print('{}_{}'.format(name, n))
Output:
foo_3
bar_4
Now I realize that Counter is a really bad choice because there is already a completely unrelated Counter class in the standard library. But I cannot think of a better name right now so I will leave it as is.

Drop Item If More Than 1 Item Present

I'm working on a piece of code, and it is for a little AI creature to randomly come into a room, and look to see if there is anything there. If there is anything that the player has touched before, then it takes the item. The next room it goes to it drops that item and may pick up a new one. So far, I have:
import random
rooms = ['kitchen', 'livingroom', 'basement']
itemsstatus = {'Umbrella': 1, 'Coin': 1}
itemsstatus['Umbrella'] = raw_input()
print "itemstatus['Umbrella']", itemsstatus['Umbrella']
roominventory = ['Umbrella', 'Coin']
goblininventory = ['baseball']
notpickedanythingelse = 'true'
gotoroom = random.choice(rooms)
if(gotoroom == 'kitchen') or (gotoroom == 'livingroom') or (gotoroom == 'basement'):
ininventory = len(goblininventory)
if(ininventory >= 1):
roominventory.append(goblininventory[0])
goblininventory.remove([0])
else:
print ""
for items in roominventory:
if(itemsstatus[items] == 1) and (notpickedanythingelse == 'true'):
goblininventory.append(items)
roominventory.remove(items)
notpickedanythingelse = 'false'
else:
print ""
notpickedanythingelse = 'true'
print roominventory
print goblininventory
The itemstatus[''] = rawinput() will be done automatically by the game and won't be a raw imput, it is just here so I can test it. As well, each room will have it's own inventory and loop, but this is just for the simplicity of it. The goblin will pick up an item, and keep it, but it won't drop the one it already has (it can only carry 1 thing at a time). How can I get it so that it will drop the item it is holding once entering a new room?
goblininventory.remove([0]) is incorrect. You should use goblininventory.pop() to remove the first element from the list.
See here for more info on remove and pop: https://docs.python.org/2/tutorial/datastructures.html#more-on-lists

Why is the counter not being reset to 0 in a recursive function?

I'm watching the MIT intro to ComScip and have to do a recursive call. I had trouble fitting in a counter nicely to count the number of string appearance. With a stroke of luck and playing with the position of the counter, the follow solution works but I don't WHY:
import string
target = "banana"
key = "an"
key_len = len(key)
found_pos = 0
found_pos = string.find(target,key)
def countSubStringMatchRecursive (target, key):
counter=0
found_pos = string.find(target,key)
if(found_pos!=-1):
print "found"
slice_pos = found_pos + key_len
counter = countSubStringMatchRecursive (target[slice_pos:], key)
counter+=1
#print counter
return counter
print countSubStringMatchRecursive (target, key)
Here is my understanding:
First recursion:
initialize counter=0
if key is found is target, counter=countSubStringMatchRecursive (target[slice_pos:], key)
This should reset the counter to 0 again because the main function is being ran
counter+=1
return 1 as the counter value because 0+1=1
The part I don't understand is why counter=0 not re-initializing the counter to 0. Instead, it lets itself to accumulate the previous value and produce the right result
counter is a variable local to the method. This means that each recursive stack frame (each new method call gets a new "stack frame") has its own copy of counter. Therefore, while counter is set to 0 every time the method is called, all these copies of counter are in fact DIFFERENT pieces of memory, and do not affect one another.

Categories