set size is changing even though it shouldn't python - python

class Test:
TheFlag = True
StartNodeQuery = {1, 2, 3, 4, 5}
def parsa(self):
while self.TheFlag:
SNQ = self.StartNodeQuery
self.iterator(SNQ)
def iterator(self, CurrentNodeQuery):
#it prints {1, 2, 3, 4, 5}
print(CurrentNodeQuery)
if len(CurrentNodeQuery) < 100:
b = len(CurrentNodeQuery) * 2
c = len(CurrentNodeQuery) * 3
self.StartNodeQuery.update({b, c})
# it prints {1, 2, 3, 4, 5, 10, 15}
print(CurrentNodeQuery)
else:
self.TheFlag = False
assert 0
obj = Test()
obj.parsa()
as you can see I deliberately ended the program with assert 0.
The Main issue is:
Before the function is finished the parameters that is passed to it gets changed!
as you can see
StartNodeQuery = {1, 2, 3, 4, 5} and
SNQ = self.StartNodeQuery
so why when I change the size of the self.StartNodeQuery inside the function before it's finished , CurrentNodeQuery which is a different variable with the same values as self.StartNodeQuery (or SNQ) gets changed as well, even though we didn't pass the new self.StartNodeQuery to CurrentNodeQuery yet?
I hope you understand my problem, if you have the solution, please help a guy out

Some issues and suggestions in your code
Don't mix class and instance variables. You are using class variables TheFlag and StartNodeQuery as instance variables, so make them one
Use a constructor to instantiate instance variables
Perhaps use exit() to break the function
You need to explicitly copy the set to make a new instance via copy.copy. Assignment SNQ = self.StartNodeQuery just makes a new reference to the variable
So the fixed code might look like
from copy import copy
class Test:
def __init__(self):
# Made instance variables as class variables
self.TheFlag = True
self.StartNodeQuery = {1, 2, 3, 4, 5}
def parsa(self):
while self.TheFlag:
SNQ = self.StartNodeQuery.copy()
self.iterator(SNQ)
def iterator(self, CurrentNodeQuery):
#it prints {1, 2, 3, 4, 5}
print(CurrentNodeQuery)
if len(CurrentNodeQuery) < 100:
b = len(CurrentNodeQuery) * 2
c = len(CurrentNodeQuery) * 3
self.StartNodeQuery.update({b, c})
# it prints {1, 2, 3, 4, 5, 10, 15}
print(CurrentNodeQuery)
else:
self.TheFlag = False
#Use exit to break the function
exit()
obj = Test()
obj.parsa()
And the output will be
{1, 2, 3, 4, 5}
{1, 2, 3, 4, 5}

Related

Updating key values in dictionaries

