python中的高效列表操作

时间:2015-01-17 20:32:46

标签: python list reference linked-list

我有一个很大的列表,并且需要找到一个满足相当复杂条件(不相等)的项目,即我被迫检查列表中的每个项目,直到找到一个。条件会发生变化,但有些项目会比其他项目更频繁地匹配。因此,我希望每次找到匹配项目时都将匹配项目放在列表的前面,这样可以更快地找到匹配的项目。

高效的,pythonic方法吗?

序列([])由数组支持,因此删除中间某处的项目并将其添加到数组意味着移动每个前一项。那是在O(n)时间,不好。

在C中,您可以构建链接列表,并在找到时自行移动项目。在Python中有一个deque,但是你不能引用节点对象,也不能访问.next指针。

Python中的自制链表非常慢。 (事实上​​,它比普通的线性搜索更慢,没有移动任何项目。)

可悲的是,dictset根据值相等性查找项目,因此不适合我的问题。

作为一个例子,这里是条件:

u, v, w = n.value   # list item
if v in g[u] and w in g[v] and u not in g[w]:
    ...

2 个答案:

答案 0 :(得分:3)

考虑使用Pythonic方法。正如Ed Post曾经说过的那样,"确定的Real Programmer可以用任何语言编写FORTRAN程序" - 这概括了......你在尝试用Python写C并且它不适合你: - )

相反,考虑在dict旁边放置一个辅助list缓存 - 缓存找到项目的索引(需要仅在"深度"更改为列表的结构)。更简单的更快......

最好通过在一个小班级中设置listdict来做到最好:

class Seeker(object):
    def __init__(self, *a, **k):
        self.l = list(*a, **k)
        self.d = {}

    def find(self, value):
        where = self.d.get(value)
        if where is None:
            self.d[value] = where = self.l.find(value)
        return where

    def __setitem__(self, index, value):
        if value in self.d: del self.d[value]
        self.l[index] = value

    # and so on for other mutators that invalidate self.d; then,

    def __getattr__(self, name):
        # delegate everything else to the list
        return getattr(self.l, name)

您只需要定义实际需要使用的变更器 - 例如,如果您不能执行insertsort__delitem__和& c,则无需要定义它们,您可以将它们委托给列表。

补充:在Python 3.2或更高版本中,functools.lru_cache实际上可以为您完成大部分工作 - 使用它来装饰find并且您将获得更好的缓存实现,如果您愿意,可以限制缓存大小。要清除缓存,您需要在适当的位置(我上面使用self.find.cache_clear())调用self.d = {} - 不幸的是,关键功能尚未(尚未! - )记录(更新文档的志愿者与更新代码的人不一样......! - )......但是,相信我,它不会消失在你身上: - )。

补充:OP编辑了Q以澄清他不是在"值相等"之后,而是一些更复杂的条件,例如:谓词

def good_for_g(g, n):
    # for some container `g` and item value `n`:
    u, v, w = n.value
    return v in g[u] and w in g[v] and u not in g[w]

然后,大概是想带来好的"前面的物品反过来取决于他们的善良"正在"粘性",即g暂时保持相同的状态。在这种情况下,可以使用谓词作为特征提取和检查功能,它将键形成字典 - 例如:

class FancySeeker(object):
    def __init__(self, *a, **k):
        self.l = list(*a, **k)
        self.d = {}

    def _find_in_list(self, predicate):
        for i, n in enumerate(self.l):
            if predicate(n):
                return i
        return -1

    def find(self, predicate):
        where = self.d.get(predicate)
        if where is None:
            where = self._find_in_list(predicate)
            self.d[predicate] = where
        return where

等等。

所以剩下的难点是将predicate放入适合有效索引到dict的形式。如果predicate只是一个函数,没问题。但是,如果predicate是带参数的函数,例如functools.partial形成的函数或某些实例的绑定方法,则需要进行一些进一步处理/换行才能使索引工作。

例如,对具有相同绑定参数和函数的functools.partial的两次调用不返回相等的对象 - 更确切地说,要检查.args和{{1}返回的对象确保可以说是一个" singleton"对于任何给定的.func对,都会返回。

此外,如果某些绑定参数是可变的,则需要使用他们的(func, args)来代替他们的id(否则原始hash对象将不可用。)它对于绑定方法来说甚至更加毛茸茸,尽管它们可以类似地被包裹成例如可以清除的,"相等的调整" functools.partial上课。

最后,如果这些旋转过于繁琐并且您真的想要快速实现链接列表,请查看https://pypi.python.org/pypi/llist/0.4 - 它是用于Python的单链接和双链接列表的C编码实现(对于每种类型,它实现三种类型:列表本身,列表节点和列表的迭代器)。

答案 1 :(得分:0)

您可以使用deque.rotate完全按照自己的意愿执行操作。

from collections import deque

class Collection:
    "Linked List collection that moves searched for items to the front of the collection"

    def __init__(self, seq):
        self._deque = deque(seq)

    def __contains__(self, target):
        for i, item in enumerate(self._deque):
            if item == target:
                self._deque.rotate(i)
                self._deque.popleft()
                self._deque.rotate(-i+1)
                self._deque.appendleft(item)
                return True
        return False

    def __str__(self):
        return "Collection({})".format(str(self._deque))

c = Collection(range(10))
print(c)
print("5 in d:", 5 in c)
print(c)

提供以下输出:

Collection(deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
5 in c: True
Collection(deque([5, 0, 1, 2, 3, 4, 6, 7, 8, 9]))