How to prevent numbers from getting too low/high in python? - python-3.x

I'm currently working on a "Hunger Games"-Simulator and every player has 17 traits. The traits of the winning person get changed the least and the traits of the worst person get changed much. But after I change the traits they often go below zero or above 1000 (should be max) even though I even coded a backup for this.
I already tried to just set them back into the range 1-1000 and give the leftover trait-points to another trait. It also just happens after multiple iterations.
This is the code to change the traits:
List "pl" is looking like this: [trait1, trait2, ..., trait18]
"count" is the rank.
while again == True:
for k in range(int(count/2)):
randnum = random.randint(0,20)
randnum2 = random.randint(0,15)
randnum3 = random.randint(0,15)
pl[randnum2] += randnum
pl[randnum3] -= randnum
for y in pl:
if y > 1000:
over = abs(y - 1000)
randnum = random.randint(0,15)
if pl[randnum] > 1000 - over:
break
else:
y = 1000
pl[random.randint(0,15)] += over
if y < 0:
over = abs(y)+1
randnum = random.randint(0,15)
if pl[randnum] < over:
break
else:
y = 1
pl[randnum] -= over
for y in pl:
if y > 1000 or y < 1:
again = True
break
else:
again = False
I expected that the traits get updated so that if you add all trait-points together you always get the same result with every player. But it seems like they either get really big or really small and don't have the same sum all the time.
Thanks! :)

use special list-like class with method:
def __setitem__(self, name, value):
if minValue < value < maxValue:
super().__setitem__(name, value)
and define other methods (__add__, append, ...) using that

Related

How can I implement " for (int i=0; i<str.size; i= i*5) " in python using for loop?

Can I use range() here, or is there any other way?
You can just use while for this:
i = 0 // maybe 1 would be better?
while i < someSize:
doSomething()
i *= 5
Just be aware that this is an infinite loop (unless someSize is zero, of course) if, as you did in your title, you start at zero. It doesn't matter how many time you multiply zero by five, you'll always end up with zero.
As an aside, there's nothing to stop you creating your own iterator, even one that calls a function to decide the next value and whether the iterator should stop.
The following code provides such a beast, one where you specify the initial value and a pair of functions to update and check the values (implemented as lambdas in the test code):
# Iterator which uses a start value, function for calculating next value,
# and function for determining whether to continue, similar to standard
# C/C++ for loop (not C++ range-for, Python already does that).
class FuncIter(object):
def __init__(self, startVal, continueProc, changeProc):
# Store all relevant stuff.
self.currVal = startVal
self.continueProc = continueProc
self.changeProc = changeProc
def __iter__(self):
return self
def __next__(self):
# If allowed to continue, return current and prepare next.
if self.continueProc(self.currVal):
retVal = self.currVal
self.currVal = self.changeProc(self.currVal)
return retVal
# We're done, stop iterator.
raise StopIteration()
print([item for item in FuncIter(1, lambda x: x <= 200, lambda x: x * 4 - 1)])
The expression:
FuncIter(1, lambda x: x <= 200, lambda x: x * 4 - 1)
generates the values that you would get with the equivalent C/C++ for statement:
for (i = 1; i <= 200; i = i * 4 - 1)
as shown by the output:
[1, 3, 11, 43, 171]

A strategy-proof method of finding the time complexity of complex algorithms?

