簡體   English   中英

Map 多維數組中的一個元素到它的索引

[英]Map an element in a multi-dimension array to its index

我正在使用 function get_tuples(length, total)這里生成給定長度和總和的所有元組的數組,一個示例和 function 如下所示。 創建數組后,我需要找到一種方法來返回數組中給定數量元素的索引。 通過將數組更改為列表,我可以使用.index()來做到這一點,如下所示。 但是,此解決方案或其他基於搜索的解決方案(例如使用np.where )需要大量時間來查找索引。 由於數組中的所有元素(示例中的數組s )都是不同的,我想知道我們是否可以構造一個一對一的映射,即 function 使得給定數組中的元素它返回元素的索引通過對該元素的值進行一些加法和乘法。 如果可能的話,有什么想法嗎? 謝謝!

import numpy as np

def get_tuples(length, total):
    if length == 1:
        yield (total,)
        return

    for i in range(total + 1):
        for t in get_tuples(length - 1, total - i):
            yield (i,) + t
#example
s = np.array(list(get_tuples(4, 20)))

# array s
In [1]: s
Out[1]: 
array([[ 0,  0,  0, 20],
       [ 0,  0,  1, 19],
       [ 0,  0,  2, 18],
       ...,
       [19,  0,  1,  0],
       [19,  1,  0,  0],
       [20,  0,  0,  0]])

#example of element to find the index for. (Note in reality this is 1000+ elements)
elements_to_find =np.array([[ 0,  0,  0, 20],
                            [ 0,  0,  7, 13],
                            [ 0,  5,  5, 10],
                            [ 0,  0,  5, 15],
                            [ 0,  2,  4, 14]])
#change array to list
s_list = s.tolist()

#find the indices
indx=[s_list.index(i) for i in elements_to_find.tolist()]

#output
In [2]: indx
Out[2]: [0, 7, 100, 5, 45]

這是一個僅基於元組計算索引的公式,即它不需要看到完整的數組。 要計算 N 元組的索引,它需要評估 N-1 個二項式系數。 以下實現是(部分)矢量化的,它接受 ND 數組,但元組必須在最后一維中。

import numpy as np
from scipy.special import comb

# unfortunately, comb with option exact=True is not vectorized
def bc(N,k):
    return np.round(comb(N,k)).astype(int)

def get_idx(s):
    N = s.shape[-1] - 1
    R = np.arange(1,N)
    ps = s[...,::-1].cumsum(-1)
    B = bc(ps[...,1:-1]+R,1+R)
    return bc(ps[...,-1]+N,N) - ps[...,0] - 1 - B.sum(-1)

# OP's generator
def get_tuples(length, total):
    if length == 1:
        yield (total,)
        return

    for i in range(total + 1):
        for t in get_tuples(length - 1, total - i):
            yield (i,) + t
#example
s = np.array(list(get_tuples(4, 20)))

# compute each index
r = get_idx(s)

# expected: 0,1,2,3,...
assert (r == np.arange(len(r))).all()
print("all ok")

#example of element to find the index for. (Note in reality this is 1000+ elements)
elements_to_find =np.array([[ 0,  0,  0, 20],
                            [ 0,  0,  7, 13],
                            [ 0,  5,  5, 10],
                            [ 0,  0,  5, 15],
                            [ 0,  2,  4, 14]])

print(get_idx(elements_to_find))

樣品運行:

all ok
[  0   7 100   5  45]

如何推導出公式:

  1. 使用星形和條形表示完整的分區計數#part(N,k) (N 是總數,k 是長度)作為單個二項式系數(N + k - 1) choose (k - 1)

  2. 從后到前計數:不難驗證在 OP 生成器的外循環的第 i 次完整迭代之后, #part(Ni,k)還沒有被枚舉。 實際上,剩下的就是所有分區 p1+p2+... = N with p1>=i; 我們可以寫 p1=q1+i 使得 q1+p2+... = Ni 並且后一個分區是無約束的,所以我們可以使用 1. 來計數。

您可以使用二分搜索來加快搜索速度。

二分搜索使搜索 O(log(n)) 而不是 O(n)(使用索引)

我們不需要對元組進行排序,因為它們已經由生成器排序

import bisect

def get_tuples(length, total):
  " Generates tuples "
  if length == 1:
    yield (total,)
    return

  yield from ((i,) + t for i in range(total + 1) for t in get_tuples(length - 1, total - i))

def find_indexes(x, indexes):
   if len(indexes) > 100:
        # Faster to generate all indexes when we have a large
        # number to check
        d = dict(zip(x, range(len(x))))
        return [d[tuple(i)] for i in indexes]
    else:
        return [bisect.bisect_left(x, tuple(i)) for i in indexes]

# Generate tuples (in this case 4, 20)
x = list(get_tuples(4, 20))

# Tuples are generated in sorted order [(0,0,0,20), ...(20,0,0,0)]
# which allows binary search to be used
indexes = [[ 0,  0,  0, 20],
           [ 0,  0,  7, 13],
           [ 0,  5,  5, 10],
           [ 0,  0,  5, 15],
           [ 0,  2,  4, 14]]

y = find_indexes(x, indexes)
print('Found indexes:', *y)
print('Indexes & Tuples:')
for i in y:
  print(i, x[i])

Output

Found indexes: 0 7 100 5 45
Indexes & Tuples:
0 (0, 0, 0, 20)
7 (0, 0, 7, 13)
100 (0, 5, 5, 10)
5 (0, 0, 5, 15)
45 (0, 2, 4, 14)

表現

場景1——元組已經計算,我們只想找到某些元組的索引

例如 x = list(get_tuples(4, 20)) 已經被執行。

搜索

indexes = [[ 0,  0,  0, 20],
           [ 0,  0,  7, 13],
           [ 0,  5,  5, 10],
           [ 0,  0,  5, 15],
           [ 0,  2,  4, 14]]

二進制搜索

%timeit find_indexes(x, indexes)
100000 loops, best of 3: 11.2 µs per loop

僅基於元組計算索引(禮貌@PaulPanzer 方法)

%timeit get_idx(indexes)
10000 loops, best of 3: 92.7 µs per loop

在這種情況下,當元組已經被預先計算時,二分查找的速度要快 8 倍。

場景 2——元組沒有被預先計算。

%%timeit
import bisect

def find_indexes(x, t):
    " finds the index of each tuple in list t (assumes x is sorted) "
    return [bisect.bisect_left(x, tuple(i)) for i in t]

# Generate tuples (in this case 4, 20)
x = list(get_tuples(4, 20))

indexes = [[ 0,  0,  0, 20],
           [ 0,  0,  7, 13],
           [ 0,  5,  5, 10],
           [ 0,  0,  5, 15],
           [ 0,  2,  4, 14]]

y = find_indexes(x, indexes)

100 loops, best of 3: 2.69 ms per loop

@PaulPanzer 方法在這種情況下是相同的時間(92.97 us)

=> @PaulPanzer 在不必計算元組時方法快約 29 倍

場景3——大量索引(@PJORR)大量隨機索引產生

x = list(get_tuples(4, 20))
xnp = np.array(x)
indices = xnp[np.random.randint(0,len(xnp), 2000)]
indexes = indices.tolist()
%timeit find_indexes(x, indexes)
#Result: 1000 loops, best of 3: 1.1 ms per loop
%timeit get_idx(indices)
#Result: 1000 loops, best of 3: 716 µs per loop

在這種情況下,我們 @PaulPanzer 快了 53%

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM