Tìm khoảng cách đến số 0 gần nhất trong NumPy Array


12

Giả sử tôi có một mảng NumPy:

x = np.array([0, 1, 2, 0, 4, 5, 6, 7, 0, 0])

Ở mỗi chỉ số, tôi muốn tìm khoảng cách đến giá trị 0 gần nhất. Nếu vị trí là một số 0 thì trả về 0 dưới dạng khoảng cách. Sau đó, chúng tôi chỉ quan tâm đến khoảng cách đến số 0 gần nhất ở bên phải của vị trí hiện tại. Cách tiếp cận siêu ngây thơ sẽ là một cái gì đó như:

out = np.full(x.shape[0], x.shape[0]-1)
for i in range(x.shape[0]):
    j = 0
    while i + j < x.shape[0]:
        if x[i+j] == 0:
            break
        j += 1
    out[i] = j

Và đầu ra sẽ là:

array([0, 2, 1, 0, 4, 3, 2, 1, 0, 0])

Tôi nhận thấy mô hình đếm ngược / giảm dần ở đầu ra ở giữa các số không. Vì vậy, tôi có thể sử dụng vị trí của các số không (nghĩa là zero_indices = np.argwhere(x == 0).flatten())

Cách nhanh nhất để có được đầu ra mong muốn trong thời gian tuyến tính là gì?


Nếu không có 0 ở bên phải thì sao?
Divakar

Câu hỏi tuyệt vời, sau đó nó nên mặc định là chỉ mục cuối cùng (nghĩa là x.shape[0] - 1)
slaw

Câu trả lời:


8

Cách tiếp cận số 1: Searchsorted giải cứu thời gian tuyến tính theo cách véc tơ (trước khi những kẻ numba bước vào)!

mask_z = x==0
idx_z = np.flatnonzero(mask_z)
idx_nz = np.flatnonzero(~mask_z)

# Cover for the case when there's no 0 left to the right
# (for same results as with posted loop-based solution)
if x[-1]!=0:
    idx_z = np.r_[idx_z,len(x)]

out = np.zeros(len(x), dtype=int)
idx = np.searchsorted(idx_z, idx_nz)
out[~mask_z] = idx_z[idx] - idx_nz

Cách tiếp cận # 2: Khác với một số cumsum-

mask_z = x==0
idx_z = np.flatnonzero(mask_z)

# Cover for the case when there's no 0 left to the right
if x[-1]!=0:
    idx_z = np.r_[idx_z,len(x)]

out = idx_z[np.r_[False,mask_z[:-1]].cumsum()] - np.arange(len(x))

Ngoài ra, bước cuối cùng cumsumcó thể được thay thế bằng repeatchức năng -

r = np.r_[idx_z[0]+1,np.diff(idx_z)]
out = np.repeat(idx_z,r)[:len(x)] - np.arange(len(x))

Cách tiếp cận # 3: Khác với chủ yếu là cumsum-

mask_z = x==0
idx_z = np.flatnonzero(mask_z)

pp = np.full(len(x), -1)
pp[idx_z[:-1]] = np.diff(idx_z) - 1
if idx_z[0]==0:
    pp[0] = idx_z[1]
else:
    pp[0] = idx_z[0]
out = pp.cumsum()

# Handle boundary case and assigns 0s at original 0s places
out[idx_z[-1]:] = np.arange(len(x)-idx_z[-1],0,-1)
out[mask_z] = 0

4

Bạn có thể làm việc từ phía bên kia. Giữ một bộ đếm về số lượng chữ số khác không đã vượt qua và gán nó cho phần tử trong mảng. Nếu bạn thấy 0, đặt lại bộ đếm về 0

Chỉnh sửa: nếu không có số 0 ở bên phải, thì bạn cần kiểm tra lại

x = np.array([0, 1, 2, 0, 4, 5, 6, 7, 0, 0])
out = x 
count = 0 
hasZero = False 
for i in range(x.shape[0]-1,-1,-1):
    if out[i] != 0:
        if not hasZero: 
            out[i] = x.shape[0]-1
        else:
            count += 1
            out[i] = count
    else:
        hasZero = True
        count = 0
print(out)

2

Bạn có thể sử dụng chênh lệch giữa các chỉ số của từng vị trí và tối đa tích lũy của các vị trí 0 để xác định khoảng cách đến 0 trước đó. Điều này có thể được thực hiện về phía trước và phía sau. Khoảng cách tối thiểu giữa khoảng cách tiến và lùi đến 0 trước (hoặc tiếp theo) sẽ là gần nhất:

import numpy as np

indices  = np.arange(x.size)
zeroes   = x==0
forward  = indices - np.maximum.accumulate(indices*zeroes)  # forward distance
forward[np.cumsum(zeroes)==0] = x.size-1                    # handle absence of zero from edge
forward  = forward * (x!=0)                                 # set zero positions to zero                

zeroes   = zeroes[::-1]
backward = indices - np.maximum.accumulate(indices*zeroes) # backward distance
backward[np.cumsum(zeroes)==0] = x.size-1                  # handle absence of zero from edge
backward = backward[::-1] * (x!=0)                         # set zero positions to zero

