我期望将列表变换为等值的较小列表。我有一个例子是:将列表拆分成等值较小的列表
["a", "a", "a", "b", "b", "c", "c", "c", "c"]
到
[["a", "a", "a"], ["b", "b"], ["c", "c", "c", "c"]]
你认为什么是最有效的方式做到这一点?
我期望将列表变换为等值的较小列表。我有一个例子是:将列表拆分成等值较小的列表
["a", "a", "a", "b", "b", "c", "c", "c", "c"]
到
[["a", "a", "a"], ["b", "b"], ["c", "c", "c", "c"]]
你认为什么是最有效的方式做到这一点?
你可以使用itertools.groupby
来解决这个问题:
>>> from itertools import groupby
>>> [list(grp) for k, grp in groupby(["a", "a", "a", "b", "b", "c", "c", "c", "c"])]
[['a', 'a', 'a'], ['b', 'b'], ['c', 'c', 'c', 'c']]
它只组连续相等的元素,但似乎足以在你的情况。
你可以使用collections.Counter
>>> lst = ["a", "a", "a", "b", "b", "c", "c", "c", "c"]
>>> import collections
>>> collections.Counter(lst).most_common()
[('c', 4), ('a', 3), ('b', 2)]
这样,即使该值不排序,并提供了一个非常紧凑的表示,然后在需要时进入名单,你可以扩展:
>>> [[i]*n for i,n in collections.Counter(lst).most_common()]
[['c', 'c', 'c', 'c'], ['a', 'a', 'a'], ['b', 'b']]
你知道如何访问每个元素的计数器值吗?在这种情况下,4,3,和2 – Enesxg
当然,只需使用:'[n for i,n in collections.Counter(lst).most_common()]' –
虽然我d亲自使用itertools.groupby
作为最方便的方式,您要求提高效率,并且这应该比itertools
选项中的任何一个快得多:
data = ["a", "a", "a", "b", "b", "c", "c", "c", "c"]
lookup = {} # lookup map
result = []
for element in data:
if element not in lookup:
target = lookup[element] = [element]
result.append(target)
else:
lookup[element].append(element)
print(result)
# [['a', 'a', 'a'], ['b', 'b'], ['c', 'c', 'c', 'c']]
如果数据总是有序的(即,元素不会混合),这可以进一步优化没有查找表和使用列表理解的最大性能。
UPDATE - 一些关于效率和操作的说明。如果您设置的测试为:
from itertools import groupby
def itools_func(data):
return [list(grp) for k, grp in groupby(data)]
def manual_func(data):
lookup = {}
result = []
for element in data:
if element not in lookup:
target = lookup[element] = [element]
result.append(target)
else:
lookup[element].append(element)
return result
的问题是,他们两个会不会返回相同的值:
test_data = ["a", "a", "b", "c", "c", "b", "a"]
itools_func(test_data) # [['a', 'a'], ['b'], ['c', 'c'], ['b'], ['a']]
manual_func(test_data) # [['a', 'a', 'a'], ['b', 'b'], ['c', 'c']]
从OP的问题,我的理解,他希望后者(基于他评论“我对列表进行排序以使值连续”),因为对于排序列表,这可以更容易完成。所以,如果我们喂这些功能很长的名单:
test_data = ["a", "a", "a", "b", "b", "c", "c", "c", "c"] * 10000 # 10000 x the original
在我的系统是钟表如下:
itools_func - 100 loops: 2.668s, per loop: 26.68ms
manual_func - 100 loops: 1.005s, per loop: 10.05ms
但是,这是为itertools.groopby
不利的环境。如果数据以像进行排序:
test_data = ["a"] * 3000 + ["b"] * 2000 + ["c"] * 40000
故事是如在C后端踢相当多的不同:
itools_func - 1000 loops: 656.3ms, per loop: 656.3µs
manual_func - 1000 loops: 4.816s, per loop: 4.816ms
当数据被排序的手动功能可以进一步优化,但是它几乎不会击败itertools
。
那么,如果你关心效率,你应该使用' defaultdict',或者至少使用普通'dict'的'.setdefault'方法,而不是检查'if not in lookup:'。另外,我很好奇你为什么说这会快很多。你有时间吗?毕竟,'itertools.groupby'是用C编写的。 –
对于真正的短输入而言,这只是“更”有效。如果“数据”很大或很大,这会比较慢。 – MSeifert
@ juanpa.arrivillaga @MSeifert - 我用一些数字更新了我的帖子。至于为什么不使用'defaultdict' - 它不会在这里添加任何东西,实际上它只是在提取数据时添加更多步骤,因为需要将单独的列表与'lookup'中的if元素一起保存到维持秩序。我用'defaultdict'试了一下,平均结果慢了约1%。 – zwer
使用defaultdict
from collections
模块(使用此方法的最佳时间为:〜= 0),获得所需输出的另一种方式是使用defaultdict
模块。02S一样使用groupby
):
from collections import defaultdict
a = ["a", "a", "a", "b", "b", "c", "c", "c", "c"]
b = defaultdict(list)
for k in a:
b[k].append(k)
>>> b
defaultdict(list,
{'a': ['a', 'a', 'a'], 'b': ['b', 'b'], 'c': ['c', 'c', 'c', 'c']})
所以,你现在要做的是:
list(b.values())
>>> [['a', 'a', 'a'], ['b', 'b'], ['c', 'c', 'c', 'c']]
是相等的值一定的连续? – anonymoose
我对列表排序以使值连续 – Enesxg