Using a condition with `__next__` on a Python class - python-3.x

Is it possible to implement a condition in a class iterator with the __next__ method? In a case like the one below, if the condition is not met the iterator returns None, but I'd like to omit these and only receive the actual values. For example:
class Foo2:
def __init__(self, _min=1, _max=5):
self._max = _max
self._min = _min
self.it = -1
def __iter__(self):
return self
def __next__(self):
self.it += 1
if self.it == self._max:
raise StopIteration
if self.it > self._min:
return self.it
fu = Foo2(_min=2, _max=5)
for x in fu:
# if x: -> can this be controlled by the __next__ method in the class?
print(x)
# prints
None
None
None
3
4
I'd like to only print the actual values 3 and 4 but instead of testing for None in the loop, would be nicer to have the class only emit those. Is it possible?

I don't know if this is the most correct approach, as there may be problems/drawbacks that I may be overlooking (would actually appreciate if someone could point these out to me), but for this particular case, if the interest is to only ignore values based on some condition, calling next recursively seems to do the trick:
def __next__(self):
self.it += 1
if self.it == self._max:
raise StopIteration
elif self.it > self._min:
return self.it
else:
return self.__next__()
Works in a for loop and by calling next directly:
fu = Foo2(_min=2, _max=5)
for x in fu:
print(x)
# prints
3
4
fu = Foo2(_min=2, _max=5)
print(next(fu)) # 3
print(next(fu)) # 4
print(next(fu)) # raise StopIteration

Related

Decorator to get even numbers from Fibonacci

I got a task on courses: Create Fibonacci generator function and create a decorator to leave only even numbers in this sequence. I tried but had no success. Lower I add a code which I try to create.
def decorator(func):
def inner(*args, **kwargs):
print("start decorator")
value = func(*args, **kwargs)
if a%2==0:
print(a)
return value
print("finish decorator")
return inner
#decorator
def fib(n):
a, b = 0, 1
for __ in range(n):
# if a%2==0:
yield a
a, b = b, a + b
print(list(fib(10)))
Please, help to find a way to solve it.

Is there a Python language feature that enables a function to be called with different (but known) parameter combinations?

Consider the following functions:
def function_a(self, index):
# ..do something with index and return the result
return index * index
def function_b(self, group, arg):
# ..do something with group and arg to derive an index and call function_a
index = group + arg
return function_a(index)
def function_c(self, group, arg_mult):
# ..do something with group and arg_mult to derive an index and call function_a
index = group * arg_mult
return function_a(index)
Assume there are multiple functions in this module that take an index as parameter. Each of those functions should be callable with other parameter combinations which are then converted into an index with which the original function is called. The allowed combinations of parameters are the same for each function (in the example above: group and arg or group and arg_mult). I wrote a decorator for that called convert_parameters and decorated function_a with it. function_b and function_c can then be omitted.
import functools
def convert_parameters(func):
#functools.wraps(func)
def wrapper(self, *args, arg_index=None, group=None, arg=None, arg_mult=None, **kwargs):
index = None
if arg_index is not None:
index = arg_index
elif group is not None:
if arg is not None:
index = group + arg
elif arg_mult is not None:
index = group * arg_mult
if index is None:
raise ValueError("invalid parameter combination provided.")
return func(self, *args, index=index, **kwargs)
return wrapper
#convert_parameters
def function_a(self, index):
# ..do something with index and return the result
return index * index
However, I am not sure if this is a very elegant solution, since the caller must know which argument combinations are valid for function_a (or any other function decorated with convert_parameters). This would basically require a look into the code. I would rather have it that the valid signatures for those functions are known to the caller, much like emulating the behavior of #overload but with my custom decorator.
Actually you dont need use decorator to call with different (but known) parameter combinations.
code:
def caller(index,**kwargs):
if not kwargs:
return func_a(index)
elif "group" in kwargs and "arg" in kwargs:
return func_a(kwargs["group"] + kwargs["arg"])
elif "group" in kwargs and "arg_mult" in kwargs:
return func_a(kwargs["group"] * kwargs["arg_mult"])
else:
raise ValueError("invalid parameter combination provided.")
def func_a(index):
return index * index
print(caller(5))
print(caller(5, group = 10, arg = 15))
print(caller(5, group = 3, arg_mult = 4))
try:
print(caller(5, group = 5, arg_mul = 4))
except ValueError as e:
print(e)
try:
print(caller(5, grou = 3, arg_mul = 4))
except ValueError as e:
print(e)
result:
25
625
144
invalid parameter combination provided.
invalid parameter combination provided.
If you must use decorator,basically same code:
def convert_parameters(func):
def wrapper(index, **kwargs):
if not kwargs:
return func(index)
elif "group" in kwargs and "arg" in kwargs:
return func(kwargs["group"] + kwargs["arg"])
elif "group" in kwargs and "arg_mult" in kwargs:
return func(kwargs["group"] * kwargs["arg_mult"])
else:
raise ValueError("invalid parameter combination provided.")
return wrapper
#convert_parameters
def function_a(index):
return index * index
print(function_a(5))
print(function_a(5, group = 10, arg = 15))
print(function_a(5, group = 3, arg_mult = 4))
try:
print(function_a(5, group = 5, arg_mul = 4))
except ValueError as e:
print(e)
try:
print(function_a(5, grou = 3, arg_mul = 4))
except ValueError as e:
print(e)
result:
25
625
144
invalid parameter combination provided.
invalid parameter combination provided.

