Python shuffle list not working [duplicate] - python

This question already has answers here:
Shuffling a list of objects
(25 answers)
Closed 8 years ago.
I am new with python. coming from a C++ background I'm ironically having trouble understanding the simplicity of this language not to mention how IDLE works.
anyhow I need to write a simple function that takes a list and returns a new list with the elements inside it shuffled
this is what I have so far
import random
def shuffle():
aList = []
i = 0
for i in range(0, 5):
elements = input(": ")
aList.append(elements)
shuffleList = random.shuffle(aList)
return shuffleList
shuffle()
and after I enter the elements(numerical numbers in this case), nothing outputs.. So the shuffleList for some reason is not being shown in there. Any ideas ?
>>>
: 1
: 2
: 3
: 4
: 5
>>>

random.shuffle shuffles the list in place, and its output is None.
Since you store and return its output in shuffleList = random.shuffle(aList), nothing is printed.
So instead, return the aList back:
import random
def shuffle():
aList = []
i = 0
for i in range(0, 5):
elements = input(": ")
aList.append(elements)
random.shuffle(aList)
return aList

random.shuffle is an in-place operation, it does not return anything
>>> l
[0, 1, 2, 3, 4]
>>> random.shuffle(l)
>>> l
[0, 3, 1, 4, 2]

Shuffle shuffles the list, it doesn't return a new list. Try this:
import random
def shuffle():
aList = []
for i in range(0, 5):
elements = input(": ")
aList.append(elements)
random.shuffle(aList)
return aList
print shuffle()

random.shuffle works in place, meaning it updates the provided parameter, rather than returning a value (other than the default, None). In C++, you would need to pass a reference to the list instead (e.g. int** aList), but in Python this distinction doesn't really exist.
So, you're actually setting shuffleList to None, and then returning it!
You can confirm this by using print to actually show the results when you're finished - just returning the value from the function won't output anything, as far as I am aware:
results = shuffle()
print results
To fix it, just return aList, instead of storing the result in another variable:
random.shuffle(aList)
return aList

random.shuffle() reorders the list in-place and returns None.

Related

Why does Python list comprehension seem to behave differently than list "multiplication"? [duplicate]

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 8 months ago.
Asking out of curiosity. For the sake of making a point I was trying to make a function that returns an "identity matrix" of n dimensions and then printing it in the most concise way.
First I came up with this:
def identity(n):
zeros = [[0 for j in range(n)] for i in range(n)]
for i in range(n):
zeros[i][i] = 1
return zeros
for i in range(5):
print(identity(5)[i])
This works as intended, however then I tried making the syntax shorter by doing this:
def identity(n):
zeros = [[0]*n]*n
for i in range(n):
zeros[i][i] = 1
return zeros
for i in range(5):
print(identity(5)[i])
And this for some reason changes every single element to a one, but I can't seem to figure out why?. This isn't an important question but help is much appreciated!
lists are kept by reference in python.
This means that if you have:
list_a = [1,2,3,4]
list_b = list_a
list_a and list_b are actually pointing to the same object in memory. so if you change an element of list_a:
list_a[2] = 9
then the same element for list_b will change because they are pointing to the same object. i.e. list_a literally equals list_b in every way.
That's what's happening in your code as well.
When you loop through and assign each value then it's as if you were explicitly creating a new list and assigning it to each element of your outter list:
l = []
l.append([1,2,3,4])
l.append([1,2,3,4])
...
but in the second piece of code, it is as if you are repeatedly appending the same value to the list:
l = []
la = [1,2,3,4]
l.append(la)
l.append(la)
It's because list comprehension performs shallow copy on the element.
All elements in zeros are refering to the same list.
Try these and see the results:
n = 4
zeros = [[0]*n]*n
zeros[0] = 1
print(zeros)
n = 4
lst = [0]*n
zeros = [lst]*n
print(zeros)
lst[0] = 1
print(zeros)
You can learn more about difference between shallow and deep copy here.
This is because when you multiply a list with a number, it duplicates it, but it does so by copying the reference of each element.
If you do id(zeros[0][0]) and id(zeros[0][1]) for the second case, you will see that they are the same, i.e. they refer to the same element and modifying one modifies them all.
This is not true for the first case where every 0 is instantiated seperately and has its own id.
Edit:
def zeros(n):
return [[0]*n]*n
x = zeros(5)
for i in range(5):
for j in range(5):
assert id(x[0][0]) == id(x[i][j])

How to have my program list every item off of a list randomly and only once

