Generate random numbers in range without repetition - python

I'm creating a trivia game and need to call 10 questions (functions) in a random order. I could simply generate a random integer 1-10 and use if statements to call each function, but I need to make sure that no questions are called more than once, so I need to generate random numbers without repetition.
def Trivia_Snap():
question_1()
question_2()
question_3()
question_4()
question_5()
question_6()
question_7()
question_8()
question_9()
question_10()

You can put the calls to the functions in a list
l = [question_1, question_2...]
Then select from that list randomly without replacment
import random
rand_l = random.sample(l, len(l))
for f in rand_l:
f()
EDIT: per comments below you can also use shuffle
random.shuffle(l)
for f in l:
f()

>>> import random
create a list of numbers
>>> l = range(0,11)
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
...and shuffle them in place with random.shuffle()
>>> random.shuffle(l)
>>> l
[5, 7, 4, 2, 1, 8, 6, 10, 9, 0, 3]
>>> random.shuffle(l)
>>> l
[1, 5, 9, 4, 10, 6, 2, 7, 0, 3, 8]

import random
qlist = [i for i in range(0,10)]
random.shuffle(qlist)
for q in qlist:
print q

Suppose you have a list of questions:
questions = ['Q: foo?', 'Q: bar?', Q: 'baz?']
With the following function you can present all these questions for an user to answer. Everytime you call the function, the questions will be presented in a different order. It will return a list of tuples in which each tuple represents the number of the question answered, as enumerated from the original question list, and the answer to the question:
from random import shuffle
def trivia_snap(questions):
q = list(enumerate(questions))
shuffle(q)
answers = []
for question in q:
answers.append((question[0], input(question[1] + " Answer: ")))
return sorted(answers)
>>> trivia_snap(questions)
>>> Q: foo? Answer: "Homer"
>>> Q: baz? Answer: "Marge"
>>> Q: bar? Answer: "Lisa"
>>> [(0, "Homer"), (1, "Lisa"), (2, "Marge"), ]
Hope it helps!

Related

How to remove an element that appears more than once in a list without changing the length of the list (no sets)

I've looked up many times and it seems that sets are the best option however for this assignment I believe it is not allowed. So, basically, I made an empty list and it generates a list that is 5 elements long with numbers between 1 and 9 inside. But all the numbers need to appear once in the list. How do I do that?
import random
lis = []
for i in range(1,6):
x = random.randint(1,9)
lis.append(x)
I've tried,
if x not in lis:
lis.append(x)
But the length of my list kept changing.
IIUC, You can use a while alongside the if statement you were originally trying:
import random
lis = []
while len(lis) < 5:
x = random.randint(1,9)
if x not in lis:
lis.append(x)
>>> lis
[2, 8, 1, 5, 3]
But if you want to be tricky about it, you can do:
x = list(range(1,10))
random.shuffle(x)
>>> x[:5]
[2, 1, 4, 3, 7]
Or
import numpy as np
>>> np.random.choice(range(1,10),5,replace=False)
array([7, 5, 3, 6, 4])
You could do this like Sacul said with a while loop, but here's another fancy option, it might not be allowed by your assignment though.
import random
lis = random.sample(range(1,10),5)

Pythonic way to append output of function to several lists

