我有一个很大的列表,并且需要找到一个满足相当复杂条件(不相等)的项目,即我被迫检查列表中的每个项目,直到找到一个。条件会发生变化,但有些项目会比其他项目更频繁地匹配。因此,我希望每次找到匹配项目时都将匹配项目放在列表的前面,这样可以更快地找到匹配的项目。
有高效的,pythonic方法吗?
序列([]
)由数组支持,因此删除中间某处的项目并将其添加到数组意味着移动每个前一项。那是在O(n)时间,不好。
在C中,您可以构建链接列表,并在找到时自行移动项目。在Python中有一个deque
,但是你不能引用节点对象,也不能访问.next
指针。
Python中的自制链表非常慢。 (事实上,它比普通的线性搜索更慢,没有移动任何项目。)
可悲的是,dict
或set
根据值相等性查找项目,因此不适合我的问题。
作为一个例子,这里是条件:
u, v, w = n.value # list item
if v in g[u] and w in g[v] and u not in g[w]:
...
答案 0 :(得分:3)
考虑使用Pythonic方法。正如Ed Post曾经说过的那样,"确定的Real Programmer可以用任何语言编写FORTRAN程序" - 这概括了......你在尝试用Python写C并且它不适合你: - )
相反,考虑在dict
旁边放置一个辅助list
缓存 - 缓存找到项目的索引(需要仅在"深度"更改为列表的结构)。更简单的和更快......
最好通过在一个小班级中设置list
和dict
来做到最好:
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)
您只需要定义实际需要使用的变更器 - 例如,如果您不能执行insert
,sort
,__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]))