variable within a function seems not to be local - python-3.x

I writing a program that takes an initial sequence and performs 3 functions on it and then spits out the 3 answers but I want to keep the original variable intact so it can be reused. From other answers on the forum I have concluded that the variable within a function should be local but it appears to be acting globally.
from collections import deque
from sys import exit
initial_state = (1,2,3,4,5,6,7,8)
initial_state = deque(initial_state)
def row_exchange(t):
t.reverse()
return t
def rt_circ_shift(t):
t.appendleft(t[3])
del t[4]
t.append(t[4])
del t[4]
return t
def md_clk_rot (t):
t.insert(1,t[6])
del t[7]
t.insert(3,t[4])
del t[5]
t.insert(4,t[5])
del t[6]
return t
print(row_exchange(initial_state))
print(initial_state)
print(rt_circ_shift(initial_state))
print(md_clk_rot(initial_state))
I would expect to get:
deque([8, 7, 6, 5, 4, 3, 2, 1])
deque([1, 2, 3, 4, 5, 6, 7, 8])
deque([4, 1, 2, 3, 6, 7, 8, 5])
deque([1, 7, 2, 4, 5, 3, 6, 8])
but instead I get:
deque([8, 7, 6, 5, 4, 3, 2, 1])
deque([8, 7, 6, 5, 4, 3, 2, 1])
deque([5, 8, 7, 6, 3, 2, 1, 4])
deque([5, 1, 8, 6, 3, 7, 2, 4])
so why isn't my variable local within the function?
is there a way I can rename the output within the function so that it isn't using the same identifier initial_state?
I'm pretty new to programming so over explanation would be appreciated.

Per the docs for deque.reverse:
Reverse the elements of the deque in-place and then return None.
(my emphasis). Therefore
def row_exchange(t):
t.reverse()
return t
row_exchange(initial_state)
modifies initial_state. Note that append, appendleft and insert also modify the deque in-place.
To reverse without modifying t inplace, you could use
def row_exchange(t):
return deque(reversed(t))
In each of the functions, t is a local variable. The effect you are seeing is
not because t is somehow global -- it is not. Indeed, you would get a
NameError if you tried to reference t in the global scope.
For more on why modifying a local variable can affect a value outside the local scope, see Ned Batchelder's Facts and myths about Python names and values. In particular, look at the discussion of the function augment_twice.

Related

Repetitive sequence (optimization)

