ค้นหาระยะทางสู่ศูนย์ที่ใกล้ที่สุดใน NumPy Array


12

สมมติว่าฉันมีอาร์เรย์ NumPy:

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

ในแต่ละดัชนีฉันต้องการค้นหาระยะทางถึงค่าศูนย์ที่ใกล้ที่สุด หากตำแหน่งเป็นศูนย์นั้นให้ส่งกลับศูนย์เป็นระยะทาง หลังจากนั้นเราสนใจเฉพาะระยะทางไปยังศูนย์ที่ใกล้ที่สุดซึ่งอยู่ทางขวาของตำแหน่งปัจจุบัน วิธีการไร้เดียงสาสุดจะเป็นสิ่งที่ชอบ:

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

และผลลัพธ์จะเป็น:

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

ฉันสังเกตเห็นรูปแบบการนับถอยหลัง / ลดลงในผลลัพธ์ระหว่างศูนย์ ดังนั้นฉันอาจสามารถใช้ตำแหน่งของศูนย์ (เช่นzero_indices = np.argwhere(x == 0).flatten())

วิธีที่เร็วที่สุดในการรับเอาต์พุตที่ต้องการในเวลาเชิงเส้นคืออะไร


ถ้าไม่มี 0 ทางขวาล่ะ
Divakar

เป็นคำถามที่ดีมากแล้วจึงควรใช้ค่าเริ่มต้นเป็นดัชนีสุดท้าย (เช่นx.shape[0] - 1)
ลาว

คำตอบ:


8

วิธีที่ # 1: Searchsortedการช่วยเหลือเวลาเชิงเส้นในลักษณะเวกเตอร์ (ก่อนที่พวก numba จะเข้ามา)!

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

วิธีการ # 2:อีกด้วย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))

อีกทางเลือกหนึ่งขั้นตอนสุดท้ายของcumsumอาจถูกแทนที่ด้วยrepeatฟังก์ชั่น -

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

วิธีการ # 3:อีกด้วยส่วนใหญ่เพียง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

คุณสามารถทำงานได้จากอีกด้านหนึ่ง เก็บตัวนับจำนวนตัวเลขที่ไม่เป็นศูนย์และส่งไปยังองค์ประกอบในอาร์เรย์ หากคุณเห็น 0 ให้รีเซ็ตตัวนับเป็น 0

แก้ไข: หากไม่มีศูนย์ทางด้านขวาคุณต้องตรวจสอบอีกครั้ง

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

คุณสามารถใช้ความแตกต่างระหว่างดัชนีของแต่ละตำแหน่งและค่าสูงสุดสะสมของตำแหน่งศูนย์เพื่อกำหนดระยะทางไปยังศูนย์ก่อนหน้า สิ่งนี้สามารถทำได้ไปข้างหน้าและข้างหลัง ค่าต่ำสุดระหว่างระยะทางไปข้างหน้าและข้างหลังถึงศูนย์ก่อนหน้า (หรือถัดไป) จะใกล้ที่สุด:

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)

ผล:

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]

กรณีพิเศษที่ไม่มีเลขศูนย์ปรากฏบนขอบด้านนอก:

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]

ยังใช้งานได้โดยไม่มีเลขศูนย์เลย

[แก้ไข]  โซลูชั่นที่ไม่ยุ่งยาก ...

หากคุณกำลังมองหาวิธีการแก้ปัญหา O (N) ที่ไม่จำเป็นต้องมีจำนวนมากคุณสามารถใช้กลยุทธ์นี้โดยใช้ฟังก์ชั่นการสะสมจาก 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)

เอาท์พุท:

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]

หากคุณไม่ต้องการใช้ห้องสมุดใด ๆ คุณสามารถสะสมระยะทางด้วยตนเองในการวนซ้ำ:

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)

เอาท์พุท:

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

สัญชาตญาณแรกของฉันคือใช้การแบ่งส่วน ถ้า x สามารถเป็นรายการปกติแทนอาร์เรย์ numpy คุณสามารถใช้

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

หากจำเป็นต้องใช้จำนวนมากคุณสามารถใช้

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

แต่สิ่งนี้มีประสิทธิภาพน้อยกว่าเพราะคุณกำลังค้นหาตำแหน่งศูนย์ทั้งหมดทางด้านขวาของค่าแล้วดึงออกมาก่อน เกือบจะเป็นวิธีที่ดีกว่าในการทำสิ่งนี้ด้วย numpy


0

แก้ไข: ฉันขอโทษฉันเข้าใจผิด นี่จะทำให้คุณมีระยะทางไปยังศูนย์ที่ใกล้ที่สุด - อาจอยู่ทางซ้ายหรือขวา แต่คุณสามารถใช้d_rightเป็นผลลัพธ์ระดับกลางได้ กรณีนี้ไม่ครอบคลุมถึงขอบกรณีที่ไม่มีศูนย์ทางขวา

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])
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.