【问题标题】:Map an element in a multi-dimension array to its index将多维数组中的元素映射到其索引
【发布时间】:2019-10-06 23:11:30
【问题描述】:

我正在使用来自here 的函数get_tuples(length, total) 要生成给定长度和总和的所有元组的数组,下面显示了一个示例和函数。创建数组后,我需要找到一种方法来返回数组中给定数量元素的索引。我可以使用.index() 通过将数组更改为列表来做到这一点,如下所示。但是,此解决方案或同样基于搜索的其他解决方案(例如使用np.where)需要花费大量时间来查找索引。由于数组中的所有元素(示例中的数组s)都是不同的,我想知道我们是否可以构造一个一对一的映射,即一个函数,给定数组中的元素,它返回的索引通过对该元素的值进行一些加法和乘法来计算该元素。如果可能的话,有什么想法吗?谢谢!

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]

【问题讨论】:

  • 你可以访问get_tuples()的输入吗?
  • 你是指例子中的参数4和20吗?是的,我设置了这些。
  • 好的,但是您为什么不能只计算元素的出现顺序以使用该信息查找?
  • 你会怎么做?这正是我想要找到的。应该有一个公式给出数组中的一个元素,例如[0, 0, 0, 20] 会返回 0,...等

标签: python arrays numpy indexing


【解决方案1】:

这是一个仅基于元组计算索引的公式,即它不需要看到完整的数组。要计算 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. 使用stars and bars 将完整分区计数#part(N,k)(N 是总数,k 是长度)表示为单个二项式系数(N + k - 1) choose (k - 1)。

  2. 从后到前计数:不难验证在 OP 生成器的外部循环的第 i 次完整迭代之后,还没有枚举出恰好 #part(N-i,k)。实际上,剩下的就是所有分区 p1+p2+... = N with p1>=i;我们可以写 p1=q1+i 使得 q1+p2+... = N-i 并且后一个分区是无约束的,所以我们可以使用 1. 来计数。

【讨论】:

  • 或许对get_idx()进行更多解释会更好
  • 我发现你可以通过使用binom 而不是comb 稍微加快速度。 from scipy.special import binomdef bc(N,k): return np.round(binom(N,k)).astype(int)
【解决方案2】:

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

二分搜索使得搜索 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])

输出

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%

【讨论】:

  • find_indexes函数是什么?
  • @PJORR--谢谢,我不知何故错过了包含它,因为它只有一行。
  • 谢谢!从我的实验中,我发现@PaulPanzer 方法在indexes 的长度为1000+ 时快3 倍,即使x = list(get_tuples(4, 20)) 已经执行。可能这两种方法的使用应根据要找到的元素数量来决定。
  • @PJORR——很有趣。但是,我的测试是使用具有 1721 个索引的 get_tuples(4, 20) 进行的。想知道为什么在那种情况下我的测量结果表明二分法更快。我的测量是使用代码,如使用 Jupyter 笔记本所示。您是否使用了更新后的算法,我删除了排序,因为它是不必要的?
  • 我不是在谈论'get_tuples(4,20)'。我的意思是尝试上述实验,索引(查找其索引的元素)的形状为(2000,4) 而不是(5,4)。例如:xnp = np.array(x)、indices = xnp[np.random.randint(0,len(xnp),2000)]、indexes = indices.tolist(),然后比较%timeit get_idx(indices)和%timeit find_indexes(x,indexes)。
猜你喜欢
  • 2013-06-11
  • 2011-08-07
  • 1970-01-01
  • 1970-01-01
  • 2016-07-02
  • 1970-01-01
  • 1970-01-01
  • 2012-12-10
  • 1970-01-01
相关资源
最近更新 更多