What could be a better alternative to this For? - python

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.

Related

How find the max of a list and then store the max in a new list

I am trying to find the max of the "rollList" and everything I have tried isn't working.I'm not very good with coding and the instruction my teacher gave me isn't very clear. I also have to reset "rollList" back to empty for each player and I am very confused.Please someone help.
import random
class Player:
def __init__(self,name ):
self.name = name
self.dice = []
def __str__(self):
return self.name
def roll_Dice(self):
rollDice = random.randint(1, 6)
return rollDice
rounds = 1
rollList = []
newplayer = []
newplayer.append(Player("CAT:"))
newplayer.append(Player("DOG:"))
newplayer.append(Player("LIZARD:"))
newplayer.append(Player("FISH:"))
for rounds in range(1,4):
print("-----------------")
print("Round" + str(rounds))
for p in newplayer:
print(p)
for x in range (4-rounds):
rollDice = random.randint(1, 6)
rollList.append(rollDice)
print(rollList)
max.pop(rollList)
print(rollList)
rollList.clear()
len(rollList)
The line max.pop(rollList) is fairly meaningless. It attempts to call the pop method of the built-in max function, which doesn't exist.
You can get the maximum by just calling max itself:
maxRoll = max(rollList)
If you want to remove that roll, you can (although it doesn't seem necessary, since you'll be clearing the list):
rollList.remove(maxRoll)
If you want to append the maximum to another list:
anotherList.append(maxRoll)
You can find the maximum of a list using max() function:
mylist = [1,2,4,5,6,7,-2,3]
max_value = max(mylist)
Now max_value is equal to 7. You can add this to a new list using append() method:
new_list = []
new_list.append(max_value)
then new_list will be [7]
I report some suggestions to resolve the error I suppose you have: AttributeError: 'builtin_function_or_method' object has no attribute 'pop'
Just change max.pop(rollList) to max(rollList).
Then you have a list of only one element because you are calling methods inside the for rounds in range(1,4): loop, without letting the list populate with other elements. You are calling also clear at each loop.
Also, the for x in range (4-rounds): it is not required, it's a nested loop.
You are printing the list of names without assign to each person the value of roll dice, so who's the winner?
Finally, you defined roll_Dice() as instance method of Person, so why not use it?
So, why not rollList.append(p.roll_Dice()) instead of:
rollDice = random.randint(1, 6)
rollList.append(rollDice)
Hope this can help.

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

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())

Random List Generation not working

I am working on a game where I need to randomly generate classes for a list. I use a self-made function randList to do this. The code for that function looks like this:
def randList(options, num): #RANDOMLY SELECTS NUM ITEMS FROM LIST OPTIONS
returnVal = [] #CREATE A LIST THAT IT RETURNS
for i in range(num - 1): #FOR DESIRED NUMBER OF RETURN ITEMS
val = r.choice(options) #RANDOMLY SELECT ITEM FROM OPTIONS
returnVal.append(val) #ADD THAT TO RETURNVAL
options.remove(val) #REMOVE IT FROM OPTIONS.
return returnVal #RETURN GENERATED LIST
I am using that to randomly generate monsters and items in a room like so:
class roomParent: #ROOM CHARACTER FINDS
def __init__(self, entities, floor): #INIT WITH ENEMIES IN ROOM, ITEMS ON FLOOR
self.entities = entities #ENEMIES THERE ARE
self.floor = floor #ON FLOOR THERE IS
def generate(self):
global enemiesBeat
if enemiesBeat >= 500:
self.entities = [dragon]
else:
self.entities = randList([goblin, dwarf, slime, naga, troll, beholder], 1)
self.floor = randList([scrap, scrap, scrap, fireJar, ambrosia, sword, spearhead, armor, potion, slimeball], r.randint(0, 3))
room = roomParent([], [])
Just so you know, goblin, dwarf, slimeball, etc. are defined earlier in the code. I don't think they have anything to do with the problem. I generate the room later like this:
def main():
room.generate()
print("Enemies: " + str(room.entities))
main()
I want it to print out a list with two random monsters in it from room.generate(), but it always prints Enemies: []. There are no errors in the code, and after trying to troubleshoot for 10 minutes, I decided to consult he web with no fruits in result of that labor. Thank you in advance for any help you give.
As Oliver points out, the reason you get always get an empty entities array is because self.entities is set to randList([goblin, dwarf, slime, naga, troll, beholder], 1) within generate (I assume the global variable enemiesBeat is less than 500 in your tests).
In your randList function you have an off-by-one error that I mention in the comments which means that the generated list will contain one fewer items than specified by num. As you try to generate a singleton list for self.entities (num = 1), you'll actually have it assigned to an empty list.
You can correct this issue by changing for i in range(num - 1) to for i in range(num) in your randList function.
As an aside, I don't think you need to pass entities and floor as parameters to the roomParent constructor, since it doesn't seem to have any effect. Instead, you could modify the class definition:
class roomParent(object):
def __init__(self):
self.entities = []
self.floor = []
...
And instantiate it like this:
room = roomParent()
Use the random.sample library function.
Also, you might want to rethink your capitalization...snake_case is preferred over inCaps for function names.