I try to solve this problem:
initial list = [0, 1, 2, 2]
You get this sequence of numbers [0, 1, 2, 2] and you need to add every time the next natural number (so 3, 4, 5, etc.) n times, where n is the element of its index. For example, the next number to add is 3, and list[3] is 2, so you append [3] 2 times. New list will be: [0, 1, 2, 2, 3, 3]. Then the index of 4 is 3, so you have to append 4 three times. The list will be [0, 1, 2, 2, 3, 3, 4, 4, 4] and so on. ([0, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10])
In order to solve this, I tried various approaches. I used recursion, but a recursive approach is very slow in this case. I tried as well the mathematical formula from OEIS (A055086) => a(n) = ceiling(2*sqrt(n+1)) - 2. The problem with the formula is that after 2 ** 20 it is too imprecise.
So, my next idea was to use memoization:
lst = [0, 1, 2, 2]
from itertools import repeat
def find(n):
global lst
print(lst[-1], n, flush = True)
if len(lst) > n:
return lst[n]
for number in range(lst[-1]+1, n+1):
lst += list(repeat(number, lst[number]))
if len(lst) > n:
return lst[n]
Now, this approach works until 2 ** 37, but after this is just timing out. The site where I try to implement my algorithm is (https://www.codewars.com/kata/5f134651bc9687000f8022c4/train/python). I don't ask for a solution, but for any hint on how to optimize my code.
I googled some similar problems and I found that in this case, I could use the total sum of the list, but is not very clear to me yet how could this help me.
Any help is welcomed!
You can answer it iteratively like so:
def find(n):
lst = [0,1,2,2]
if n < 4:
return lst[n]
to_add = 3
while n >= len(lst):
for i in range(lst[to_add]):
lst.append(to_add)
to_add += 1
return lst[n]
You could optimise for large n by breaking early in the for loop, and by keeping track of the list length separately, rather than calls to len

Python colon as value

How can a variable replace a colon?
For example, index slicing of
data[2,10] can be replaced by variables data[x,y] to get all the data after x, data[x,:].
So what would allow a function
def sliceData(data,x,y):
return data[x,y]
and instead allow for not entering a value using a colon as a default
def sliceData(data,x=:,y=:):
return data[x,y]
You can try to use eval function for your purpose of job:
>>> new = np.array([[1,2,3,4,5,6,7,8,9],[2,3,4,56,78,8,9,7,5]])
>>> def slice(data, x=':', y=':'):
... return eval(f'{data}[{x}, {y}]')
...
>>> slice('new', 1, 2)
4
>>> slice('new')
array([[ 1, 2, 3, 4, 5, 6, 7, 8, 9],
[ 2, 3, 4, 56, 78, 8, 9, 7, 5]])

Why there are two square brackets required inside numpy array?

I am learning python, and I recently came across a module Numpy. With the help of Numpy, one can convert list to arrays and perform operations much faster.
Let's say we create an array with following values :
import numpy as np
np_array=array([1,2,3,4,5])
So we need one square bracket if we need to store one list in the form of array. Now if I want to create a 2D array, why it should be defined like this:
np_array=array([[1,2,3,4,5],[6,7,8,9,10]])
And not like this:
np_array=array([1,2,3,4,5],[6,7,8,9,10])
I apologize if this question is a duplicate, but I couldn't find any answer.
Many Thanks
Array function has the following form.
array(object, dtype=None, copy=True, order=None, subok=False, ndmin=0)
If you use
np_array=array([1,2,3,4,5],[6,7,8,9,10])
The function call will result in passing [1,2,3,4,5] to object and [6,7,8,9,10] to dtype, which wont make any sense.
This actually has little to do with numpy. You are essentially asking what is the difference between foo(a, b) and foo([a, b]).
arbitrary_function([1, 2, 3, 4, 5], [6, 7, 8, 9, 10]) passes two lists as separate arguments to arbitrary_function (one argument is [1, 2, 3, 4, 5] and the second is [6, 7, 8, 9, 10]).
arbitrary_function([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]) passes a list of lists ([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]) to arbitrary_function.
Now, numpy creators could have chosen to allow arbitrary_function([1, 2, 3, 4, 5], [6, 7, 8, 9, 10]) but it would have made little to no sense to do so.

How to sum an output in Python

The program below will create a list of 100 numbers chosen randomly between 1-10. I need help to then sum the list, then average the list created.
I have no idea how to begin and since I'm watching videos online I have no person to turn to. I'm very fresh in this world so I may just be missing entire ideas. I would doubt that I don't actually know enough though because the videos I paid for are step by step know nothing to know something.
Edit: I was informed that what the program does is overwrite a variable, not make a list. So how do I sum my output like this example?
This is all I have to go on:
Code:
import random
x=0
while x < 100:
mylist = (random.randrange(1,10))
print(mylist)
x = x+1
I think the shortest and pythonic way to do this is:
import random
x = [random.randrange(1,10) for i in range(100)] #list comprehension
summed = sum(x) #Sum of all integers from x
avg = summed / len(x) #Average of the numbers from x
In this case this shouldn't have a big impact, but you should never use while and code manual counter when you know how many times you want to go; in other words, always use for when it's possible. It's more efficient and clearer to see what the code does.
def sum(list):
sm = 0
for i in list:
sm+=i
return sm
Just run sum(list) to get sum of all elements
Or you can use
import random
x=0
mylist = []
sm = 0
while x < 100:
mylist.append(random.randrange(1,10))
sm += mylist[x]
x += 1
Then sm will be sum of list
The code is not correct. It will not create a list but generate a number everytime. Use the below code to get your desired result.
import random
mylist = []
for x in range(100):
mylist.append(random.randrange(1,10))
print(mylist)
print(sum(mylist))
OR
import random
mylist = [random.randrange(1,10) for value in range(100)]
print(mylist)
print(sum(mylist))
Output:
[3, 9, 3, 1, 3, 5, 8, 8, 3, 3, 1, 2, 5, 1, 2, 1, 4, 8, 9, 1, 2, 2, 4,
6, 9, 7, 9, 5, 4, 5, 7, 7, 9, 2, 5, 8, 2, 4, 3, 8, 2, 1, 3, 4, 2, 2,
2, 1, 6, 8, 3, 2, 1, 9, 6, 5, 8, 7, 7, 9, 9, 9, 8, 5, 7, 9, 4, 9, 8,
7, 5, 9, 2, 6, 8, 8, 3, 4, 8, 4, 7, 9, 9, 4, 2, 9, 9, 6, 3, 4, 9, 5,
3, 8, 4, 1, 1, 3, 2, 6]
512

What is the pythonic way of extracting (& removing) a slice from a list? Or... What is the "inverse" of the slice+=list operator - python

I know how insert a list into a list, "slice+=list" ...
master=[0,1,2,3,7,8,9]
master[:4]+=[4,5,6] # insert 4,5,6
(crudely) The inverse of this operation is removing a slice 4:7 from the list, I tried:
extracted=del master[4:7]
But this gives a syntax error "SyntaxError: invalid syntax".
Likewise the inverse slice operator "-=" doesn't appear to exist.
As a workaround I have used the following:
extracted=master[4:7]; del master[4:7]
This "works" and the "extracted" is the subslice removed from "master", e.g.
print dict(master=master,extracted=extracted)
Output:
{'extracted': [4, 5, 6], 'master': [0, 1, 2, 3, 7, 8, 9]}
Is there a better/pythonic/simpler way ?
In particular I don't like the repeated [4:7] in:
extracted=master[4:7]; del master[4:7]"
Because of potential side-effects: eg
extracted=master[randint(0,3):randint(7,10)]; del master[randint(0,3):randint(7,10)]
i.e. the following reads much better, and would have no "side-effects"...
extracted=del master[randint(0,3):randint(7,10)]
Any hints? Is there a slice "-=" operator I could have used to invert the action of the slice "+=" operator?
Your best bet is to use a slice:
-> s = slice(4, 7)
-> master
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
--> extracted = master[s]
--> extracted
[4, 5, 6]
--> del master[s]
--> master
[0, 1, 2, 3, 7, 8, 9]
It still requires two commands, but you can use a single object to respresent the piece you want.
For me the cleaner option is as follows:
>>> L=[1,2,3,4,5,6] #Define the list
>>> L #Check it
[1, 2, 3, 4, 5, 6]
>>> extract= L[3:6] ; L[3:6]=[] #Assign the slice to 'extract'; delete the slice
>>> L #Items removed from the list
[1, 2, 3]
>>> extract #And assigned to extract
[4, 5, 6]
Cheers!

Resources