I have a question in regard to time complexity (big-O) in Python. I want to understand the general method I would need to implement when trying to find the big-O of a complex algorithm. I have understood the reasoning behind calculating the time complexity of simple algorithms, such as a for loop iterating over a list of n elements having a O(n), or having two nested for loops each iterating over 2 lists of n elements each having a big-O of n**2. But, for more complex algorithms that implement multiple if-elif-else statements coupled with for loops, I would want to see if there is a strategy to, simply based on the code, in an iterative fashion, to determine the big-O of my code using simple heuristics (such as, ignoring constant time complexity if statements or always squaring the n upon going over a for loop, or doing something specific when encountering an else statement).
I have created a battleship game, for which I would like to find the time complexity, using such an aforementioned strategy.
from random import randint
class Battle:
def __init__(self):
self.my_grid = [[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False]]
def putting_ship(self,x,y):
breaker = False
while breaker == False:
r1=x
r2=y
element = self.my_grid[r1][r2]
if element == True:
continue
else:
self.my_grid[r1][r2] = True
break
def printing_grid(self):
return self.my_grid
def striking(self,r1,r2):
element = self.my_grid[r1][r2]
if element == True:
print("STRIKE!")
self.my_grid[r1][r2] = False
return True
elif element == False:
print("Miss")
return False
def game():
battle_1 = Battle()
battle_2 = Battle()
score_player1 = 0
score_player2 = 0
turns = 5
counter_ships = 2
while True:
input_x_player_1 = input("give x coordinate for the ship, player 1\n")
input_y_player_1 = input("give y coordinate for the ship, player 1\n")
battle_1.putting_ship(int(input_x_player_1),int(input_y_player_1))
input_x_player_2 = randint(0,9)
input_y_player_2 = randint(0,9)
battle_2.putting_ship(int(input_x_player_2),int(input_y_player_2))
counter_ships -= 1
if counter_ships == 0:
break
while True:
input_x_player_1 = input("give x coordinate for the ship\n")
input_y_player_1 = input("give y coordinate for the ship\n")
my_var = battle_1.striking(int(input_x_player_1),int(input_y_player_1))
if my_var == True:
score_player1 += 1
print(score_player1)
input_x_player_2 = randint(0,9)
input_y_player_2 = randint(0,9)
my_var_2 = battle_2.striking(int(input_x_player_2),int(input_y_player_2))
if my_var_2 == True:
score_player2 += 1
print(score_player2)
counter_ships -= 1
if counter_ships == 0:
break
print("the score for player 1 is",score_player1)
print("the score for player 2 is",score_player2)
print(game())
If it's just nested for loops and if/else statements, you can take the approach ibonyun has suggested - assume all if/else cases are covered and look at the deepest loops (being aware that some operations like sorting, or copying an array, might hide loops of their own.)
However, your code also has while loops. In this particular example it's not too hard to replace them with fors, but for code containing nontrivial whiles there is no general strategy that will always give you the complexity - this is a consequence of the halting problem.
For example:
def collatz(n):
n = int(abs(n))
steps = 0
while n != 1:
if n%2 == 1:
n=3*n+1
else:
n=n//2
steps += 1
print(n)
print("Finished in",steps,"steps!")
So far nobody has been able to prove that this will even finish for all n, let alone shown an upper bound to the run-time.
Side note: instead of the screen-breaking
self.my_grid = [[False,False,...False],[False,False,...,False],...,[False,False,...False]]
consider something like:
grid_size = 10
self.my_grid = [[False for i in range(grid_size)] for j in range(grid_size)]
which is easier to read and check.
Empirical:
You could do some time trials while increasing n (so maybe increasing the board size?) and plot the resulting data. You could tell by the curve/slope of the line what the time complexity is.
Theoretical:
Parse the script and keep track of the biggest O() you find for any given line or function call. Any sorting operations will give you nlogn. A for loop inside a for loop will give you n^2 (assuming their both iterating over the input data), etc. Time complexity is about the broad strokes. O(n) and O(n*3) are both linear time, and that's what really matters. I don't think you need to worry about the minutia of all your if-elif-else logic. Maybe just focus on worst case scenario?

How do I fix TypeError: 'int' object is not iterable

I am creating a 2D RPG game and want the Defence stat from a base number to increase by the random numbers between 4 and 8 as the player's level increases by 1 each time.
In the previous while loop the int() works fine but this one does not and I can't remember how it got fixed.
I have a class Player which I have def Shield(self): then I have if statements of small shield, medium shield, large shield. Each shield depends on what the player class is, such as; Assassin or Warrior which are if statements within the shield if statements. within the player classes if statements I have self.S_Defence += # and as you can see up there self.D_Stats = self.S_Defence, but the code up there should be updating and resetting the number to an increased number
self.S_Exp = 600
while self.S_Exp >= 0 + (self.S_Level * 100):
self.S_Level += 1
self.S_Exp -= ((self.S_Level - 1) * 250)
self.S_Level_Stats = [self.S_Exp, self.S_Level]
self.Increased_Defence = []
for loop in self.D_Stats:
loop += random.randint(4, 8)
self.Increased_Defence.append(loop)
self.D_Stats = self.Increased_Defence
self.D_Stats = [self.S_Defence]
What is expected is if the player level increases, the specific stat in this case Defence should increase by 4,5,6,7, or 8
EDIT: The definition for self.D_Stats is not a list, see the definition for
self.P_Stats = [self.P_HP, self.P_Attack, self.P_Speed]. This isn't working "the same" because (I guess) it should be: self.D_Stats = [self.S_Defence]
Python for loops need to take an iterable. So in this for loop, self.D_Stats must be a list, or suchlike.
for loop in self.D_Stats:
loop += random.randint(4, 8)
self.Increased_Defence.append(loop)
self.D_Stats = self.Increased_Defence
self.D_Stats = self.S_Defence
If self.D_Stats is an integer, then use the python range() function, which creates an iterable from 0 -> <argument>-1, or otherwise with extra arguments.
for loop in range( self.D_Stats ):
loop += random.randint(4, 8)
The body of the for loop looks a bit strange too. self.D_Stats is set twice, so the self.Increased_Defence value is being lost. Is self.D_Stats supposed to be a list/array and be .append()ed to ?
Based on the description of what the code should do, I think it needs to look something like this, as the "Shield Defence" doesn't seem to have any relation to the base defence stat increasing. We're increasing levels, and "using up" experience points. So the while() loop iterates, decrementing experience points, but increasing D_Stats. This is how I understand it from the description anyway.
while self.S_Exp >= 0 + (self.S_Level * 100):
self.S_Level += 1
self.S_Exp -= ((self.S_Level - 1) * 250)
self.S_Level_Stats = [self.S_Exp, self.S_Level]
points_increase = random.randint(4, 8)
self.Increased_Defence.append(points_increase)
self.D_Stats += points_increase
EDIT2: Looking at your code, I think it would be neater, cleaner and shorter if you use data structures to hold some of the stat values. The Weapon modifier can be greatly simplified:
def Weapon(self, Weapon):
weapon_mods = { "Dagger":[6,0], "Knife":[5,0], "Sword":[7,0], "Axe":[7,-5] } #etc
if ( Weapon in weapon_mods ):
attack_mod, speed_mod = weapon_mods[ Weapon ]
self.P_Attack += attack_mod
self.P_Speed += speed_mod
self.P_Stats = [self.P_HP, self.P_Attack, self.P_Speed]
I think it's better to always add the modifier, and use negative/positive modifiers. It makes the logic simpler (and what if you want to make a weapon that increases attack speed?)