I am trying to write code for the following:
The idea is to have a storage/inventory dictionary and then have the key values be reduced by certain household tasks. E.g. cleaning, cooking etc.
This would be the storage dictionary:
cupboard= {"cookies":30,
"coffee":3,
"washingpowder": 5,
"cleaningspray": 5,
'Pasta': 0.5,
'Tomato': 4,
'Beef': 2,
'Potato': 2,
'Flour': 0.2,
'Milk': 1,
"Burger buns": 6}
now this is the code that I wrote to try and reduce one single key's value (idea is that the action "cleaning" reduces the key "cleaning spray" by one cleaning unit = 0.5
cleaning_amount = 0.5
def cleaning(room):
while cupboard["cleaningspray"] <0.5:
cleaned = {key: cupboard.get(key) - cleaning_amount for key in cupboard}
return cupboard
livingroom = 1*cleaning_amount
cleaning(livingroom)
print(cupboard)
but it returns this, which is the same dictionary as before, with no updated values
{'cookies': 30, 'coffee': 3, 'washingpowder': 5, 'cleaningspray': 5, 'Pasta': 0.5, 'Tomato': 4, 'Beef': 2, 'Potato': 2, 'Flour': 0.2, 'Milk': 1, 'Burger buns': 6}
Can anybody help?
Thank you!!
picture attached to see indents etc.
I guess you want to decrease the "cleaningspray" amount depending on the room size (or other factors). I would do it like this:
cleaning_amount = 0.5
def cleaning(cleaning_factor):
if cupboard["cleaningspray"] > 0.5:
# reduce the amount of cleaning spray depending on the cleaning_factor and the global cleaning_amount
cupboard["cleaningspray"] -= cleaning_factor * cleaning_amount
livingroom_cleaning_factor = 1
cleaning(livingroom_cleaning_factor)
print(cupboard)
Output:
{'cookies': 30, 'coffee': 3, 'washingpowder': 5, 'cleaningspray': 4.5, 'Pasta': 0.5, 'Tomato': 4, 'Beef': 2, 'Potato': 2, 'Flour': 0.2, 'Milk': 1, 'Burger buns': 6}
So I believe the reason that the values don't change is because it is being done in a for loop.
e.g.
list_values = [1, 2, 3, 4, 5]
new_variable = [num + 1 for num in list_values]
print("list_values", list_values) # The original list_values variable doesn't change
print("new_variable", new_variable) # This new variable holds the required value
This returns:
list_values [1, 2, 3, 4, 5]
new_variable [2, 3, 4, 5, 6]
So to fix the problem, you can use the 'new_variable'
So, now that the concept is clear (I hope), in your case it would be something like this
def cleaning():
while cupboard["cleaningspray"] > 0.5: # Also here, i beleive you intend to have `>`
#and not `<` in the original code
cleaned = {key: cupboard.get(key) - cleaning_amount for key in cupboard}
return cleaned
We return the 'new_variable' that is cleaned
so it can be assigned to the original dictionary variable as follows if required:
cupboard = cleaning()
EDIT:
Also, as #d-k-bo commented, if you intend to only have the operation carry out once... an if statement would also do the job
if cupboard["cleaningspray"] > 0.5: # Again assuming you intended '>' and not '<'
Otherwise, you should keep the return statement outside the while loop

Inserting a set of elements alternatively

