Creating a new method - python

I'm working on a MOOC on Python Programming and am having a hard time finding a solution to a problem set. I hope you can provide some assistance.
The problem is:
The Fibonacci sequence is a number sequence where each number is the sum of the previous two numbers. The first two numbers are defined as 0 and 1, so the third number is
1 (0 + 1 = 1), the fourth number is 2 (1 + 1 = 2), the fifth number is 3 (1 + 2 = 3), the sixth number is 5(2 + 3 = 5), and so on.
Below we've started a class called FibSeq. At any time, FibSeq holds two values from the Fibonacci sequence: back1 and back2.
Create a new method inside FibSeq called next_number. The next_number method should:
Calculate and return the next number in the sequence,
based on the previous 2.
Update back2 with the former value of back1, and update
back1 with the new next item in the sequence.
This means that consecutive calls to next_number should yield each consecutive number from the Fibonacci sequence. Calling next_number 5 times would print 1, 2, 3, 5, and 8.
My code is below:
class FibSeq:
def __init__(self):
self.back1 = 1
self.back2 = 0
def next_number(self):
self.back1 = self.back1 + self.back2
self.back2 = self.back1 - self.back2
yield(self.back1)
f = FibSeq()
for i in range(5):
s = f.next_number()
print(next(s))
My code returns the following:
1
2
3
5
8
<generator object FibSeq.next_number at 0x7f68f6fbe678>
<generator object FibSeq.next_number at 0x7f68f6fbe678>
<generator object FibSeq.next_number at 0x7f68f6fbe678>
<generator object FibSeq.next_number at 0x7f68f6fbe678>
<generator object FibSeq.next_number at 0x7f68f6fbe678>
However, it should only return 1,2,3,5,8, after running the below code:
newFib = FibSeq()
print(newFib.next_number())
print(newFib.next_number())
print(newFib.next_number())
print(newFib.next_number())
print(newFib.next_number())
Why does my code return the last 5 "error" statements like this <generator object FibSeq.next_number at 0x7f68f6fbe678>?
Thank you.

