I want to extract a slice of length 10, beginning at index 2, of a numpy array A:
import numpy
A = numpy.array([1,3,5,3,9])
def bigslice(A, begin_at, length):
a = A[begin_at:begin_at + length]
while len(a) + len(A) < length:
a = numpy.concatenate((a,A))
return numpy.concatenate((a, A[:length-len(a)]))
print bigslice(A, begin_at = 2, length = 10)
#[5,3,9,1,3,5,3,9,1,3]
This is correct. But I'm looking for a more efficient way to do this (especially when I'll have arrays of thousands of elements at the end) : I suspect the concatenate used here to recreate lots of new temporary arrays, and that would be un-efficient.
How to do the same thing more efficiently ?
Since the middle part of the array is already known to you (i.e. n repetitions of the full array), you can simply construct the middle portion using np.tile:
def cyclical_slice(A, start, length):
arr_l = len(A)
middle = np.tile(A, length // arr_l)
return np.array([A[start:], middle, A[0:length - len(middle)]])
Your code doesn't seem to guarantee that you get a slice of length length, e.g.
>>> A = numpy.array([1,3,5,3,9])
>>> bigslice(A, 0, 3)
array([1, 3, 5, 3, 9, 1, 3, 5])
Assuming that this is an oversight, maybe you could use np.pad, e.g.
def wpad(A, begin_at, length):
to_pad = max(length + begin_at - len(A), 0)
return np.pad(A, (0, to_pad), mode='wrap')[begin_at:begin_at+length]
which gives
>>> wpad(A, 0, 3)
array([1, 3, 5])
>>> wpad(A, 0, 10)
array([1, 3, 5, 3, 9, 1, 3, 5, 3, 9])
>>> wpad(A, 2, 10)
array([5, 3, 9, 1, 3, 5, 3, 9, 1, 3])
and so on.
Related
I have a simple code that generates a list of random numbers.
x = [random.randrange(0,11) for i in range(10)]
The problem I'm having is that, since it's random, it sometimes produces duplicate numbers right next to each other. How do I change the code so that it never happens? I'm looking for something like this:
[1, 7, 2, 8, 7, 2, 8, 2, 6, 5]
So that every time I run the code, all the numbers that are next to each other are different.
x = []
while len(x) < 10:
r = random.randrange(0,11)
if not x or x[-1] != r:
x.append(r)
x[-1] contains the last inserted element, which we check not to be the same as the new random number. With not x we check that the array is not empty, as it would generate a IndexError during the first iteration of the loop
Here's an approach that doesn't rely on retrying:
>>> import random
>>> x = [random.choice(range(12))]
>>> for _ in range(9):
... x.append(random.choice([*range(x[-1]), *range(x[-1]+1, 12)]))
...
>>> x
[6, 2, 5, 8, 1, 8, 0, 4, 6, 0]
The idea is to choose each new number by picking from a list that excludes the previously picked number.
Note that having to re-generate a new list to pick from each time keeps this from actually being an efficiency improvement. If you were generating a very long list from a relatively short range, though, it might be worthwhile to generate different pools of numbers up front so that you could then select from the appropriate one in constant time:
>>> pool = [[*range(i), *range(i+1, 3)] for i in range(3)]
>>> x = [random.choice(random.choice(pool))]
>>> for _ in range(10000):
... x.append(random.choice(pool[x[-1]]))
...
>>> x
[0, 2, 0, 2, 0, 2, 1, 0, 1, 2, 0, 1, 2, 1, 0, ...]
O(n) solution by adding to the last element randomly from [1,stop) modulo stop
import random
x = [random.randrange(0,11)]
x.extend((x[-1]+random.randrange(1,11)) % 11 for i in range(9))
x
Output
[0, 10, 4, 5, 10, 1, 4, 8, 0, 9]
from random import randrange
from itertools import islice, groupby
# Make an infinite amount of randrange's results available
pool = iter(lambda: randrange(0, 11), None)
# Use groupby to squash consecutive values into one and islice to at most 10 in total
result = [v for v, _ in islice(groupby(pool), 10)]
Function solution that doesn't iterate to check for repeats, just checks each add against the last number in the list:
import random
def get_random_list_without_neighbors(lower_limit, upper_limit, length):
res = []
# add the first number
res.append(random.randrange(lower_limit, upper_limit))
while len(res) < length:
x = random.randrange(lower_limit, upper_limit)
# check that the new number x doesn't match the last number in the list
if x != res[-1]:
res.append(x)
return res
>>> print(get_random_list_without_neighbors(0, 11, 10)
[10, 1, 2, 3, 1, 8, 6, 5, 6, 2]
def random_sequence_without_same_neighbours(n, min, max):
x = [random.randrange(min, max + 1)]
uniq_value_count = max - min + 1
next_choises_count = uniq_value_count - 1
for i in range(n - 1):
circular_shift = random.randrange(0, next_choises_count)
x.append(min + (x[-1] + circular_shift + 1) % uniq_value_count)
return x
random_sequence_without_same_neighbours(n=10, min=0, max=10)
It's not to much pythonic but you can do something like this
import random
def random_numbers_generator(n):
"Generate a list of random numbers but without two duplicate numbers in a row "
result = []
for _ in range(n):
number = random.randint(1, n)
if result and number == result[-1]:
continue
result.append(number)
return result
print(random_numbers_generator(10))
Result:
3, 6, 2, 4, 2, 6, 2, 1, 4, 7]
I have the following variables:
import numpy as np
gens = np.array([2, 1, 2, 1, 0, 1, 2, 1, 2])
p = [0,1]
I want to return the entries of gens that match each element of p.
So ideally I would like it to return:
result = [[4],[2,3,5,7],[0,2,6,8]]
#[[where matched 0], [where matched 1], [the rest]]
--
My attempts so far only work with one variable:
indx = gens.argsort()
res = np.searchsorted(gens[indx], [0])
gens[res] #gives 4, which is the position of 0
But I try with with
indx = gens.argsort()
res = np.searchsorted(gens[indx], [1])
gens[res] #gives 1, which is the position of the first 1.
So:
how can I search for an entry that has multiple occurrences
how can I search for multiple entries each of which have multiple occurrences?
You can use np.where
>>> np.where(gens == p[0])[0]
array([4])
>>> np.where(gens == p[1])[0]
array([1, 3, 5, 7])
>>> np.where((gens != p[0]) & (gens != p[1]))[0]
array([0, 2, 6, 8])
Or np.in1d and np.nonzero
>>> np.nonzero(np.in1d(gens, p[0]))[0]
>>> np.nonzero(np.in1d(gens, p[1]))[0]
>>> np.nonzero(~np.in1d(gens, p))[0]
The function np.array_split(x, n) splits the array x into n roughly equally sized chunks. I am wondering what the most convenient form of this if one wants to obtain the indices of where the array is to be split. So an array of
ix = [(start1, end1), (start2, end2), ... (startn, endn)]
such that
np.array_split(x, n)[i] == x[ix[i][0]:ix[i][1]]
I can think of a few awkward ways of obtaining this but nothing simple.
You know the lengths of the sub arrays. Just use them to find start and end indices:
a = np.arange(10)
res = np.array_split(a, 3)
end = list(np.add.accumulate([len(x) for x in res]))
start = [0] + end[:-1]
ix = list(zip(start, end))
Now, the indices are:
>>> ix
[(0, 4), (4, 7), (7, 10)]
for this result:
>>> res
[array([0, 1, 2, 3]), array([4, 5, 6]), array([7, 8, 9])]
or:
for i in range(3):
assert np.all(np.array_split(a, 3)[i] == a[ix[i][0]:ix[i][1]])
In the method below, I have assembled a list of tuples, while trying to ensure that none of the values are less than zero. The method below first takes a block and uses it to calculate the coordinates of neighboring blocks in increments of one and then proceeds to remove blocks that have either of their coordinates less than zero. My issue lies in the second stage as it does not remove two coordinates if I input a block with coordinates (0,0): (0,-1) and (-1,0).
The codes is as follows:
def get_block_neighbors(self, block):
neighbor_list = []
neighbor_list.append((block.x,block.y+1))
neighbor_list.append((block.x+1,block.y+1))
neighbor_list.append((block.x+1,block.y))
neighbor_list.append((block.x+1,block.y-1))
neighbor_list.append((block.x,block.y-1))
neighbor_list.append((block.x-1,block.y-1))
neighbor_list.append((block.x-1,block.y))
neighbor_list.append((block.x-1,block.y+1))
for item in neighbor_list: #each tuple item in the list
if item[0] < 0 or item[1] < 0:
print item
neighbor_list.remove(item)
print neighbor_list
get_block_neighbors(Block(Point(0,0),"green"))
for which I get the following output:
(1, -1)
(-1, -1)
(-1, 1)
[(0, 1), (1, 1), (1, 0), (0, -1), (-1, 0)]
Here, the first three lines are printouts of the tuples I would like to remove, while the last one is a list of the tuples that I have assembled. As you can see, the last two tuples have negative values for at least one of their coordinates. Ideally, I would want this:
(1, -1)
(-1, -1)
(-1, 1)
(0, -1)
(-1, 0)
[(0, 1), (1, 1), (1, 0)]
Curiously enough, when I remove/comment out the line neighbor_list.remove(item), I get a bit closer to where I need to be in one sense in that the method in its print-out includes the two tuples that I want removed. But of course, the one disadvantage of doing this is that I no longer remove any of the target tuples from this list.
Any help on this would be much appreciated and I really do hope that my oversight wasn't something super obvious. On a side note, I feel like there should be a way to assemble this list while being able to forego a removal stage, which is how I started coding this method before I just settled for the code above. Thanks!
The issue is that you're removing items from the list at the same time you're iterating over that list. List iteration happens by index (behind the scenes) so this doesn't work as you'd expect, as some values are skipped when their predecessors are removed.
To avoid this issue, you can iterate over a copy of the list, using a slice:
for item in neighbor_list[:]:
Or, better yet, use a list comprehension to build a new list instead of modifying the old one:
new_list = [(x, y) for x, y in neighbor_list if x >= 0 and y >= 0]
You shouldn't remove items from a list you're iterating over. Make a copy of the list first, and iterate over that instead.
The problem lies in the fact that you're removing elements of a list and at the same time you're iterating over it. It's simpler if you just create a new list with the elements removed:
[item for item in neighbor_list if item[0] >= 0 and item[1] >= 0]
Make a copy of the list first:
for item in neighbor_list[:]: #each tuple item in the list
if item[0] < 0 or item[1] < 0:
print item
neighbor_list.remove(item)
print neighbor_list
Much easier all around:
def get_block_neighbors(self, block):
neighbor_list = []
neighbor_list.append((block.x,block.y+1))
neighbor_list.append((block.x+1,block.y+1))
neighbor_list.append((block.x+1,block.y))
if block.y > 0:
neighbor_list.append((block.x+1,block.y-1))
neighbor_list.append((block.x,block.y-1))
if block.x > 0:
neighbor_list.append((block.x-1,block.y-1))
if block.x > 0:
neighbor_list.append((block.x-1,block.y))
neighbor_list.append((block.x-1,block.y+1))
return neighbor_list
Here is an example, showing some code that looks like it's trying to filter some numbers from a list
>>> L = range(10)
>>> for x in L:
... print x, L
... if x in (4,5,6,7):
... L.remove(x)
...
0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
3 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
4 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
6 [0, 1, 2, 3, 5, 6, 7, 8, 9]
8 [0, 1, 2, 3, 5, 7, 8, 9]
9 [0, 1, 2, 3, 5, 7, 8, 9]
Removing items while iterating over the list is generally a bad idea.
Here is a simpler way to get the list of neighbors. It avoids the need for .remove altogether
def get_block_neighbors(self, block):
x = block.x
y = block.y
xrange = (-1, 0, 1)[x<1:]
yrange = (-1, 0, 1)[y<1:]
return [(x + dx,y + dy) for dx in xrange for dy in yrange if dx or dy]
I am trying to program a standard snake draft, where team A pick, team B, team C, team C, team B, team A, ad nauseum.
If pick number 13 (or pick number x) just happened how can I figure which team picks next for n number of teams.
I have something like:
def slot(n,x):
direction = 'down' if (int(x/n) & 1) else 'up'
spot = (x % n) + 1
slot = spot if direction == 'up' else ((n+1) - spot)
return slot
I have feeling there is a simpler, more pythonic what than this solution. Anyone care to take a hack at it?
So I played around a little more. I am looking for the return of a single value, rather than the best way to count over a looped list. The most literal answer might be:
def slot(n, x): # 0.15757 sec for 100,000x
number_range = range(1, n+1) + range(n,0, -1)
index = x % (n*2)
return number_range[index]
This creates a list [1,2,3,4,4,3,2,1], figures out the index (e.g. 13 % (4*2) = 5), and then returns the index value from the list (e.g. 4). The longer the list, the slower the function.
We can use some logic to cut the list making in half. If we are counting up (i.e. (int(x/n) & 1) returns False), we get the obvious index value (x % n), else we subtract that value from n+1:
def slot(n, x): # 0.11982 sec for 100,000x
number_range = range(1, n+1) + range(n,0, -1)
index = ((n-1) - (x % n)) if (int(x/n) & 1) else (x % n)
return number_range[index]
Still avoiding a list altogether is fastest:
def slot(n, x): # 0.07275 sec for 100,000x
spot = (x % n) + 1
slot = ((n+1) - spot) if (int(x/n) & 1) else spot
return slot
And if I hold the list as variable rather than spawning one:
number_list = [1,2,3,4,5,6,7,8,9,10,11,12,12,11,10,9,8,7,6,5,4,3,2,1]
def slot(n, x): # 0.03638 sec for 100,000x
return number_list[x % (n*2)]
Why not use itertools cycle function:
from itertools import cycle
li = range(1, n+1) + range(n, 0, -1) # e.g. [1, 2, 3, 4, 4, 3, 2, 1]
it = cycle(li)
[next(it) for _ in xrange(10)] # [1, 2, 3, 4, 4, 3, 2, 1, 1, 2]
Note: previously I had answered how to run up and down, as follows:
it = cycle(range(1, n+1) + range(n, 0, -1)) #e.g. [1, 2, 3, 4, 3, 2, 1, 2, 3, ...]
Here's a generator that will fulfill what you want.
def draft(n):
while True:
for i in xrange(1,n+1):
yield i
for i in xrange(n,0,-1):
yield i
>>> d = draft(3)
>>> [d.next() for _ in xrange(12)]
[1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 1]
from itertools import chain, cycle
def cycle_up_and_down(first, last):
up = xrange(first, last+1, 1)
down = xrange(last, first-1, -1)
return cycle(chain(up, down))
turns = cycle_up_and_down(1, 4)
print [next(turns) for n in xrange(10)] # [1, 2, 3, 4, 4, 3, 2, 1, 1, 2]
Here is a list of numbers that counts up, then down:
>>> [ -abs(5-i)+5 for i in range(0,10) ]
[0, 1, 2, 3, 4, 5, 4, 3, 2, 1]
Written out:
count_up_to = 5
for i in range( 0, count_up_to*2 ):
the_number_you_care_about = -abs(count_up_to-i) + count_up_to
# do stuff with the_number_you_care_about
Easier to read:
>>> list( range(0,5) ) + list( range( 5, 0, -1 ) )
[0, 1, 2, 3, 4, 5, 4, 3, 2, 1]
Written out:
count_up_to = 5
for i in list( range(0,5) ) + list( range(5, 0, -1) ):
# i is the number you care about
Another way:
from itertools import chain
for i in chain( range(0,5), range(5,0,-1) ):
# i is the number you care about