【问题标题】:Most suitable data structure to support delete from left and delete from arbitrary index where delete from left is twice as much frequent最适合支持从左侧删除和从任意索引删除的数据结构,其中从左侧删除的频率是其两倍
【发布时间】:2021-09-01 16:52:31
【问题描述】:

我正在编写一个函数,它接受大量字符串并将它们分类。

每个分类的字符串一个接一个地从列表中删除。

函数有时需要删除任意索引中的字符串,有时需要从最左边的位置删除字符串。

这两种情况之间的比率大约是它从最左边位置删除的时间的 2/3 和从中间任意位置删除的时间的 1/3。

我知道使用列表作为数据结构来保存字符串是 O(n) 删除最左边的单元格并 O(n-i) 删除第 i 个单元格,因为每次删除都会将所有单元格向右移动一步。

然后我尝试使用collections.deque 代替列表,但实际上它比原始list 数据结构花费了更多时间(大约多4/3 时间),可能是因为任意位置删除。

在这些假设下哪种数据结构的性能最好?有什么比只使用list 更好的方法吗?

从最左边的单元格中删除list 数据结构使用list.pop(0),从任意索引中删除使用list.remove(value) 按值。

【问题讨论】:

  • 向我们展示你是如何使用双端队列的
  • 列表有多大,如何找到那些任意索引?
  • 只用leftpop()从左边删除,remove(index)在任意索引中删除
  • 也许反转你的列表(并相应地调整算法)所以你从右边删除?
  • 或者也许做惰性删除,即,不是在任意索引处删除,而是将值替换为None,并在以后遇到它时将其删除(当它是最后一个列表元素时)。如果您告诉我们更多有关您的整体程序的信息,我们可以为您提供更好的帮助。

标签: python performance


【解决方案1】:

试试OrderedDict,用你的字符串作为键。

仅使用 30,000 个字符串、20,000 个左侧删除和 10,000 个“任意”删除的基准:

1476 ms  1490 ms  1491 ms  using_list
  10 ms    11 ms    11 ms  using_OrderedDict

代替

strings_list.pop(0)
strings_list.remove(value)

使用

strings_dict.popitem(False)
strings_dict.pop(value)

这假设您没有重复的字符串,这似乎很可能。如果这样做,则使用字典值作为频率。

基准代码 (Try it online!):

from timeit import timeit
from collections import OrderedDict
from random import randrange

def using_list(strings, removes):
    for string in removes:
        strings.pop(0)
        strings.pop(0)
        strings.remove(string)

def using_OrderedDict(strings, removes):
    strings = OrderedDict.fromkeys(strings)
    for string in removes:
        strings.popitem(False)
        strings.popitem(False)
        strings.pop(string)

# Build testcase with 30,000 elements
strings = list(map(str, range(10_000, 40_000)))
copy = strings.copy()
removes = []
for _ in range(10_000):
    copy.pop(0)
    copy.pop(0)
    i = randrange(len(copy))
    removes.append(copy.pop(i))

# Benchmark
for _ in range(3):
    for func in using_list, using_OrderedDict:
        times = []
        for _ in range(3):
            copy = strings.copy()
            t = timeit(lambda: func(copy, removes), number=1)
            times.append(t)
        times.sort()
        print(*('%4d ms ' % (t * 1e3) for t in times), func.__name__)
    print()

【讨论】:

  • 我很惊讶这工作如此之快,几天变成了几分钟
【解决方案2】:

如果您知道要删除的确切位置,您可能正在寻找linked list implementation

请注意,从链表中删除的是O(1)

但是,搜索一个元素是 O(n)

编辑: 如果您关心要删除的值,set 可能就是您要查找的内容。请注意,set 是一个无序的数据结构,只允许删除值

【讨论】:

  • 取决于他们如何找到删除索引。如果它们实际上不是来自索引,而是来自要删除的 value,则可以使用 dict 将值映射到链表节点。那么“搜索”也应该是 O(1)。
  • 但我相信使用字典是 O(logn(n)) tho
  • 不,它是 O(1),除了非常特殊的情况(如 this 但对于 dict)。特别是对于字符串,因为涉及到随机化,所以即使你尝试也很难让它变慢。
猜你喜欢
  • 2021-12-05
  • 1970-01-01
  • 2011-09-10
  • 2020-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-30
  • 1970-01-01
相关资源
最近更新 更多