There is a list with elements of similar nature (4 7's,3 5's, etc.) that I want to insert in right left order into a another list ().
newlst = []
lst = [7, 7, 7, 7, 5, 5, 5, 3, 3, 3, 2, 2]
So the first thing being inserted into newlst is the group of 7's:
newlst = [7,7,7,7]
Subsequently, the group of 5's is inserted into the list on the right:
newlst = [7, 7, 7, 7, 5, 5, 5]
And then the group of 3's is inserted on the left, and after that the group of 2's is inserted on the right. The final list looks like this
newlst = [3, 3, 3, 7, 7, 7, 7, 5, 5, 5, 2, 2]
In order to add elements in the list on a right left basis, I did this:
for i in lst:
lst.insert(0,i)
else:
lst.append(i)
The insert method inserts elements into the 0 index (which is the right of the list) and append adds elements at the end of the list (which is the left of the list). However, I'm having problems adding the group of elements into the newlst. To that end, I thought using a dictionary would be a good idea.
myDict = {2: 2, 3: 3, 5: 3, 7: 4}
EDIT:
for k, v in myDict.items():
if k in lst:
for i in range(v):
lst.append(i)
else:
lst.insert(0,i)
The intention of this dictionary is for each key, I want to insert the key value 'x' times, e.g. the key 7, would be inserted 4 times: [7,7,7,7]. Is there a way to achieve this in Python so I can get the output newlist: [3, 3, 3, 7, 7, 7, 7, 5, 5, 5, 2, 2] ?
You can accomplish this pretty easily with a deque, along with cycle and groupby
from collections import deque
from itertools import groupby, cycle
#creates a deque object
d = deque()
#creates a repeating iterator to alternate function calls
c = cycle([d.extendleft, d.extend])
lst = [7, 7, 7, 7, 5, 5, 5, 3, 3, 3, 2, 2]
for _, items in groupby(lst):
#calls the alternated function to extend the items
next(c)(items)
print(list(d))
>>> [3, 3, 3, 7, 7, 7, 7, 5, 5, 5, 2, 2]
Here is your initial code:
newlst = []
lst = [7, 7, 7, 7, 5, 5, 5, 3, 3, 3, 2, 2]
myDict = {2: 2, 3: 3, 5: 3, 7: 4}
for k, v in myDict.items():
if k in lst:
for i in range(v):
lst.append(i)
else:
lst.insert(0,i)
You have a few major problems here:
k is always in lst, by definition. That means your check is not a valid way to alternate.
Your data is getting appended/prepended to lst instead of newlst.
A dict is a hash-table. This means that the order of the keys will pretty much never be in the order you defined them in.
The first item can be solved through enumeration:
for i, (k, v) in enumerate(myDict.items()):
if i % 2:
newlst = [k] * v + newlst
else:
newlst += [k] * v
I've fixed the list you are appending to, and am using [k] * v to construct the prepended/appended list. newlst += [k] * v is equivalent to newlst.extend([k] * v). However, keep in mind that newlst = [k] * v + newlst creates a new list object rather than concatenating in-place.
The third item can be fixed using OrderedDict instead of a regular dict:
from collections import OrderedDict
...
myDict = OrderedDict([(2, 2), (3, 3), (5, 3), (7, 4)])
That will make the keys run in the order that you want. In fact, you don't need to construct myDict by hand at all. You can combine OrderedDict with a Counter to get the exact same result dynamically. The recipe for this is given in the OrderedDict docs:
from collections import Counter, OrderedDict
...
class OrderedCounter(Counter, OrderedDict):
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))
def __reduce__(self):
return self.__class__, (OrderedDict(self),)
myDict = OrderedCounter(lst)
All this is pretty verbose and not very efficient. As #Wondercricket's answer points out, you can use the functions in itertools to perform the same task using generators.
This is what you want to do?
list = [7, 7, 7, 7, 5, 5, 5, 3, 3, 3, 2, 2]
def list_to_answerlist(list, default=[]):
if not list:
return default
else:
result = []
direction = True # Insert Direction: True = Insert Left / False = Insert Right
actual_group = list[0]
for element in list:
if (element != actual_group):
direction = not direction # Change Insert Direction
actual_group = element # Update Actual Group
if direction: # Insert Left
result.insert(0,element)
else: # Insert Right
result.append(element)
return result
new_list = list_to_answerlist(list) # output = [3, 3, 3, 7, 7, 7, 7, 5, 5, 5, 2, 2]

How do you change a specific element of a set?

In this code, I am trying to compare the value of a set that has been looped each time to a value passed (in this case a) in a parameter. What's interesting though is that it shows when I use a for each loop that each element is an integer. How do I get a integer to integer comparison without a console error?
def remove(s,a,b):
c=set()
c=s
for element in c:
element=int(element)
if(element<a or element>b):
c.discard(element)
return c
def main():
remove({3, 17, -1, 4, 9, 2, 14}, 1, 10)
main()
Output:
if(element<=a or element>=b):
TypeError: '>=' not supported between instances of 'int' and 'set'
You reassign your local variable b:
def remove(s,a,b):
b=set() # now b is no longer the b you pass in, but an empty set
b=s # now it is the set s that you passed as an argument
# ...
if(... element > b): # so the comparison must fail: int > set ??
Short implementation using a set comprehension:
def remove(s, a, b):
return {x for x in s if a <= x <= b}
>>> remove({3, 17, -1, 4, 9, 2, 14}, 1, 10)
{9, 2, 3, 4}
if you want int to int compare then make b as list of s.
def remove(s,a,b):
b = list(s)
for element in s:
element=int(element)
if(element< a or element > b):
b.remove(element)
return b
def main():
remove({3, 17, -1, 4, 9, 2, 14}, 1, 10)
main()
Come on, why don't we make the code shorter?
Try this:
def remove(s, a, b):
return s.difference(filter(lambda x: not int(a) < int(x) < int(b), s))
def main():
new_set = remove({3, 17, -1, 4, 9, 2, 14}, 1, 10)
# {2, 3, 4, 9}
print(new_set)
main()

