仅当元素尚未存在时,将元素添加到列表的最有效方法是什么?

时间:2009-08-23 18:50:42

标签: python list optimization

我在Python中有以下代码:

def point_to_index(point):
    if point not in points:
        points.append(point)
    return points.index(point)

这段代码非常低效,特别是因为我期望points增长到容纳几百万个元素。

如果该点不在列表中,我将遍历列表3次:

  1. 寻找并决定它不存在
  2. 转到列表末尾并添加新元素
  3. 转到列表末尾,直到找到索引
  4. 如果列表中 ,我会遍历两次: 1.寻找并决定它在那里 2.几乎到列表末尾,直到我找到索引

    有没有更有效的方法来做到这一点?例如,我知道:

    • 我更有可能使用不在列表中的点来调用此函数。
    • 如果该点位于列表中,那么它的结尾可能比在开头时更接近。

    所以,如果我有这条线:

    if point not in points:
    

    从结尾到开头搜索列表,当点已经在列表中时,它将提高性能。

    但是,我不想这样做:

    if point not in reversed(points):
    

    因为我认为reversed(points)本身会付出巨大的代价。

    我也不想在列表的开头添加新的点(假设我知道如何在Python中执行此操作)因为这会改变索引,索引必须保持不变才能使算法起作用。

    我能想到的唯一改进是只使用一次传递来实现该功能,如果可能的话,从最后到开始。底线是:

    • 有一个很好的方法吗?
    • 有更好的方法来优化功能吗?

    编辑:我已经获得了仅使用一次传递来实现此功能的建议。 index()有什么方法可以从最后到开头吗?

    编辑:人们已经问过为什么索引很关键。我正在尝试使用OFF file format来描述3D表面。此格式使用其顶点和面来描述曲面。首先列出顶点,然后使用顶点索引列表描述面。这就是为什么一旦我向列表中添加一个漩涡,它的索引就不能改变。

    编辑:有一些使用dict的建议(例如igor's)。这是扫描列表的一个很好的解决方案。但是,当我完成后,我需要按照创建的顺序打印出列表。如果我使用dict,我需要打印出按值排序的键。有没有办法做到这一点?

    修改:我实施了www.brool.comsuggestion。这是最简单,最快速的。它本质上是一个有序的Dict,但没有开销。表现很棒!

6 个答案:

答案 0 :(得分:12)

您想使用set

>>> x = set()
>>> x
set([])
>>> x.add(1)
>>> x
set([1])
>>> x.add(1)
>>> x
set([1])

一个集合只包含您添加的任何项目的一个实例,它比手动迭代列表更有效。

如果您之前没有在Python中使用过集,那么

This wikibooks page看起来是一个很好的入门。

答案 1 :(得分:10)

这最多会遍历一次:

def point_to_index(point):
    try: 
        return points.index(point)
    except ValueError:
        points.append(point)
        return len(points)-1

您可能还想尝试这个版本,考虑到匹配可能接近列表的末尾。请注意,reversed()即使在非常大的列表上也几乎没有成本 - 它不会创建副本,也不会多次遍历列表。

def point_to_index(point):
    for index, this_point in enumerate(reversed(points)):
        if point == this_point:
            return len(points) - (index+1)
    else:
        points.append(point)
        return len(points)-1

您可能还会考虑保留并行dictset点来检查成员资格,因为这两种类型都可以在O(1)中进行成员资格测试。当然,会有大量的内存成本。

显然,如果以某种方式对这些点进行排序,那么你可以使用许多其他选项来加速这段代码,特别是使用二进制搜索来进行成员资格测试。

答案 2 :(得分:5)

如果您担心内存使用情况,但想要优化常见情况,请保留包含最后n个点及其索引的字典。 points_dict = dictionary,max_cache =缓存的大小。

def point_to_index(point):
    try:
        return points_dict.get(point, points.index(point))
    except:
        if len(points) >= max_cache:
            del points_dict[points[len(points)-max_cache]]
        points.append(point)
        points_dict[points] = len(points)-1
        return len(points)-1

答案 3 :(得分:2)

def point_to_index(point):
    try:
        return points.index(point)
    except:
        points.append(point)
        return len(points)-1

更新:在Nathan的异常代码中添加。

答案 4 :(得分:1)

正如其他人所说,考虑使用set或dict。你不解释为什么你需要索引。如果他们只需要为点分配唯一ID(我不能轻易想出使用它们的另一个原因),那么dict确实会更好用,例如,

points = {}
def point_to_index(point):
    if point in points:
        return points[point]
    else:
       points[point] = len(points)
       return len(points) - 1

答案 5 :(得分:1)

你真正想要的是一个有序的字典(键插入确定顺序):