什么';在Python列表中查找随机索引的最快方法是什么?

什么';在Python列表中查找随机索引的最快方法是什么?,python,list,performance,random,Python,List,Performance,Random,从大量(>1M)次的列表中提取随机值的最佳(最快)方法是什么 我目前的情况是,我有一个表示为邻接列表的图,其内部列表的长度可能相差很大(范围为[2,可能为100k]) 我需要迭代这个列表来生成随机游动,所以我当前的解决方案是 获取随机节点 从该节点的邻接列表中选择一个随机索引 移动到新节点 去2号 重复此操作,直到随机行走达到所需的长度 转到1 当图形不太大时,这就足够了,但是现在我正在处理一个包含>440k个节点的图形,每个节点的边数差异很大 我现在用来提取随机索引的函数是 node_neig

从大量(>1M)次的列表中提取随机值的最佳(最快)方法是什么

我目前的情况是,我有一个表示为邻接列表的图,其内部列表的长度可能相差很大(范围为[2,可能为100k])

我需要迭代这个列表来生成随机游动,所以我当前的解决方案是

  • 获取随机节点
  • 从该节点的邻接列表中选择一个随机索引
  • 移动到新节点
  • 去2号
  • 重复此操作,直到随机行走达到所需的长度
  • 转到1
  • 当图形不太大时,这就足够了,但是现在我正在处理一个包含>440k个节点的图形,每个节点的边数差异很大

    我现在用来提取随机索引的函数是

    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可以将其矢量化,但速度没有加快)

    设置

    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
    
    测量值

    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的矢量化,您可以通过创建节点标识符(列)矩阵来并行进行多个遍历,每一行都是并行遍历

    为了说明这一点,这里有一个函数,它使多个“蚂蚁”在各自的随机路径上通过节点前进:

    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
    
    [编辑]我为可变大小的行想出了一个更好的数组模型,并提出了一种不会在固定维度的矩阵(大部分为空)中浪费内存的方法。该方法是使用一个1D阵列来连续保持所有节点的链路,并使用另外两个阵列来保持起始位置和相邻节点的数量。这种数据结构的运行速度甚至比固定大小的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倍):


    random.choice
    ..?正如@deceze提到的,你可以使用
    random.choice
    或者你可以使用
    numpy.random.choice
    @deceze
    choice
    random
    慢。你需要它快多少?@Pawan那一个numpy对你来说快吗?我用
    数组([0]*1000)
    测试了它,它比
    [0]*1000
    上的OP慢了20倍左右。感谢你的回答,我已经在想,在数组大小如此多变的情况下,numpy不会表现得那么好,我想知道是否有办法回避这个问题。我不确定我能否立即在我的代码中实现您的解决方案,但无论如何,再次感谢您找到了一种比我现有技术更快的技术。有关使用numpy管理可变大小矩阵的更有效方法(以及更好的引导性能),请参阅我的编辑。