local variable optimization in python method

In the following code, I would like to know if the grade_to_score dictionary will be created every time the method is called?
def get_score(grade):
grade_to_score = {'A': 10, 'B': 8, 'C': 6, 'D': 4, 'F': 0} # step 1
return grade_to_score.get(grade, -1)
also, what is the way to confirm that? I am working with Python 2.7
Yes it will. To get around it, you can pass it as a default argument so that it will only be evaluated once:
def get_score(grade, grade_to_score = {'A': 10, 'B': 8, 'C': 6, 'D': 4, 'F': 0}):
return grade_to_score.get(grade, -1)
or the better approach:
def get_score(grade, grade_to_score = None):
if grade_to_score == None:
grade_to_score = {'A': 10, 'B': 8, 'C': 6, 'D': 4, 'F': 0}
return grade_to_score.get(grade, -1)
To answer your question "what is the way to confirm that?", you can check whether the same object is being used each time:
def get_score(grade):
grade_to_score = {'A': 10, 'B': 8, 'C': 6, 'D': 4, 'F': 0} # step 1
print(id(grade_to_score)) # check object ID
return grade_to_score.get(grade, -1)
Now you can call it:
>>> a = get_score("")
50252080
>>> b = get_score("")
50249920
>>> c = get_score("")
50249776
A different id means a different object, so grade_to_score clearly is being created anew on each call. Interestingly, this doesn't happen if you call in a for loop:
>>> for _ in range(3):
a = get_score("")
50249920
50249920
50249920
>>> scores = [get_score(grade) for grade in "ABC"]
53737032
53737032
53737032
Yes, the dictionary is created a-new every time the function is called.
You can make it a global instead, or make it a function default:
grade_to_score = {'A': 10, 'B': 8, 'C': 6, 'D': 4, 'F': 0} # step 1
def get_score(grade):
return grade_to_score.get(grade, -1)
or
def get_score(grade, grade_to_score={'A': 10, 'B': 8, 'C': 6, 'D': 4, 'F': 0}):
return grade_to_score.get(grade, -1)
In the second casegrade_to_score is passed into the function as a local, so lookups are (marginally) faster.
In both cases the dictionary literal is executed once, on module import. Note that in both cases, because grade_to_score is a mutable dictionary, so any changes you make to it are global, not local to the get_score() call.

What do * and ** before a variable name mean in a function signature? [duplicate]

This question already has answers here:
What does ** (double star/asterisk) and * (star/asterisk) do for parameters?
(25 answers)
Closed 1 year ago.
What do the * and ** mean in this code?
def functionA(self, *a, **kw):
# code here
Inside a function header:
* collects all the positional arguments in a tuple.
** collects all the keyword arguments in a dictionary.
>>> def functionA(*a, **kw):
print(a)
print(kw)
>>> functionA(1, 2, 3, 4, 5, 6, a=2, b=3, c=5)
(1, 2, 3, 4, 5, 6)
{'a': 2, 'c': 5, 'b': 3}
In a function call:
* unpacks a list or tuple into position arguments.
** unpacks a dictionary into keyword arguments.
>>> lis=[1, 2, 3, 4]
>>> dic={'a': 10, 'b':20}
>>> functionA(*lis, **dic) #it is similar to functionA(1, 2, 3, 4, a=10, b=20)
(1, 2, 3, 4)
{'a': 10, 'b': 20}
** takes specified argument names and puts them into a dictionary. So:
def func(**stuff):
print(stuff)
func(one = 1, two = 2)
Would print:
{'one': 1, 'two': 2}
** means named arguments of the functions.
$ cat 2.py
def k(**argv):
print argv
k(a=10, b = 20)
$ python 2.py
{'a': 10, 'b': 20}
argv is a dictionary that contains all named arguments of the function.
And you can also reverse it. You can use a dictionary as a set of aruments
for a function:
def k(a=10, b=20):
print a
print b
d={'a':30,'b':40}
k(**d)
would print
30
40

Categories

Resources