Number of calls of recursive function with python decorators

I have the following code chunk about functional decorators where I wanted to initialize the variable wrapper.calls to be 0 from the second time I call fib() knowing that at the first run it gives the correct answer and after, it behaves as summing previous outputs. My question is how to fix this with changing only decorator profiler, please?
Here is my code:
from functools import wraps
import time
def profiler(func):
#wraps(func)
def wrapper(*args, **kwargs):
wrapper.calls += 1
return func(*args, **kwargs)
wrapper.calls = 0
return wrapper
#profiler
def fib(n):
if n == 0:
return 0
if n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
fib(1)
print(fib.calls)
fib(2)
print(fib.calls)
Output of this program after execution:
1
4
Expected output
1
3
def profiler(func):
calls = 0
#wraps(func)
def wrapper(*args, **kwargs):
nonlocal calls
if not calls:
wrapper.calls = 0
calls += 1
result = func(*args, **kwargs)
calls -= 1
wrapper.calls += 1
return result
return wrapper
Because calls = 0 are only initialized at the fib = profiler(fib).
If you only execute
fib(2)
print(fib.calls)
it will be 3
or
for input_n in (1, 2):
fib = profiler(fib)
fib(input_n)
print(fib.calls)
Will be what you expect
1
3

How to find a value from a Binary Search Tree in python?

I want to create a interactive Binary Search Tree(BST). So I created the BST using the following code as
class BTreeNode(object):
def __init__(self, data):
self.data = data
self.rChild = None
self.lChild = None
def __str__(self):
return (self.lChild.__str__() + '<-' if self.lChild != None
else '') + self.data.__str__() + (
'->' + self.rChild.__str__() if self.rChild != None else '')
# Insert method to create nodes
def insert(self, btreeNode):
if self.data > btreeNode.data: # insert left
if self.lChild == None:
self.lChild = btreeNode
else:
self.lChild.insert(btreeNode)
else: # insert right
if self.rChild == None:
self.rChild = btreeNode
else:
self.rChild.insert(btreeNode)
# Insert method to create nodes
# findval method to compare the value with nodes
def findval(self, lkpval):
if lkpval < self.data:
if self.lChild.data is None:
return str(lkpval)+" Not Found"
return self.lChild.findval(lkpval)
elif lkpval > self.data:
if self.rChild.data is None:
return str(lkpval)+" Not Found"
return self.rChild.findval(lkpval)
else:
print(str(self.data) + ' is found')
# findval method to compare the value with nodes
def display():
btreeRoot = BTreeNode(5)
print('inserted %s:' % 5, btreeRoot)
btreeRoot.insert(BTreeNode(7))
print('inserted %s:' % 7, btreeRoot)
btreeRoot.insert(BTreeNode(3))
print('inserted %s:' % 3, btreeRoot)
btreeRoot.insert(BTreeNode(1))
print('inserted %s:' % 1, btreeRoot)
# print(btreeRoot.findval(3))
print(display())
If I execute the above code I will get the following interactive output as
inserted 5: 5
inserted 7: 5->7
inserted 3: 3<-5->7
inserted 1: 1<-3<-5->7
This is the my expected output and I got it. Also, I want to find a value from the BST. So I used the following code in the display function as
# print(btreeRoot.findval(3))
If I un comment the code I will get error. So, how can I modify my code to display whether 3 is present in the BST or not? Thanks..
The problem occurs when you perform checks if self.lChild.data is None and if self.rChild.data is None. These lines should be replaced by if self.lChild is None and if self.rChild is None respectively, as your class implies those attributes being None's, not their data attributes.
Also consider using is None instead == None. This is not a big problem in this case, but may cause troubles in other contexts. Look here for details: Python None comparison: should I use "is" or ==?