I have a question that I haven't quite found a good solution to. I'm looking for a better way to append function output to two or more lists, without using temp variables. Example below:
def f():
return 5,6
a,b = [], []
for i in range(10):
tmp_a, tmp_b = f()
a.append(tmp_a)
b.append(temp_b)
I've tried playing around with something like zip(*f()), but haven't quite found a solution that way.
Any way to remove those temp vars would be super helpful though, thanks!
Edit for additional info:
In this situation, the number of outputs from the function will always equal the number of lists that are being appended to. The main reason I'm looking to get rid of temps is for the case where there are maybe 8-10 function outputs, and having that many temp variables would get messy (though I don't really even like having two).
def f():
return 5,6
a,b = zip(*[f() for i in range(10)])
# this will create two tuples of elements 5 and 6 you can change
# them to list by type casting it like list(a), list(b)
First solution: we make a list of all results, then transpose it
def f(i):
return i, 2*i
# First make a list of all your results
l = [f(i) for i in range(5)]
# [(0, 0), (1, 2), (2, 4), (3, 6), (4, 8)]
# then transpose it using zip
a, b = zip(*l)
print(list(a))
print(list(b))
# [0, 1, 2, 3, 4]
# [0, 2, 4, 6, 8]
Or, all in one line:
a, b = zip(*[f(i) for i in range(5)])
A different solution, building the lists at each iteration, so that you can use them while they're being built:
def f(i):
return 2*i, i**2, i**3
doubles = []
squares = []
cubes = []
results = [doubles, squares, cubes]
for i in range(1, 4):
list(map(lambda res, val: res.append(val), results, f(i)))
print(results)
# [[2], [1], [1]]
# [[2, 4], [1, 4], [1, 8]]
# [[2, 4, 6], [1, 4, 9], [1, 8, 27]]
print(cubes)
# [1, 8, 27]
Note about list(map(...)): in Python3, map returns a generator, so we must use it if we want the lambda to be executed.list does it.
For your specific case, the zip answers are great.
Using itertools.cycle and itertools.chain is a different approach from the existing answers that might come in handy if you have a lot of pre-existing lists that you want to append to in a round-robin fashion. It also works when your function returns more values than you have lists.
>>> from itertools import cycle, chain
>>> a, b = [], [] # new, empty lists only for demo purposes
>>> for l, v in zip(cycle([a, b]), (chain(*(f() for i in range(10))))):
... l.append(v)
...
>>> a
[5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
>>> b
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6]
I'd do
tmp = f()
a.append(tmp[0])
b.append(tmp[1])
Not sure how pythonic it is for you though.

How can I select entries from list in random order in python

Question is simple. I have a list of say 10 entries, I am running a loop over it. What i want here is getting each entry exactly once but in random order.
What is the best and most pythonic way to do it?
You can use random.sample, it returns random elements preventing duplicates:
>>> import random
>>> data = range(10)
>>> print(random.sample(data, len(data)))
[2, 4, 8, 7, 0, 5, 6, 3, 1, 9]
The original list remains unchanged.
You can use random.shuffle:
In [1]: import random
In [3]: a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [4]: random.shuffle(a)
In [5]: a
Out[5]: [3, 6, 9, 1, 8, 0, 4, 7, 5, 2]
Shuffle the list first by importing the random module and using the shuffle function:
import random
x = ... # A list
random.shuffle(x)
Then loop over your list. Note that shuffle mutates the original list and does not return the shuffled version.
You could use shuffle:
import random
random.shuffle(yourlist)
# loop as you would normally
for item in yourlist:
.....
you could use random.shuffle():
import random
original = range(10)
# Make a copy of the original list, as shuffling will be in-place
shuffled = list(original)
random.shuffle(shuffled)
Valid remark from Jean-François Fabre: if you were to use the original variable and pass it directly to random.shuffle, Python would return an error, stating 'range' object does not support item assignment, as range returns a generator.
To solve this, simply replace the assignment with list(range(10)).
import random
s=set(range(10))
while len(s)>0:
s.remove(random.choice(yourlist(s)))
print(s)

How to print list of list into one single list in python without using any for or while loop?

