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).
Related
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.
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)
I'm looking for maximum absolute value out of chunked list.
For example, the list is:
[1, 2, 4, 5, 4, 5, 6, 7, 2, 6, -9, 6, 4, 2, 7, 8]
I want to find the maximum with lookahead = 4. For this case, it will return me:
[5, 7, 9, 8]
How can I do simply in Python?
for d in data[::4]:
if count < LIMIT:
count = count + 1
if abs(d) > maximum_item:
maximum_item = abs(d)
else:
max_array.append(maximum_item)
if maximum_item > highest_line:
highest_line = maximum_item
maximum_item = 0
count = 1
I know I can use for loop to check this. But I'm sure there is an easier way in python.
Using standard Python:
[max(abs(x) for x in arr[i:i+4]) for i in range(0, len(arr), 4)]
This works also if the array cannot be evenly divided.
Map the list to abs(), then chunk the list and send it to max():
array = [1,2,4,5,4,5,6,7,2,6,-9,6,4,2,7,8]
array = [abs(item) for item in array]
# use linked question's answer to chunk
# array = [[1,2,4,5], [4,5,6,7], [2,6,9,6], [4,2,7,8]] # chunked abs()'ed list
values = [max(item) for item in array]
Result:
>>> values
[5, 7, 9, 8]
Another way, is to use islice method from itertools module:
>>> from itertools import islice
>>> [max(islice(map(abs,array),i,i+4)) for i in range(0,len(array),4)]
[5, 7, 9, 8]
To break it down:
1 - map(abs, array) returns a list of all absolute values of array elemets
2 - islice(map(abs,array),i,i+4)) slices the array in chunks of four elements
3 - i in range(0,len(array),4) stepping range for islice to avoid overlapping
This can be wrapped in function as fellows:
def max_of_chunks(lst, chunk_size):
lst = map(abs, lst)
result = [max(islice(lst,i,i+chunk_size)) for i in range(0,len(lst),chunk_size)]
return result
Upd: Oh, I've just seen newest comments to task and answers. I wasn't get task properly, my bad :) Let my old answer stay here for history. Max numbers from list chunks you can find in the way like that:
largest = [max(abs(x) for x in l[i:i+n]) for i in xrange(0, len(l), n)]
or
largest = [max(abs(x) for x in l[i:i+n]) for i in range(0, len(l), n)]
if you're use Python3.
Original answer just for history: If you had to choice some numbers (once) from not a big list, you shouldn't install big libraries like numpy for such simple tasks. There are a lot of techniques to do it with built-in Python tools. Here they are (something of them).
So we have some list and count of maximum different elements:
In [1]: l = [1, 2, 4, 5, 4, 5, 6, 7, 2, 6, -9, 6, 4, 2, 7, 8]
In [2]: n = 4
A. First we getting only unique numbers from source list by converting it to set. Then we creating a list consist of these unique numbers, sort it and finally get N last (greatest) elements:
In [3]: sorted(list(set(l)))[-n:]
Out[3]: [5, 6, 7, 8]
B. You can use built-in heapq module:
In [7]: import heapq
In [8]: heapq.nlargest(n, set(l))
Out[8]: [8, 7, 6, 5]
Of course you can 'wrap' A or B technique into some human-friendly function like def get_largest(seq, n): return sorted(list(set(l)))[-n:]. Yes I've ommited some details like handling IndexError. You should remember about it when you'll writing the code.
C. If your list(s) is very long and you had to do many of these operations so fast as Python can, you should use special third-party libraries like numpy or bottleneck.
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!
I am trying to shift the elements of an array cyclically so all elements are replaced with the previous element, and the last rotates to the first poaition, like so: shift(1, [5, 6, 7])=>[7, 5, 6].
The following code only returns [7,5]. Could someone please tell me what is causing this to happen? I went through the code step by step and simply could not find a solution. I also tried 3 different interpreters.
def shift(key, array):
counter = range(len(array)-1)
new = counter
for i in counter:
new[i] = array[i-key]
return new
print shift(1, [5, 6, 7])
range(5) returns [0, 1, 2, 3, 4]. It excludes 5.
Just remove the -1 from range(len(array)-1) and it should work.
You could also use list slicing:
def shift(key, array):
return array[-key:] + array[:-key]
Here is the python way:
def shift(key, array):
return array[-key:]+array[:-key]
You need to remove the -1 from your range:
counter = range(len(array))
If you want a faster method though,
You could instead try using a deque?
from collections import deque
def shift(key, array):
a = deque(array) # turn list into deque
a.rotate(key) # rotate deque by key
return list(a) # turn deque back into a list
print (shift(1, [5, 6, 7]))
The answers are good, but it doesn't work if the key is greater than the length of the array. If you think the key will be larger than the array length, use the following:
def shift(key, array):
return array[key % len(array):] + array[:key % len(array)]
A positive key will shift left and a negative key will shift right.
The numpy package contains the roll function to perform exactly this task:
import numpy as np
b=[5,6,7]
c=np.roll(b,1).tolist()
>>> c
[7, 5, 6]
A function using this and returning a list is:
def shift(array,key):
return np.roll(array,key).tolist()
#!/usr/bin/env python
def ashift(key,array):
newqueue = array[-key:]
newqueue.extend( array[:-key] )
return newqueue
print ashift( 1, [5,6,7] )
print ashift( 2, [5,6,7] )
Results in:
$ ./shift
[7, 5, 6]
[6, 7, 5]
The only potential penalty is if the array is sufficiently large, you may encounter memory issues, as this operation is doing a copy. Using a "key" with an absolute value greater than the length of the array will result in wrapping and results may not be as expected, but will not error out.
Good old fashioned POP & APPEND
arr = [5, 6, 7]
for _ in range(0, 2):
shift = arr.pop(0)
arr.append(shift)
print(arr)
=>[7, 5, 6]
You can use numpy roll
>>> x = np.arange(10)
>>> np.roll(x, 2)
array([8, 9, 0, 1, 2, 3, 4, 5, 6, 7])
>>> np.roll(x, -2)
array([2, 3, 4, 5, 6, 7, 8, 9, 0, 1])