PEG Online Judge Coding

I am solving a problem on the PEG Online Judge, which is a site where you can solve lots of problems for practice and fun.
I am having trouble with one in particular. I have posted there for help but am not receiving any.
It is the Caporegime problem: http://wcipeg.com/problem/capos
You can use a number of languages to solve this. I decided on Python (although I have coded it in C++ too). There are 12 datasets the judge uses in testing the code. My code passes 11/12. I have no idea why I can't pass the last test and am hoping someone can help me.
I think it's a set partitioning problem of some kind and I solve it with a breadth first search approach. The problem datasets are not big, so it doesn't get out of hand.
Here is my solution:
import sys
import copy
class SearchState():
def __init__(self, label, crews):
self.label = label
self.crews = crews
def __repr__(self):
return "State: %s: %s" % (self.label, str(self.crews))
def crewsSoldierCanBeIn(s, crews, grudges):
'''
For a given soldier and a list of crews and grudges,
return the crews the soldier an go in
'''
noGrudgeCrews = []
for i, crew in enumerate(crews):
conflict = False
for c in crew:
if [s, c] in grudges or [c, s] in grudges:
conflict = True
break
if not conflict:
noGrudgeCrews.append(i)
return noGrudgeCrews
def solve(numSoldiers, grudges):
'''
Put each soldier in a crew, output min no. of crews and who is in them
'''
crews = [[1]]
numStates = 0
states = [SearchState(numStates, crews)]
for s in range(2, numSoldiers+1):
newStates = []
for state in states:
possibleCrews = crewsSoldierCanBeIn(s, state.crews, grudges)
if len(possibleCrews) > 0:
for crew in possibleCrews:
numStates += 1
newCrews = copy.deepcopy(state.crews)
newCrews[crew].append(s)
newStates.append(SearchState(numStates, newCrews))
else:
numStates += 1
newCrews = copy.deepcopy(state.crews)
newCrews.append([s])
newStates.append(SearchState(numStates, newCrews))
states = copy.deepcopy(newStates)
minNumCrews = 1000000
minState = -1
for i, state in enumerate(states):
if len(state.crews) < minNumCrews:
minNumCrews = len(state.crews)
minState = i
print(len(states[minState].crews))
for crew in states[minState].crews:
for s in crew:
print("%d " % (s), end = "")
print()
def readInData(f):
numSoldiers, numGrudges = map(int, f.readline().strip().split())
grudges = []
for _ in range(numGrudges):
grudges.append(list(map(int, f.readline().strip().split())))
return numSoldiers, grudges
def main():
# Read in the data
f = sys.stdin
numSoldiers, grudges = readInData(f)
solve(numSoldiers, grudges)
if __name__ == '__main__':
main()
Ok, so I've finally solved this.
Basically I needed to use a DFS approach, it can't really be solved (withing the online Judge's memory and time constraints) via BFS.
The advantage of DFS is twofold: 1) I can reach a solution (not the best solution) fairly quickly and use this to prune the tree, to get rid of heaps of partial solutions that will never be any good and 2) Very little memory is needed.
So DFS is faster and uses less memory, for this problem.

Using lambda function to change value of an attribute