Today I had an interview and I was asked to print a list of list into one single list without using any for or while loop but you can use other built in function.
Here is the list:
>>> myList = [[[1,2,3],[4,5],[6,7,8,9]]]
>>> myList
[[[1, 2, 3], [4, 5], [6, 7, 8, 9]]]
>>>
The output will be [1, 2, 3, 4, 5, 6, 7, 8, 9].
Any idea how to go about this?
Three options:
You could sum the nested lists; sum() takes a second argument, a starting value, set that to an empty list:
>>> sum(myList[0], [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
This works because sum() is essentially implemented as a loop:
def sum(values, start=0):
total = start
for value in values:
total += value
return total
which works with list concatenation, provided the start value is a list object itself. 0 + [1, 2, 3] would not work, but [] + [1, 2, 3] works just fine.
You could use reduce() with operator.add(), which is essentially the same as sum(), minus the requirement to give a start value:
from operator import add
reduce(add, myList[0])
operator.add() could be replaced with lambda a, b: a + b or with list.__add__ if imports are to be avoided at all cost.
As the nested input list grows, operator.iadd() (in-place add, for lists the equivalent of list.extend()) will rapidly become a faster option:
from operator import iadd
reduce(add, myList[0], [])
but this does need an empty list to start with.
You could chain the lists using itertools.chain.from_iterable():
>>> from itertools import chain
>>> list(chain.from_iterable(myList[0]))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
All three solutions require that you use indexing to remove the outermost list, although you can also pass the one element in myList as a single argument to chain.from_iterable() with list(chain.from_iterable(*myList)) as well.
Of these options, reduce(add, ...) is the fastest:
>>> timeit.timeit("sum(myList[0], [])", 'from __main__ import myList')
1.2761731147766113
>>> timeit.timeit("reduce(add, myList[0])", 'from __main__ import myList; from operator import add')
1.0545191764831543
>>> timeit.timeit("reduce(lambda a, b: a.extend(b) or a, myList[0], [])", 'from __main__ import myList')
2.225532054901123
>>> timeit.timeit("list(chain.from_iterable(myList[0]))", 'from __main__ import myList; from itertools import chain')
2.0208170413970947
and comparing iadd versus add:
>>> timeit.timeit("reduce(add, myList[0])", 'from __main__ import myList; from operator import add')
0.9298770427703857
>>> timeit.timeit("reduce(iadd, myList[0], [])", 'from __main__ import myList; from operator import iadd')
1.178157091140747
>>> timeit.timeit("reduce(add, myListDoubled)", 'from __main__ import myList; myListDoubled = myList[0] + myList[0]; from operator import add')
2.3597090244293213
>>> timeit.timeit("reduce(iadd, myListDoubled, [])", 'from __main__ import myList; myListDoubled = myList[0] + myList[0]; from operator import iadd')
1.730151891708374
You could use recursion to avoid using a loop, to make this work for arbitrarily nested lists:
def flatten(lst):
try:
return flatten(sum(lst, []))
except TypeError:
return lst
Demo:
>>> flatten(myList)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> flatten(myList + myList)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9]
If we assume no imports allowed and that we are still on 2.7.x,
reduce(lambda x,y:x+y,*myList)
A quick search show that this question, making a flat list out of lists, has been analyzed in depth: Making a flat list out of list of lists in Python and although in that thread there is no restriction on what functions you can use, they answer goes into great detail about the time complexity of using different methods. This is quite important, as it could be the follow up question in an interview.
myList = [[[1,2,3],[4,5],[6,7,8,9]]]
sum(myList[0], [])
Output
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Use itertools.chain.from_iterable:
In [34]: from itertools import chain
In [35]: list(chain.from_iterable(myList[0]))
Out[35]: [1, 2, 3, 4, 5, 6, 7, 8, 9]
try this
import itertools
list(itertools.chain(*mylist))

Python Random Slice Idiom

Is there a pythonic way to slice a sequence type such that the returned slice is of random length and in random order? For example, something like:
>>> l=["a","b","c","d","e"]
>>> rs=l[*:*]
>>> rs
['e','c']
How about...
random.sample(l, random.randint(1, len(l)))
Quick link to docs for the random module can be found here.
No idiom that I'm aware of, but random.sample does what you need.
>>> from random import sample, randint
>>>
>>> def random_sample(seq):
... return sample(seq, randint(0, len(seq)))
...
>>> a = range(0,10)
>>> random_sample(a)
[]
>>> random_sample(a)
[4, 3, 9, 6, 7, 1, 0]
>>> random_sample(a)
[2, 8, 0, 4, 3, 6, 9, 1, 5, 7]
There's a subtle distinction that neither your question nor the other answers address, so I feel I should point it out. It's illustrated by the example below.
>>> random.sample(range(10), 5)
[9, 2, 3, 6, 4]
>>> random.sample(range(10)[:5], 5)
[1, 2, 3, 4, 0]
As you can see from the output, the first version doesn't "slice" the list, but only samples it, so the return values can be from anywhere in the list. If you literally want a "slice" of the list -- that is, if you want to constrain the sample space before sampling -- then the following doesn't do what you want:
random.sample(l, random.randint(1, len(l)))
Instead, you would have to do something like this:
sample_len = random.randint(1, len(l))
random.sample(l[:sample_len], sample_len)
But I think an even better way to do this would be like so:
shuffled = l[:random.randint(1, len(l))]
random.shuffle(shuffled)
Unfortunately there's no copy-returning version of shuffle that I'm aware of (i.e. a shuffled akin to sorted).

Categories