簡體   English   中英

在 Python 列表中大量查找隨機索引的最快方法是什么?

[英]What's the fastest way of finding a random index in a Python list, a large number of times?

從列表中提取隨機值的最佳(最快)方法是大量(> 1M)次?

我目前處於一種情況,我有一個表示為鄰接列表的圖,其內部列表的長度可能有很大不同(在 [2,可能是 100k] 的范圍內)。

我需要遍歷這個列表來生成隨機游走,所以我目前的解決方案是

  1. 獲取隨機節點
  2. 從該節點的鄰接列表中選擇一個隨機索引
  3. 移動到新節點
  4. Go 至 2
  5. 重復直到隨機游走所需的時間
  6. Go 為 1

當圖形不是太大時,這工作得很好,但是現在我正在處理一個包含> 440k節點的圖形,每個節點的邊數差異很大。

我目前用來提取隨機索引的 function 是

node_neighbors[int(random.random() * number_neighbors_of_node)]

這加快了我之前實現的計算速度,但是對於我的目的來說它仍然慢得不能接受。

一個節點的鄰居個數可以從2個到上萬個,我不能去掉小節點,在這個環境下我要生成上萬個隨機游走。

從分析代碼開始,大部分生成時間都花在尋找這些索引上,所以我正在尋找一種可以減少這樣做所花費時間的方法。 但是,如果可以通過修改算法完全避開它,那也很棒。

謝謝!

編輯:出於好奇,我使用timeit測試了相同代碼的三個變體,結果如下:

setup='''
import numpy as np
import random

# generate a random adjacency list, nodes have a number of neighbors between 2 and 10000

l = [list(range(random.randint(2, 10000))) for _ in range(10000)]
'''

for _ in range(1000):    
    v = l[random.randint(0, 10000-1)] # Get a random node adj list 
    vv = v[random.randint(0, len(v)-1)] # Find a random neighbor in v

0.29709450000001425

for _ in range(1000):    
    v = l[random.randint(0, 10000-1)]
    vv = v[np.random.choice(v)]

26.760767499999986

for _ in range(1000):    
    v = l[random.randint(0, 10000-1)]
    vv = v[int(random.random()*(len(v)))]

0.19086300000000733

for _ in range(1000):    
    v = l[random.randint(0, 10000-1)]
    vv = v[int(random.choice(v))]

0.24351880000000392

您的解決方案(sol3)已經比您的測試建議的速度更快。 我調整了性能測量以消除節點的任意選擇,以支持更接近您既定目標的路徑遍歷。

這是改進的性能測試和結果。 我添加了 sol5() 以查看預先計算隨機值列表是否會產生影響(我希望 numpy 能夠對其進行矢量化,但它並沒有更快地 go )。

設置

import numpy as np
import random

# generate a random adjacency list, nodes have a number of neighbors between 2 and 10000

nodes     = [list(range(random.randint(2, 10000))) for _ in range(10000)]
pathLen   = 1000

解決方案

def sol1():
    node = nodes[0]
    for _ in range(pathLen):
        node = nodes[random.randint(0, len(node)-1)] # move to a random neighbor

def sol2():
    node = nodes[0]
    for _ in range(pathLen):
        node = nodes[np.random.choice(node)]

def sol3():
    node = nodes[0]
    for _ in range(pathLen):
        node = nodes[int(random.random()*(len(node)))]

def sol4():
    node = nodes[0]
    for _ in range(pathLen):
        node = nodes[int(random.choice(node))]

def sol5():
    node = nodes[0]
    for rng in np.random.random_sample(pathLen):
        node = nodes[int(rng*len(node))]

測量

from timeit import timeit
count = 100

print("sol1",timeit(sol1,number=count))
print("sol2",timeit(sol2,number=count))
print("sol3",timeit(sol3,number=count))
print("sol4",timeit(sol4,number=count))
print("sol5",timeit(sol5,number=count))

sol1 0.12516996199999975
sol2 30.445685411
sol3 0.03886452900000137
sol4 0.1244026900000037
sol5 0.05330073100000021

numpy 不太擅長處理具有可變維度的矩陣(例如您的鄰居列表),但加速該過程的一種方法可能是矢量化下一個節點選擇。 通過為 numpy 數組中的每個節點分配一個隨機浮點數,您可以使用它在節點之間導航,直到您的路徑返回到已訪問的節點。 只有這樣,您才需要為該節點生成一個新的隨機值。 據推測,根據路徑長度,這些“碰撞”的數量相對較少。

使用相同的想法,並利用 numpy 的矢量化,您可以通過創建節點標識符(列)矩陣來並行進行多次遍歷,其中每一行都是並行遍歷。