Can I use lambda function to loop over a list of class objects and change value of an attribute (for all objects or for the one that meet a certain condition)?
class Student(object):
def __init__(self,name,age):
self.name = name
self.age = age
student1 = Student('StudOne',18)
student2 = Student('StudTwo',20)
student3 = Student('StudThree',29)
students = [student1,student2,student3]
new_list_of_students = map(lambda student:student.age+=3,students)
Unfortunately, that’s not possible since the body of a lambda only allows for simple expressions while a student.age += 3 is a statement. So you can’t use a lambda there. You could however still use the map solution:
def incrementAge (student):
student.age += 3
return student
students2 = map(incrementAge, students)
Note that students2 will contain the same students as students though, so you don’t really need to capture the output (or return something from incrementAge). Also note that in Python 3, map returns a generator which you need to iterate on first. You can call list() on it to do that: list(map(…)).
Finally, a better solution for this would be to use a simple loop. That way, you don’t have overhead of needing a function or create a duplicate students list, and you would also make the intention very clear:
for student in students:
student.age += 3
Using a simple for-loop to retrieve the students to update the age for each is good enough like others said, but if you still want to use a lambda to update the values, you may need to leverage the exec() function:
_ = list(map(lambda student: exec("student.age+=3"), students))
for _ in students: print(_.age)
Output:
21 23 32
In this case, what actually does the updating is the exec(), and the map() just yields None. So the returned result makes no sense and I put a _ to clarify this. A more concise form would be this:
list(map(lambda _: exec("_.age+=3"), students))
Besides, if only considering what you want to do, you don't need to use a map() at all (probably more confusing though):
[(lambda _: exec("_.age += 3"))(_) for _ in students]
Furthermore, a lambda can be discarded either:
[exec("_.age += 3") for _ in students]
As you can see, no "trick" codes above seem more concise than what other answers post:
for s in students:
s.age += 3
So maybe the so-called "one-liner" is useful just when it comes to having fun... :)
Lambda functions can only contain expressions, not statements. Assignment in Python is a statement. Lambdas cannot do assignments. Additionally, assignment statements do not evaluate to their values, so your map would not produce a list of students.
You want this:
for student in students:
student.age += 3
This does not give you a new list, it modifies the old list, but your old list would be modified anyway, you aren't doing anything to produce new Students.
You can use setattr, which will apply the change to the objects. A big plus is that you can continue using the same list.
map(lambda s: setattr(s, 'age', s.age + 3), students)
From the docs:
The function assigns the value to the attribute, provided the object allows it. For example, setattr(x, 'foobar', 123) is equivalent to x.foobar = 123.
The equivalency of which is:
for s in students:
s.age += 3
If you really want a new list:
The above approach doesn't return a new list; instead returning None (the return value of setattr). Adding an or comparison with the object you want in the array (in this case s) will amend that, though.
new_students = map(lambda s: setattr(s, 'age', s.age + 3) or s, students)
The comparison is equivalent to None or s which will always yield the latter. Also note that the new list is identical to the old one.
Have you tried playing around with the setattr and getattr functions? With these you can write and read the writable attributes of the object directly.
So you could do something like this:
map_of_students = map(lambda student: setattr(student, "age", getattr(student, "age") + 3), students)
print("Age BEFORE list: " + str(students[0].age)); list(map_of_students); print("Age AFTER list: " + str(students[0].age))
In this case the original students' list will be updated with the new ages for each student, which might not be necessarily what you want, but can easily be worked around by making a backup of the original list's objects before casting the map object to a list. In that case, you could do:
students_bkp = []
for s in students:
students_bkp.append(Student(**s.__dict__))
Here is the full snippet:
class Student(object):
def __init__(self,name,age):
self.name = name
self.age = age
student1 = Student('StudOne',18)
student2 = Student('StudTwo',20)
student3 = Student('StudThree',29)
students = [student1,student2,student3]
students_bkp = []
for s in students:
students_bkp.append(Student(**s.__dict__))
map_of_students = map(lambda student: setattr(student, "age", getattr(student, "age") + 3), students)
print("Age BEFORE list: " + str(students[0].age)); list(map_of_students); print("Age AFTER list: " + str(students[0].age)); print("Age BKP: " + str(students_bkp[0].age))
Q: "Can I use lambda function to loop over a list of class objects and change value of an attribute"
A: Yes....but you shouldn't. It's poor coding style, inefficient, and only appropriate for things like code golf
You should write it like the other two answers have suggested.
...but if you really wanted to...
new_list_of_students = [(lambda student:(setattr(student, 'age', student.age+3),
student))(s)[1] for s in students]
print [student.age for student in new_list_of_students]
Prints:
[21, 23, 32]
...or even:
from operator import itemgetter
new_list_of_students = map(itemgetter(1),map(lambda student:(setattr(student,
'age', student.age+3), student),students))
print [student.age for student in new_list_of_students]
[Same output]

Categories