I'm trying to have a function that uses the random import to randomly select a value from the list [0, 1, 2, 3, 4, 5, 6, 7, 8], print that value, then repeat itself until all the numbers are selected. The problem I'm having is then making sure when it repeats itself it won't choose that number again. What I've tried is:
def ListArranger():
randomPick = random.choices(list)
if len(list) > 0:
if list[randomPick] != " ":
print(list[randomPick])
list[randomPick] = " "
ListArranger()
else:
ListArranger()
I run into the problem that says list indices must be integers or slices, not list. I'm assuming it's having a problem because I'm trying to set the list value to a string, but I can't figure out how to work around this
The obvious choice that nobody seems to mention is random.sample:
def ListArranger(lst):
return random.sample(lst, len(lst))
for pick in ListArranger(range(9)):
print(pick)
4
0
8
1
2
3
7
6
5
Here is the solution:
import random
def ListArranger(list):
if len(list) > 0:
randomPick = random.choice(list)
print(randomPick)
list.remove(randomPick)
ListArranger(list)
list = [0,1,2,3,4,5,6,7,8]
ListArranger(list)
Output:
2
8
7
1
3
4
0
5
6
This function takes a list as an argument an calls itself until the list is empty.
It removes a random element of the list in each recursive call while printing out the removed element.
You can shuffle() copy (quote from docs: "All slice operations return a new list containing the requested elements.") of given list once and yield from (PEP 380) shuffled list from generator function.
from random import shuffle
def ListArranger(lst):
lst_copy = lst[:]
shuffle(lst_copy)
yield from lst_copy
You can iterate over this function which will return you every time new random element from lst.
If you don't need to keep source list untouched, you can omit copying, shuffle list itself and iterate over it.
The error comes from line lines 3,4,5
Because you are indexing your list with a list in all those lines.
randomPick is a list not an integer or a slice that's what the error indicates.
Example:
print(randomPick[ **[1,2,3,4]** ] here is the actual problem.
You can only index a list by an integer or a slice.
Solution:
import random
lst = [1,2,3,4,5,6,7,8]
check_lst = []
while True:
random_num = random.choice(lst)
if random_num in check_lst:
continue
elif random_num not in check_lst:
check_lst.append(random_num)
print(random_num)
if len(lst) == len(check_lst):
print("Finish the loop")
break
import random
lst=[0,1,2,3,4,5,6,7,8]
lstlen=len(lst)
def ListArranger():
if len(lst)!=0:
randomPick=random.choice(lst)
print(randomPick)
lst.pop(lst.index(randomPick))
ListArranger()
else:
for i in range(lstlen):
lst.append(" ")
ListArranger()
If you think of your list as a stack, you can just shuffle() once and then pop() each element off in-sequence until you are done.
Also, this is a chance to use a generator. You can turn any function into a generator simply by adding a yield <something>. Then you can use it in a for statement.
Our input list remains intact because we internally copy() it.
from random import shuffle
def chooseOneWithoutReplace(items):
data = items.copy()
shuffle(data)
while data:
yield data.pop()
# nothing more to yield
# generator ends -> outer for loop ends
daysOfWeek = [1,2,3,4,5,6,7]
for i in chooseOneWithoutReplace(daysOfWeek):
print(i)
Output
1
4
5
3
7
2
6
Use shuffle, which does just that with a given sequence.
def listArranger(a):
random.shuffle(a)
for item in a:
print(item)
Do not shadow a built-in name with a local variable: list is perhaps the most common bad variable name.
Pass your list into the function, rather than making it an assumption of the outer scope.

Fibonacci series by recursive function in Python

Hello I am trying to generate Fibonacci series by using a recursive function in python.
Here is my code
def fibolist(n):
list1 = [1, 1]
if n in (1,2) :
return list1
else:
fibolist(n-1).append(sum(fibolist(n-1)[n-3:]))
return list1
but when I enter any number as an argument, the result is [1, 1]
Could you please help me?!
You start with
list1 = [1, 1]
You never change that value, and then you return it to the calling routine.
Each invocation of fibolist has a local variable named list1; appending to one does not change the list1 value in the calling program. You need to explicitly do that. Try
else:
return fibolist(n-1) + [sum(fibolist(n-1)[n-3:])]
Just to fix your code:
def fibolist(n):
if n in (0,1) :
return [1,1]
else:
return fibolist(n-1)+[sum(fibolist(n-1)[n-2:])]
Few notes:
lists in python have starting index=0, so it's better start with it (unless you want to put start return to [0,1,1] for n in (1,2)).
Also - as already mentioned you shouldn't return local variable, which you preassign in each go.
Your code is not updating the list1 variable that it returns upon coming back from the recursion. Doing fibolist(n-1).append(...) updates the list returned by the next level but that is a separate list so list1 is not affected.
You could also make your function much simpler by making it pass the last two values to itself:
def fibo(n,a=1,b=1): return [a] if n==1 else [a] + fibo(n-1,b,a+b)
BTW, the modern interpretation of the fibonacci sequence starts at 0,1 not 1,1 so the above signature should be def fibo(n,a=0,b=1).
Output:
print(fibo(5))
#[1, 1, 2, 3, 5]

How do I fix IndexError: string index out of range in python

My code returns
IndexError: string index out of range
The code is supposed to make a string be split into groups of two and inserted into a list but instead returns Error
def tokenize(tokenlist):
newList = []
for i in range(1,6,2):
newList.append(tokenlist[i]+tokenlist[i+1])
return newList
The input is "abcdef" and the
output I expected was the list ["ab","cd","ef"] but I got an error. How do I get my code to do what I intended?
Your input is of length 6 so last index is 5
Your range goes up to 5
So i+1 in tokenlist[i+1] goes up to 6 which causes the IndexError as lists and strings are indexed from 0 in python
Correct to range(0,6,2)
Better yet, use len(tokenlist) instead of 6.
Be aware that if it is odd you will get an error. You should specify expected behavior in this case.
For instance, if last character may be alone use string slicing:
def tokenize(tokenlist):
newList = []
for i in range(0, len(tokenlist), 2):
newList.append(tokenlist[i: i + 2])
return newList
In any case, as commented, you should refactor your code according to python guidelines. For instance
def tokenize(tokenlist):
newList = []
for i in range(0, len(tokenlist), 2):
newList.append(tokenlist[i] + tokenlist[i + 1])
return newList
Look at the call to range( 1, 6, 2 ).
What will happen when i = 5 ?
This will have the code trying to make an element of tokenlist[5] and tokenlist[6], whereas when working on "abcdef", there are only elements tokenlist[0] (a) to tokenlist(5) (f).
So this element in the range is off the end of the list.
BTW: What should this function do when len( tokenlist ) is an odd number?

Removing a class from a list [duplicate]

This question already has answers here:
How to remove items from a list while iterating?
(25 answers)
Closed 6 years ago.
I am trying to remove classes from a list based on their hp. I am making a large scale battle simulator for a D&D campaign. Its a simple program that makes two lists of classes and pits them against each other.
I am running into a problem when it comes to removing dead fighters. It works fine if one fighter dies in a round, but when multiple die, it goes wonky.
def check_human_deaths():
for i in range(len(goodguys)):
if goodguys[i].hp <= 0:
print('{} has died...'.format(goodguys[i].name))
goodguys.remove(goodguys[i])
Removing the dead fighter changes the length of the list, throwing an index error:
IndexError: list index out of range
I am not to sure how to proceed with removing the dead from the battlefield. Any tips are appreciated. Let me know if I am going about this in a fundamentally wrong way.
Two choices:
Modify a copy of the list and use the result of that as the new list:
>>> lst = [1,2,3,4,5]
>>> for i in lst[:]:
... if i % 2:
... result.append(i)
...
>>> lst = result
>>> lst
[1, 3, 5]
Modify the list in place, but do so in reverse to avoid messing up the indexes:
>>> lst = [1,2,3,4,5]
>>> for i in lst[::-1]:
... if not i % 2:
... lst.remove(i)
...
>>> lst
[1, 3, 5]
The problem is that when you remove from goodguys, the index is reduced by one. Ex:
1,2,3,4
Remove 2
1,3,4
Index of three has been decremented by one and the size has been decremented by one.
goodguys = [ guy for guy in goodguys if guy.hp > 0 ]
This will filter out any dead goodguys in your array.
You can use it in your function like this:
def check_human_deaths():
global goodguys
dedguys = [ guy for guy in goodguys if guy.hp <= 0 ]
for guy in dedguys:
print('{} has died...'.format(guy.name))
goodguys = [ guy for guy in goodguys if guy.hp > 0 ]
You are getting this error because you are iterating over the length of the list let's say l. Each time you call list.remove(), length of the list is decreased by 1, and your program gives index error because list[l] does not exists. Instead you can use:
def check_human_deaths():
for goodguy in list(goodguys):
if goodguy.hp <= 0:
print('{} has died...'.format(goodguy.name))
goodguys.remove(goodguy)
Note: list(goodguys) will create the copy of goodguys list preventing the weird behavior of for loop when object is removed from the list:
>>> t= [1,2,3]
>>> x= list(t)
>>> x.remove(1)
>>> x
[2, 3]
>>> t
[1, 2, 3]
Even after removing the value from x, this value will still be present in t and your for for loop will not behave weirdly
As others said, You can't change the list while iterating over it because weird things will happen. Instead You can save elements that You want to delete and delete them after the loop:
def check_human_deaths():
things_to_delete = []
for i in range(len(goodguys)):
if goodguys[i].hp <= 0:
print('{} has died...'.format(goodguys[i].name))
things_to_delete.append(goodguys[i])
goodguys = [x for x in goodguys if x not in things_to_delete]
You can do it using a generator function to allow you to print and update the list in a more efficient and correct manner doing a single pass over the list:
class Foo():
def __init__(self, hp, name):
self.hp = hp
self.name = name
def check_human_deaths(goodguys):
for guy in goodguys:
if guy.hp < 1:
print('{} has died...'.format(guy.name))
else:
yield guy
Demo:
In [29]: goodguys = [Foo(0,"foo"), Foo(1,"bar"), Foo(2,"foobar"), Foo(-1,"barf")]
In [30]: goodguys[:] = check_human_deaths(goodguys)
foo has died...
barf has died...
In [31]:
In [31]: print(goodguys)
[<__main__.Foo instance at 0x7fdab2c74dd0>, <__main__.Foo instance at 0x7fdab2c6e908>]
This answer has a very good explanation as to why you should never mutate a list you are iterating it.
Also if you are going to start at the end of the list and use remove, you can use reversed instead of creating another copy of the list:
for guy in reversed(goodguys):
if guy.hp <= 0:
print('{} has died...'.format(guy.name))
goodguys.remove(guy)
But that is not going to be as efficient as the first option.

Categories

Resources