Generate a list based on the index of another list - python

I have a list:
hash_table = [1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1]
I want to change this to:
result = [[0, 0], [1, 2], [4, 5]]
How to generate:
array: [1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1]
map: 0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0
# start to end, generate the result like `[int(start), int(end)]`
combine them:[[0, 0], [1, 2], [4, 5]]
0 and 1 wouldn't appear in pairs. So the numbers in result must be an integer.
What I have tried:
hash_table = [1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1]
output = [[]]
for pre, next_ in zip(hash_table, hash_table[1:]):
output[-1].append(pre)
if {next_, pre} == {0, 1}:
output.append([])
output[-1].append(hash_table[-1])
# the output is [[1], [0], [1, 1, 1], [0, 0, 0], [1, 1, 1]]
start = index = 0
result = []
while index < len(output):
# output[index]
if output[0] != 0:
res.append([start, math.ceil(len(output[index]))])
# I don't know how to handle the list "output".
# I couldn't know it. My mind has gone blank
start += len(output[index])/2
Any good ideas? I thought I made it too complicated.

You can use itertools.groupby to group the 0s and 1s:
import itertools
hash_table = [1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1]
result = []
cur_ind = 0
for (val, vals) in itertools.groupby(hash_table):
vals = list(vals) # itertools doesn't make it a list by default
old_ind = cur_ind
cur_ind += len(vals)
if val == 0:
continue
result.append([old_ind // 2, (cur_ind - 1) // 2])
print(result)
Essentially, itertools.groupby will give an iterator of [(1, [1]), (0, [0]), (1, [1, 1, 1]), (0, [0, 0, 0]), (1, [1, 1, 1])] (more or less). We can iterate through this iterator and keep track if the index we're on by adding the length of the sublist to the current index. If the value is 1, then we have a run of ones so we append it to the results. The old_ind // 2 is integer division and is equivalent to int(old_ind / 2).

You could use groupby from itertools library:
import itertools
hash_table = [1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1]
s = "".join(map(str, hash_table)) # s = "10111000111"
gs = [(i, list(g)) for i, g in itertools.groupby(s)]
idx, result = 0, []
for i, g in gs: # i can be '1' or '0' (i.e, if the group consist in 1's or 0's)
if i == '1':
result.append([idx/2, (idx + len(g) - 1)/2])
idx += len(g)
return result

Related

How to convert list of list binary into list of decimal?

LIST OF LIST BIN DIVIDED INTO 8 : [[0, 1, 1, 0, 0, 1, 0, 1], [0, 1, 1, 1, 0, 1, 1, 1]]
the output I want is:
[101, 119]
This is more complex but significantly faster than any kind of string manipulation as it's essentially just integer arithmetic.
from timeit import timeit
lob = [[0, 1, 1, 0, 0, 1, 0, 1], [0, 1, 1, 1, 0, 1, 1, 1]]
def v1():
result = []
for e in lob:
r = 0
for _e in e:
r = r * 2 + _e
result.append(r)
return result
def v2():
return [int(''.join([str(y) for y in x]), 2) for x in lob]
assert v1() == v2()
for func in v1, v2:
print(func.__name__, timeit(func))
Output:
v1 0.6906622060014342
v2 2.173182999999881

Fast way to find length and start index of repeated elements in array

I have an array A:
import numpy as np
A = np.array( [0, 0, 1, 1, 1, 0, 1, 1, 0 ,0, 1, 0] )
The length of consecutive '1s' would be:
output: [3, 2, 1]
with the corresponding starting indices:
idx = [2, 6, 10]
The original arrays are huge and I prefer a solution with less for-loop.
Edit (Run time):
import numpy as np
import time
A = np.array( [0, 0, 1, 1, 1, 0, 1, 1, 0 ,0, 1, 0] )
def LoopVersion(A):
l_A = len(A)
size = []
idx = []
temp_idx = []
temp_size = []
for i in range(l_A):
if A[i] == 1:
temp_size.append(1)
if not temp_idx:
temp_idx = i
idx.append(temp_idx)
else:
size.append( len(temp_size) )
size = [i for i in size if i != 0]
temp_size = []
temp_idx = []
return size, idx
Quang's solution:
def UniqueVersion(A):
_, idx, counts = np.unique(np.cumsum(1-A)*A, return_index=True, return_counts=True)
return idx, counts
Jacco's solution:
def ConcatVersion(A):
A = np.concatenate(([0], A, [0])) # get rid of some edge cases
starts = np.argwhere((A[:-1] + A[1:]) == 1).ravel()[::2]
ends = np.argwhere((A[:-1] + A[1:]) == 1).ravel()[1::2]
len_of_repeats = ends - starts
return starts, len_of_repeats
Dan's solution (works with special cases as well):
def structure(A):
ZA = np.concatenate(([0], A, [0]))
indices = np.flatnonzero( ZA[1:] != ZA[:-1] )
counts = indices[1:] - indices[:-1]
return indices[::2], counts[::2]
Run time analysis with 10000 elements:
np.random.seed(1234)
B = np.random.randint(2, size=10000)
start = time.time()
size, idx = LoopVersion(B)
end = time.time()
print ( (end - start) )
# 0.32489800453186035 seconds
start = time.time()
idx, counts = UniqueVersion(B)
end = time.time()
print ( (end - start) )
# 0.008305072784423828 seconds
start = time.time()
idx, counts = ConcatVersion(B)
end = time.time()
print ( (end - start) )
# 0.0009801387786865234 seconds
start = time.time()
idx, counts = structure(B)
end = time.time()
print ( (end - start) )
# 0.000347137451171875 seconds
Let's try unique:
_, idx, counts = np.unique(np.cumsum(1-A)*A, return_index=True, return_counts=True)
# your expected output:
idx, counts
Output:
(array([ 2, 6, 10]), array([3, 2, 1]))
Here is a pedestrian try, solving the problem by programming the problem.
We prepend and also append a zero to A, getting a vector ZA, then detect the 1 islands, and the 0 islands coming in alternating manner in the ZA by comparing the shifted versions ZA[1:] and ZA[-1]. (In the constructed arrays we take the even places, corresponding to the ones in A.)
import numpy as np
def structure(A):
ZA = np.concatenate(([0], A, [0]))
indices = np.flatnonzero( ZA[1:] != ZA[:-1] )
counts = indices[1:] - indices[:-1]
return indices[::2], counts[::2]
Some sample runs:
In [71]: structure(np.array( [0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0] ))
Out[71]: (array([ 2, 6, 10]), array([3, 2, 1]))
In [72]: structure(np.array( [1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1] ))
Out[72]: (array([ 0, 5, 9, 13, 15]), array([3, 3, 2, 1, 1]))
In [73]: structure(np.array( [1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0] ))
Out[73]: (array([0, 5, 9]), array([3, 3, 2]))
In [74]: structure(np.array( [1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1] ))
Out[74]: (array([ 0, 2, 5, 7, 11, 14]), array([1, 2, 1, 3, 2, 3]))
You can use the fact that the indexes of '1s' provide all information you need. It's enough to find starts and ends of series of '1s'.
A = np.concatenate(([0], A, [0])) # get rid of some edge cases
diff = np.argwhere((A[:-1] + A[1:]) == 1).ravel()
starts = diff[::2]
ends = diff[1::2]
print(starts, ends - starts)
Consider also the more-itertools locate and consecutive_groups tools.
Given
import more_itertools as mit
data = [0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0] # or numpy array
Code
indices = list(mit.locate(data))
group_lengths = [len(list(group)) for group in mit.consecutive_groups(indices)]
starting_indices = [list(group)[0] for group in mit.consecutive_groups(indices)]
Results
group_lengths
# [3, 2, 1]
starting_indices
# [2, 6, 10]
Details
locate yields indices of every 1
consecutive_groups yields groups of runs, e.g. (1, 2, 3), (8, 9)
the groups are iterators, from which you get the length and first index
more-itertools is a third-party library. > pip install more-itertools

i have a sequence of list with 1 and 0 and i would like output in this format where sub lists are created until zero is met

This is the input and the output should be as shown. This is what i have tried:
input a = [1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0]
output a = [[1,1,0],[1,1,1,0],[1,0],[1,1,0]]
def new_list(x):
new = []
for item in range(length(x)):
if x[item]== 0:
new.append(x[:item+1])
return new
First extract to inides of 0 elements. Then use it to slice the original list:
a = [1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0]
def new_list(x):
indices = [i for i,e in enumerate(x) if e == 0] # indices of zeros
indices = [0] + [i+1 for i in indices] # 0 is 1st slice, +1 is for slicing (1 higher than index)
ii1,ii2 = indices[:-1],indices[1:] # first and second index for each sub-list
return [x[i1:i2] for i1,i2 in zip(ii1,ii2)]
print(new_list(a))
Few modifications in your function to get your required output.
def new_list(x):
new = []
slice_idx = 0
for index, val in enumerate(x):
if val == 0:
new.append(x[slice_idx:index+1])
slice_idx = index +1
return new
Input: a = [1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0]
new_list(a)
[[1, 1, 0], [1, 1, 1, 0], [1, 0], [1, 1, 0]]

Most efficient way of comparing each element in a 2D numpy array to its 8 neighbours

So in a binary array I'm trying to find the points where a 0 and a 1 are next to each other, and redraw the array with these crossover points indicated by modifying the 0 value. Just wondering if there's a better way of comparing each of the values in a numpy array to the 8 surrounding values than using nested for loops.
Currently I have this, which compares to 4 surrounding just for readability here
for x in range(1, rows - 1):
for y in range(1, columns - 1):
if f2[x, y] == 0:
if f2[x-1, y] == 1 or f2[x+1, y] == 1 or f2[x, y-1] == 1 or f2[x, y+1] == 1:
f2[x, y] = 2
EDIT
For example
[[1, 1, 1, 1, 1, 1, 1],
[1, 1, 0, 0, 0, 1, 1],
[1, 1, 0, 0, 0, 1, 1],
[1, 1, 0, 0, 0, 1, 1],
[1, 1, 1, 1, 1, 1, 1]]
to
[[1, 1, 1, 1, 1, 1, 1],
[1, 1, 2, 2, 2, 1, 1],
[1, 1, 2, 0, 2, 1, 1],
[1, 1, 2, 2, 2, 1, 1],
[1, 1, 1, 1, 1, 1, 1]]
This problem can be solved quickly with binary morphology functions
import numpy as np
from scipy.ndimage.morphology import binary_dilation, generate_binary_structure
# Example array
f2 = np.zeros((5,5), dtype=float)
f2[2,2] = 1.
# This line determines the connectivity (all 8 neighbors or just 4)
struct_8_neighbors = generate_binary_structure(2, 2)
# Replace cell with maximum of neighbors (True if any neighbor != 0)
has_neighbor = binary_dilation(f2 != 0, structure=struct_8_neighbors)
# Was cell zero to begin with
was_zero = f2 == 0
# Update step
f2[has_neighbor & was_zero] = 2.

In python randomly concatenating two arrays until limit length is met

Say i have two arrays:
a = [1, 1, 1] and
b = [0, 0]
I want to concatenate these randomly in a seperate variable c, until i reach some desired length of c. So something like:
N = 10
c = random_concatenating((a, b), N)
[1, 1, 1, 0, 0, 1, 1, 1, 0, 0]
A verbose version:
import random
a = [1, 1, 1]
b = [0, 0]
N = 10
def random_concatenating(iterable, n):
rv = []
l = [*iterable]
print(l)
while len(rv) < n:
rv.extend(random.choice(l))
return rv[:n]
c = random_concatenating((a, b), N)
print(c)
Prints (for example):
[1, 1, 1, 0, 0, 1, 1, 1, 1, 1]
You can use itertools.cycle to repeat the lists as many times as you need then slice them to the needed list
>>> from itertools import cycle, islice
>>> a = [1, 1, 1]
>>> b = [0, 0]
>>> N = 10
>>> list(islice(cycle(a+b), N))
[1, 1, 1, 0, 0, 1, 1, 1, 0, 0]
This is one approach. Using random.choice
import random
a = [1, 1, 1]
b = [0, 0]
N = 10
foo = a + b
c = []
for i in range(N):
c.append(random.choice(foo))
print(c)
Output:
[0, 1, 1, 1, 1, 0, 1, 1, 1, 1]
One-line
c = [random.choice(foo) for i in range(N)]

Categories