為了說明這一點,這里有一個 function,它通過節點在它們各自的隨機路徑上推進多個“螞蟻”:

import numpy as np
import random

nodes   = [list(range(random.randint(2, 10000))) for _ in range(10000)]
nbLinks = np.array(list(map(len,nodes)),dtype=np.int)         # number of neighbors per node
npNodes = np.array([nb+[-1]*(10000-len(nb)) for nb in nodes]) # fixed sized rows for numpy

def moveAnts(antCount=12,stepCount=8,antPos=None,allPaths=False):
    if antPos is None:
        antPos = np.random.choice(len(nodes),antCount)
    paths = antPos[:,None]

    for _ in range(stepCount):
        nextIndex = np.random.random_sample(size=(antCount,))*nbLinks[antPos]
        antPos    = npNodes[antPos,nextIndex.astype(np.int)]
        if allPaths:
            paths = np.append(paths,antPos[:,None],axis=1)
        
    return paths if allPaths else antPos

示例:12 只螞蟻從隨機起始位置隨機前進 8 步

print(moveAnts(12,8,allPaths=True))

"""
    [[8840 1302 3438 4159 2983 2269 1284 5031 1760]
     [4390 5710 4981 3251 3235 2533 2771 6294 2940]
     [3610 2059 1118 4630 2333  552 1375 4656 6212]
     [9238 1295 7053  542 6914 2348 2481  718  949]
     [5308 2826 2622   17   78  976   13 1640  561]
     [5763 6079 1867 7748 7098 4884 2061  432 1827]
     [3196 3057   27  440 6545 3629  243 6319  427]
     [7694 1260 1621  956 1491 2258  676 3902  582]
     [1590 4720  772 1366 2112 3498 1279 5474 3474]
     [2587  872  333 1984 7263  168 3782  823    9]
     [8525  193  449  982 4521  449 3811 2891 3353]
     [6824 9221  964  389 4454  720 1898  806   58]]
"""

單個螞蟻的性能並不好,但同時每個螞蟻的時間要好得多

from timeit import timeit
count = 100

antCount  = 100
stepCount = 1000
ap = np.random.choice(len(nodes),antCount)

t = timeit(lambda:moveAnts(antCount,stepCount,ap),number=count)

print(t) # 0.9538277329999989 / 100 --> 0.009538277329999989 per ant

[編輯] 我想到了一個更好的數組 model 用於可變大小的行,並提出了一種不會在固定維度的(大部分為空的)矩陣中浪費 memory 的方法。 該方法是使用一維數組連續保存所有節點的鏈接,並使用兩個額外的 arrays 保存起始 position 和鄰居數。 事實證明,這種數據結構的運行速度甚至比固定大小的 2D 矩陣還要快。

import numpy as np
import random

nodes     = [list(range(random.randint(2, 10000))) for _ in range(10000)]
links     = np.array(list(n for neighbors in nodes for n in neighbors))
linkCount = np.array(list(map(len,nodes)),dtype=np.int) # number of neighbors for each node
firstLink = np.insert(np.add.accumulate(linkCount),0,0) # index of first link for each node



def moveAnts(antCount=12,stepCount=8,antPos=None,allPaths=False):
    if antPos is None:
        antPos = np.random.choice(len(nodes),antCount)
    paths = antPos[:,None]

    for _ in range(stepCount):
        nextIndex = np.random.random_sample(size=(antCount,))*linkCount[antPos]
        antPos    = links[firstLink[antPos]+nextIndex.astype(np.int)]
        if allPaths:
            paths = np.append(paths,antPos[:,None],axis=1)
        
    return paths if allPaths else antPos

from timeit import timeit
count = 100

antCount  = 100
stepCount = 1000
ap = np.random.choice(len(nodes),antCount)

t = timeit(lambda:moveAnts(antCount,stepCount,ap),number=count)

print(t) # 0.7157810379999994 / 100 --> 0.007157810379999994 per ant

當您添加更多它們時,“每只螞蟻”的性能會提高,達到一定程度(大約比 sol3 快 10 倍):

antCount  = 1000
stepCount = 1000
ap = np.random.choice(len(nodes),antCount)

t = timeit(lambda:moveAnts(antCount,stepCount,ap),number=count)

print(t,t/antCount) #3.9749405650000007, 0.0039749405650000005 per ant

antCount  = 10000
stepCount = 1000
ap = np.random.choice(len(nodes),antCount)

t = timeit(lambda:moveAnts(antCount,stepCount,ap),number=count)

print(t,t/antCount) #32.688697579, 0.0032688697579 per ant

暫無
暫無

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

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