distZero = np.minimum(forward,backward) # closest distance (minimum)

các kết quả:

distZero
# [0, 1, 1, 0, 1, 2, 2, 1, 0, 0]

forward
# [0, 1, 2, 0, 1, 2, 3, 4, 0, 0]

backward
# [0, 2, 1, 0, 4, 3, 2, 1, 0, 0]

Trường hợp đặc biệt không có số 0 ở các cạnh bên ngoài:

x = np.array([3, 1, 2, 0, 4, 5, 6, 0,8,8])

forward:  [9 9 9 0 1 2 3 0 1 2]
backward: [3 2 1 0 3 2 1 0 9 9]
distZero: [3 2 1 0 1 2 1 0 1 2]

cũng hoạt động mà không có số không

[EDIT]  giải pháp không khó chịu ...

nếu bạn đang tìm kiếm một giải pháp O (N) không yêu cầu numpy, bạn có thể áp dụng chiến lược này bằng cách sử dụng hàm tích lũy từ itertools:

x = [0, 1, 2, 0, 4, 5, 6, 7, 0, 0]

from itertools import accumulate

maxDist  = len(x) - 1
zeroes   = [maxDist*(v!=0) for v in x]
forward  = [*accumulate(zeroes,lambda d,v:min(maxDist,(d+1)*(v!=0)))]
backward = accumulate(zeroes[::-1],lambda d,v:min(maxDist,(d+1)*(v!=0)))
backward = [*backward][::-1]
distZero = [min(f,b) for f,b in zip(forward,backward)]                      

print("x",x)
print("f",forward)
print("b",backward)
print("d",distZero)

đầu ra:

x [0, 1, 2, 0, 4, 5, 6, 7, 0, 0]
f [0, 1, 2, 0, 1, 2, 3, 4, 0, 0]
b [0, 2, 1, 0, 4, 3, 2, 1, 0, 0]
d [0, 1, 1, 0, 1, 2, 2, 1, 0, 0]

Nếu bạn không muốn sử dụng bất kỳ thư viện nào, bạn có thể tích lũy khoảng cách theo cách thủ công trong một vòng lặp:

x = [0, 1, 2, 0, 4, 5, 6, 7, 0, 0]
forward,backward = [],[]
fDist = bDist = maxDist = len(x)-1
for f,b in zip(x,reversed(x)):
    fDist = min(maxDist,(fDist+1)*(f!=0))
    forward.append(fDist)
    bDist = min(maxDist,(bDist+1)*(b!=0))
    backward.append(bDist)
backward = backward[::-1]
distZero = [min(f,b) for f,b in zip(forward,backward)]

print("x",x)
print("f",forward)
print("b",backward)
print("d",distZero)

đầu ra:

x [0, 1, 2, 0, 4, 5, 6, 7, 0, 0]
f [0, 1, 2, 0, 1, 2, 3, 4, 0, 0]
b [0, 2, 1, 0, 4, 3, 2, 1, 0, 0]
d [0, 1, 1, 0, 1, 2, 2, 1, 0, 0]

0

Trực giác đầu tiên của tôi sẽ là sử dụng cắt lát. Nếu x có thể là một danh sách bình thường thay vì một mảng numpy, thì bạn có thể sử dụng

 out = [x[i:].index(0) for i,_ in enumerate(x)]

Nếu cần thiết thì bạn có thể sử dụng

 out = [np.where(x[i:]==0)[0][0] for i,_ in enumerate(x)]

nhưng điều này kém hiệu quả hơn vì bạn đang tìm tất cả các vị trí 0 ở bên phải của giá trị và sau đó rút ra đầu tiên. Hầu như chắc chắn là một cách tốt hơn để làm điều này trong numpy.


0

Chỉnh sửa: Tôi xin lỗi, tôi hiểu lầm. Điều này sẽ cung cấp cho bạn khoảng cách đến các số 0 gần nhất - có thể nó ở bên trái hoặc bên phải. Nhưng bạn có thể sử dụng d_rightnhư kết quả trung gian. Điều này không bao gồm trường hợp cạnh của việc không có bất kỳ số 0 nào ở bên phải.

import numpy as np

x = np.array([0, 1, 2, 0, 4, 5, 6, 7, 0, 0])

# Get the distance to the closest zero from the left:
zeros = x == 0
zero_locations = np.argwhere(x == 0).flatten()
zero_distances = np.diff(np.insert(zero_locations, 0, 0))

temp = x.copy()
temp[~zeros] = 1
temp[zeros] = -(zero_distances-1)
d_left = np.cumsum(temp) - 1

# Get the distance to the closest zero from the right:
zeros = x[::-1] == 0
zero_locations = np.argwhere(x[::-1] == 0).flatten()
zero_distances = np.diff(np.insert(zero_locations, 0, 0))

temp = x.copy()
temp[~zeros] = 1
temp[zeros] = -(zero_distances-1)
d_right = np.cumsum(temp) - 1
d_right = d_right[::-1]

# Get the smallest distance from both sides:
smallest_distances = np.min(np.stack([d_left, d_right]), axis=0)
# np.array([0, 1, 1, 0, 1, 2, 2, 1, 0, 0])
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.