Although I agree that a generator is a much more pythonic solution to this problem, I don't believe the assignment wants you to write a generator. What it says is:
This means that consecutive calls to next_number should yield each consecutive number from the Fibonacci sequence.
Their use of the word "yield" (rather than the more natural "return") is confusing, to be sure, but "return" is surely what they meant because each call to a generator function returns an iterator, not a value, and you need to call next on the returned iterator to get successive values.
Generators and class instances are two different ways of solving the same problem, which is how to retain state while producing successive values of a sequence. In the object-oriented solution, you create an object by calling the class' constructor, and then you repeatedly call an instance method (such as next_number. That would lead to a solution like this:
class FibSeq:
def __init__(self):
self.back1, self.back2 = 1, 0
def next_number(self):
self.back1, self.back2 = self.back1 + self.back2, self.back1
return self.back1
# Example usage:
fibber1 = FibSeq()
for i in range(1, 6):
print(i, fibber1.next_number())
That prints
1 1
2 2
3 3
4 5
5 8
(Note that Fib(0) is also 1. To my mind, a Fibonacci generator should start at 0, but to conform to the expectation that the first five calls to next_number, I started the counter at 1.)
If we want another sequence starting at the beginning, we just create another instance of the class. We can then use both instances independently. Here I print out the next five values produced by the first object, whose next value produced will be Fib(6), and in another column, I output the first five values produced by a new object:
fibber2 = FibSeq()
for i in range(6, 11):
print(i, fibber2.next_number(), fibber1.next_number())
That prints
6 1 13
7 2 21
8 3 34
9 5 55
10 8 89
And it's easy to see that the two instances of FibSeq are each using their own state (that is, the members back1 and back2).
But that's not very Pythonic (in my opinion). In Python, we can create a generator function to do the same thing; the generator's state is now contained in local variables, not so easily available for inspection. Calling the generator function returns an iterator --a different one each time, with its own local variables-- and the iterators can be used in a for statement or a comprehension. Generators are distinguished from ordinary functions by the fact that they include at least one yield statement. They normally don't include a return statement, or if they do, it doesn't return anything. This has nothing to do with the return value of calling the generator; as I said, calling the generator returns an iterator, but that's done automatically as soon as you call the generator, before any statement has been executed.
So here's the generator version of the Fibonacci sequence:
def FibGen():
back1, back2 = 1, 0
while True:
back1, back2 = back1 + back2, back1
yield back1
Now, we can use a simple for loop, but with a bit of caution because the iterator produced by FibGen never stops. To specify the desired number of values generated, I can zip it with a range iterator:
for i, f in zip(range(1, 6), FibGen()):
print(i, f)
# Output:
1 1
2 2
3 3
4 5
5 8
This works because zip stops iterating as soon as one of its argument iterators is done.
In that for loop, I created the iterator without saving it. But I might want to save it, in order to do something like the side-by-side display from the O-O example. That works in a very similar fashion:
# Create one iterator and print the first five values
fibber1 = FibGen()
for i, f in zip(range(1, 6), fibber1):
print(i, f)
# Now create another, independent iterator:
fibber2 = FibGen()
for i, f1, f2 in zip(range(6, 11), fibber1, fibber2):
print(i, f1, f2)
The output from the two loops:
1 1
2 2
3 3
4 5
5 8
6 1 13
7 2 21
8 3 34
9 5 55
10 8 89
I can also use the generator to fill in a list comprehension, although again I need to be careful to stop the generation, since I have enough memory to store an infinite list. Again, zipping with a range is a simple way to control the generation count:
v = [f for i,f in zip(range(10), FibGen())]
print(v)
# Output:
[1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
Collecting generated values is obviously simpler for iterators which only generate a finite number of values. But on the whole, you should avoid collecting generated values. It's surprising how rarely you actually need to save the list.

Related

Augmented Assignment

I am new to the world of Python and programming in general, and today I have faced a problem with augmented assignment. I unfortunately do not understand the code, and what for i in range(multiplier) and answer *= number does. I tried understanding it but I still don't really get the logic behind it. Can somebody please explain?
number = 5
multiplier = 8
answer = 0
for i in range(multiplier):
answer *= number
print(answer)
range([start], stop[, step])
range is a function that takes a number and return a list of number from 0 ... right through to the number you gave it as an argument.
BUT THE KEY TO NOTICE IS THAT IT WILL NEVER INCLUDE THE NUMBER YOU
TOLD IT TO COUNT TO
. Example :
This is an example of giving the function range 1 argument:
>>> # One parameter
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
Here is an example of giving it two arguments where the first argument is telling the function what to start the list it returns at. The second argument is where it should end:
>>> # Two parameters
>>> for i in range(3, 6):
... print(i)
...
3
4
5
Here is an even cooler example where we use the third argument as well. This argument tells the function to count from whatever number you told it to start at, right through to whatever number you told it to stop at (just like the above to examples)... only now, the third argument tells it in what steps to count:
Like count from 2 to 12 but count in 2's:
>>> # Three parameters
>>> for i in range(2, 12, 2):
... print(i)
...
2
4
6
8
10
SO....
the for loop is just iterating through that list of numbers that is given back by the function range
so lets break that for loop in to pseudo code.
***loop***
for i in range(multiplier):
answer *= number
***Pseudo code***
Give me a list of numbers
(not giving it a start value but only a value to end the count at).
The numbers has to be from 0 to multiplier (8).
so range(multiplier) -> range(8) -> [0, 1, 2, 3, 4, 5, 6, 7]
now you have a list
now you ask the compiler to go through that list.
you say : go through that list and everytime you at a new number, give it to me in the for of a variable called i.
then when you have i, you don't use it because i was just trying to loop 8 (multiplier) times... but now take the answer and add to it this (answer * number) ... this will happen 8 times because you loop 8 times

Variable references the wrong iterator, even after confirmed reassignment

I have the following loop that reassigns to an iterator variable:
currentPrime = None
sieve = iter(range(2, 10))
while True:
try:
# The first value should be prime.
currentPrime = next(sieve)
except StopIteration:
# Stop when sieve is empty.
print(currentPrime)
break
print(currentPrime)
# Filter out all multiples of currentPrime.
sieve = (x for x in sieve if x % currentPrime)
#print(tuple(sieve))
Even though I apply a filter on each iteration of the loop, the output goes through the entire range:
2
3
4
5
6
7
8
9
9
If I uncomment the last print call, I see (3, 5, 7, 9), which means the filter and assignment to sieve work correctly, but the next(sieve) call somehow accesses the original iterator which no variable points to.
Any idea what's happening here? I'm using Python 3.7.0.
As user2357112 said, "currentPrime is looked up on use, not on generator creation."
One solution is to use filter with a lambda that localizes the current value of currentPrime. Note how the lambda uses a default argument to create a local variable:
currentPrime = None
sieve = iter(range(2, 10))
while True:
try:
# The first value should be prime.
currentPrime = next(sieve)
except StopIteration:
# Stop when sieve is empty.
print(currentPrime)
break
# Filter out all multiples of currentPrime.
sieve = filter(lambda x, prime=currentPrime: x % prime, sieve)
There is a stack of generators if I'm not wrong.
What happens (TL;DR: all generators refer to one currentPrime instance and refer lazy):
Get value 2 from range iterator.
Create generator expression (A). Variable currentPrime = 2 now and is NOT free (not in closure). Generator consume rest of range (3..9).
Get value 3 from generator A (3 % 2 is True)
Create generator expression (B). Variable currentPrime = 3 for both generators (A, B). Generator consume rest of generator A (4..9).
Get value 4 from generator B. See: next() → B.next(A) → A yields 4 (A checks: 4 % 3 is True), then B checks 4 % 3 is True.
Create generator expression (C) ... and so on.
Variable in in generator is not in closure, see:
>>> a = 5
>>> gen = (a for i in range(3))
>>> a = 3
>>> list(gen)
[3, 3, 3]

Python - Random sample from a range whilst avoiding certain values

I have been reading up about the random.sample() function in the random module and have not seen anything that solves my problem.
I know that using random.sample(range(1,100),5) would give me 5 unique samples from the 'population'...
I would like to get a random number in range(0,999). I could use random.sample(range(0,999),1) but why then am I thinking about using random.sample() ?
I need the random number in that range to not match any number in a separate array (Say, [443,122,738])
Is there a relatively easy way I could go about doing this?
Also, I am pretty new to python and am definitely a beginner -- If you would like me to update the question with any information I may have missed then I will.
EDIT:
Accidentally said random.range() once. Whoops.
One way you can accomplish that is by simply checking the number and then appending it to a list where you can then use the numbers.
import random
non_match = [443, 122, 738]
match = []
while len(match) < 6: # Where 6 can be replaced with how many numbers you want minus 1
x = random.sample(range(0,999),1)
if x not in non_match:
match.append(x)
There are two main ways:
import random
def method1(lower, upper, exclude):
choices = set(range(lower, upper + 1)) - set(exclude)
return random.choice(list(choices))
def method2(lower, upper, exclude):
exclude = set(exclude)
while True:
val = random.randint(lower, upper)
if val not in exclude:
return val
Example usage:
for method in method1, method2:
for i in range(10):
print(method(1, 5, [2, 4]))
print('----')
Output:
1
1
5
3
1
1
3
5
5
1
----
5
3
5
1
5
3
5
3
1
3
----
The first is better for a smaller range or a larger list exclude (so the choices list won't be too big), the second is better for the opposite (so it doesn't loop too many times looking for an appropriate option).

how to increment the iterator from inside for loop in python 3?

for i in range (0, 81):
output = send command
while True:
last_byte = last_byte - offset
if last_byte > offset:
output = send command
i+
else:
output = send command
i+
break
I want to increase the iterator every time the send command is executed. Right now it only increases by one when the for loop is executed. Please advise
for i in range(0,10):
print(i)
i +=2
print("increased i", i)
I ran this code and it produced out from 0 to 9. I was expecting it would increase the iterator by 2.
Save a copy of the iterator as a named object. Then you can skip ahead if you want to.
>>> myiter = iter(range(0, 10))
>>> for i in myiter:
print(i)
next(myiter, None)
...
0
2
4
6
8
You can't do this inside a for loop, because every time the loop is restarted it reassigns the variable i regardless of your changes on the variable.
To be able to manipulate your loop counting variable, a good way is to use a while loop and increase the throwaway variable manually.
>>> i = 0
>>> while i < 10 :
... print(i)
... i += 2
... print("increased i", i)
...
0
('increased i', 2)
2
('increased i', 4)
4
...
Additionally, if you want to increase the variable on a period rather than based on some particular condition, you can use a proper slicers to slice the iterable on which you're looping over. For instance, if you have an iterator you can use itertools.islice() if you have a list you can simply use steps while indexing (my_list[start:end:step]).
range() has an optional third parameter to specify the step. Use that to increment the counter by two. For example:
for i in range(0, 10, 2):
print(i)
print("increased i", i)
The reason that you cannot increment i like a normal variable is because when the for-loop starts to execute, a list (or a range object in Python 3+) is created, and i merely represents each value in that object incrementally.
#ilaunchpad Sorry I know it's too late to post this but here is what you were looking for
i=0
for J in range(0,10):
print(i)
i = i + 2
print("increased i", i)
You should not use the same variable throughout in the For statement.
Output
vaibhav#vaibhav-Lenovo-G570:~$ python3 /home/vaibhav/Desktop/Python/pattern_test.py
0
2
4
6
8
10
12
14
16
18
increased i 20
How about this?
for i in range(10):
if i == 3:
i += 1
continue
print(i)
Just adding the continue makes the counter go up - the print result is:
0
1
2
4
5
6
7
8
9
Note that without the continue, the 4 will be printed twice.
I think this answers the question.
I wrote something like this.
while True:
if l < 5:
print "l in while", l
l += 1
else:
break
Now I am having a full control but the backdrop is that I have to check all the conditions.
As there are no answers that allow to use the result of the next iterator, here is my proposed solution, that works with many lists and iterables by using either the enumerate() function or just the iter() function:
x = [1, True, 3, '4', 5.05, 6, 7, 8, 9, 10]
x_enum = enumerate(x)
for idx, elem in x_enum:
if idx == 1: # Example condition to catch the element, where we want to iterate manually
print('$: {}'.format(elem))
idx, elem = next(x_enum)
print('$: {}'.format(elem))
else:
print(elem)
will print:
1
$: True # As you can see, we are in the if block
$: 3 # This is the second print statement, which uses the result of next()
4
5.05
6
7
8
9
10
This is also possible with a simple iterator:
x_iter = iter(x)
for elem in x_iter:
if elem == '4': # Example condition to catch the element, where we want to iterate manually
print('$: {}'.format(elem))
elem = next(x_iter)
print('$: {}'.format(elem))
else:
print(elem)

How to change for-loop iterator variable in the loop in Python?

I want to know if is it possible to change the value of the iterator in its for-loop?
For example I want to write a program to calculate prime factor of a number in the below way :
def primeFactors(number):
for i in range(2,number+1):
if (number%i==0)
print(i,end=',')
number=number/i
i=i-1 #to check that factor again!
My question : Is it possible to change the last two line in a way that when I change i and number in the if block, their value change in the for loop!
Update: Defining the iterator as a global variable, could help me? Why?
Short answer (like Daniel Roseman's): No
Long answer: No, but this does what you want:
def redo_range(start, end):
while start < end:
start += 1
redo = (yield start)
if redo:
start -= 2
redone_5 = False
r = redo_range(2, 10)
for i in r:
print(i)
if i == 5 and not redone_5:
r.send(True)
redone_5 = True
Output:
3
4
5
5
6
7
8
9
10
As you can see, 5 gets repeated. It used a generator function which allows the last value of the index variable to be repeated. There are simpler methods (while loops, list of values to check, etc.) but this one matches you code the closest.
No.
Python's for loop is like other languages' foreach loops. Your i variable is not a counter, it is the value of each element in a list, in this case the list of numbers between 2 and number+1. Even if you changed the value, that would not change what was the next element in that list.
The standard way of dealing with this is to completely exhaust the divisions by i in the body of the for loop itself:
def primeFactors(number):
for i in range(2,number+1):
while number % i == 0:
print(i, end=',')
number /= i
It's slightly more efficient to do the division and remainder in one step:
def primeFactors(number):
for i in range(2, number+1):
while True:
q, r = divmod(number, i)
if r != 0:
break
print(i, end=',')
number = q
The only way to change the next value yielded is to somehow tell the iterable what the next value to yield should be. With a lot of standard iterables, this isn't possible. however, you can do it with a specially coded generator:
def crazy_iter(iterable):
iterable = iter(iterable)
for item in iterable:
sent = yield item
if sent is not None:
yield None # Return value of `iterable.send(...)`
yield sent
num = 10
iterable = crazy_iter(range(2, 11))
for i in iterable:
if not num%i:
print i
num /= i
if i > 2:
iterable.send(i-1)
I would definitely not argue that this is easier to read than the equivalent while loop, but it does demonstrate sending stuff to a generator which may gain your team points at your next local programming trivia night.
It is not possible the way you are doing it. The for loop variable can be changed inside each loop iteration, like this:
for a in range (1, 6):
print a
a = a + 1
print a
print
The resulting output is:
1
2
2
3
3
4
4
5
5
6
It does get modified inside each for loop iteration.
The reason for the behavior displayed by Python's for loop is that, at the beginning of each iteration, the for loop variable is assinged the next unused value from the specified iterator. Therefore, whatever changes you make to the for loop variable get effectively destroyed at the beginning of each iteration.
To achieve what I think you may be needing, you should probably use a while loop, providing your own counter variable, your own increment code and any special case modifications for it you may need inside your loop. Example:
a = 1
while a <= 5:
print a
if a == 3:
a = a + 1
a = a + 1
print a
print
The resulting output is:
1
2
2
3
3
5
5
6
Yes, we can only if we dont change the reference of the object that we are using. If we can edit the number by accessing the reference of number variable, then what you asked is possible.
A simple example:
a=[1,2,3]
a=a+[4]==>here, a new object is created which plots to different address.
a+=[4]==>here , the same object is getting updated which give us the desired result.
number=10
list1=list(range(2,number+1))
# list1
for i in list1:
print(list1,i)
if (number%i==0):
print(i,end=',')
number=number//i #we can simply replace it with number//=i to edit the number without changing the reference or without creating a new object.
try:
[list1.pop() for i in range(10,0,-1) if(i>number)]
#here pop() method is working on the same object which list created by number refers. so, we can able to change the iterable in the forloop.
except:
continue
i=i-1 #to check that factor again!

Categories