power (a, n) in PYTHON

POwer in Python. How to write code to display a ^ n using funсtion?
why doesn't this code working?
a = int(input())
n = int(input())
def power(a, n):
for i in range (n):
a=1
a *= n
print(power (a, n))
Few errors:
Changing a will lose your power parameter, use result (or something else).
Move setting result = 1 outside your loop to do so once.
Multiply by a not by n.
Use return to return a value from the function
def power(a, n):
result = 1 # 1 + 2
for _ in range (n):
result *= a # 3
return result # 4
Style notes:
Setting/mutating a parameter is considered bad practice (unless explicitly needed), even if it is immutable as is here.
If you're not going to use the loop variable, you can let the reader know by using the conventional _ to indicate it (_ is a legal variable name, but it is conventional to use it when not needing the variable).
Tip: you can simple use a**n
It doesn't work because your function doesn't return the end value. Add return a to the end of the function.
ALSO:
That is not how a to the power of n is is calculated.
A proper solution:
def power(a,n):
pow_a = a
if n is 0:
return 1
for _ in range(n-1): # Substracting 1 from the input variable n
pow_a *= a # because n==2 means a*a already.
return pow_a
and if you want to be really cool, recursion is the way:
def power_recursive(a,n):
if n is 0:
return 1
elif n is 1:
return a
else:
a *= power_recursive(a,n-1)
return a

Wrong output binary search program

In writing a BinarySearch program, I've written the program:
def binary_search(array, x, low=0, high=None):
if high is None:
high = len(array)
while low < high:
mid = (low+high)//2
midval = array[mid]
if midval < x:
low = mid+1
elif midval > x:
high = mid
else:
return mid
return -1
When I put the following:
binary_search([1,2,2,3],2)
the output given by the program is
2
However, I would like the program to give as output the index of the first integer 'x' it finds. So in the previous example it would be '1' instead of '2'. Any idea on how I can alter this?
You need to remove the early out (the final else condition), replacing the prior elif with a straight else; only when the loop terminates do you test for equality and choose to return the index found or -1 if it wasn't found:
def binary_search(array, x, low=0, high=None):
if high is None:
high = len(array)
while low < high:
mid = (low+high)//2
if array[mid] < x:
low = mid+1
else:
high = mid
return -1 if low >= len(array) or array[low] != x else low
It's also a good idea to behave this way because in general, you don't want to perform multiple comparisons per loop (< and > would each invoke comparisons, which could be expensive depending on the type); simplifying to exactly one non-numeric comparison per loop saves time (and often runs faster; Python libraries often hand implement < and ==, and use wrappers to implement the other comparators in terms of < and ==, making them slower).
This is actually what bisect.bisect_left does in its pure Python implementation; it's otherwise nearly identical to your code. It makes it take longer, because it's more likely to take the full log(n) steps to identify the left most instance of a value, but the incremental cost is usually going to be small unless your input has many repeated values.
Given [1,2,2,3] the line mid = (low+high)//2 is evaluated for the first run as:
mid = (0 + 4) // 2 ==> 2
Than array[mid] satisfies the condition (its value is exactly 2), and the index returned is mid (2).
if midval < x:
...
elif midval > x:
...
else:
return mid

Resources