union of two default dictionaries

am trying to create a union of two default dictionaries. Here is the code for the method in my class:
def __add__(self,right):
mergedbag = copy.copy(self.bag_value)
for item in right:
if item not in mergedbag:mergedbag[item] = 0
mergedbag[item] += right[item]
return mergedbag
I create two default dictionaries:
b = Bag(['d','a','b','d','c','b','d'])
c = Bag(['d','a','b','d','c','b','d'])
the result of
print(b+c)
should be the total count of elements after the union..
Bag(a[a],b[4],c[3],d[6])
This is the error I keep getting:
Traceback (most recent call last):
File "D:\workspace33\courselib\driver.py", line 229, in driver
Command[print(b+c)]: exec(old,local,globl)
File "<string>", line 1, in <module>
File "D:\workspace33\Project2\src\bag.py", line 58, in __add__
mergedbag[item] += right[item]
TypeError: 'Bag' object is not subscriptable
Here is the new code:
class Bag:
def __init__(self, items = []):
self.bag_value = defaultdict(int)
for item in items:
self.bag_value[item] += 1
def __repr__(self):
bag_list = []
for item, count in self.bag_value.items():
bag_list.extend(list(item*count))
return 'Bag(' + str(bag_list) + ')'
def __str__(self):
return 'Bag(' + ','.join(str(item) + '[' + str(count) + ']' for item, count in self.bag_value.items()) + ')'
def __len__(self):
bag_len = 0
for value in self.bag_value:
bag_len += self.bag_value[value]
return bag_len
def unique(self):
return len(self.bag_value)
def __contains__(self, item):
return item in self.bag_value
def count(self, item):
return(self.bag_items.count(item))
def add(self, new):
self.bag_value[new] += 1
def __add__(self,right):
mergedbag = copy.copy(self.bag_value)
for item in right:
if item not in mergedbag:mergedbag[item] = 0
mergedbag[item] += right[item]
return mergedbag
def remove(self, item):
if item in self.bag_items:
del(item)
else:
raise ValueError(type_as_str(item) + ' not in bag.')
def __eq__(self, right):
if type(right) is not Bag:
raise TypeError('Cannot compare Bag with' + type_as_str(right) + '. Can only compare Bag with Bag')
else:
return (len(self) == len(right)) and (self.unique() == right.unique())
def __ne__(self, right):
return not self.__eq__(right)
def _bag_gen(self, bag_value):
for item in self.bag_value:
for count in range(self.bag_value[item]):
yield item
def __iter__(self):
return self._bag_gen(self.bag_value)
if __name__ == '__main__':
# bag = Bag(['d','a','b','d','c','b','d'])
# bag2 = Bag(['d','a','b','d','c','b','d'])
# bag3 = Bag(['d','a','b','d','c','b'])
# print(bag == bag2)
# print(bag == bag3)
# print(bag != bag2)
# print(bag != bag3)
import driver
driver.driver()
First of all, it is important to note that your Bag class is basically the same as a Counter. If you need any specific extension, just inherit from it and you are done. I would say that the following code just address all the functionality you are implementing yourself in your Bag class:
from collections import Counter
class Bag(Counter):
pass
b = Bag(['d','a','b','d','c','b','d'])
c = Bag(['d','a','b','d','c','b','d'])
print (b)
print (c)
print (b+c)
If you are doing an exercise to learn, the problem is that you are not implementing setitem and getitem methods in your Bag class to allow the [] notation. You could implement them or just access the attribute bag_value:
def __add__(self, right):
mergedbag = Bag()
mergedbag.bag_value = copy.copy(self.bag_value)
for item in right.bag_value.keys():
mergedbag.bag_value[item] += right.bag_value[item]
return mergedbag
Be sure to create and return a Bag object
The definition is
def __add__(self,other):
So you ought to merge the contents of self.items with other.items and then return the result.
Also, not sure if bag1+bag2 will work, but my pyfu is weak, you will explicitly have to iterate through them and add the counts together.
import copy
c = {'a':2,'b':1}
d = {'b':1,'c':1}
#
mergedBag = copy.copy(c) #might wanna use deepcopy?
for k in d:
if k not in mergedBag: mergedBag[k] = 0
mergedBag[k] += d[k]
print mergedBag

Resources