2017-06-19 75 views
0

我期望将列表变换为等值的较小列表。我有一个例子是:将列表拆分成等值较小的列表

["a", "a", "a", "b", "b", "c", "c", "c", "c"] 

[["a", "a", "a"], ["b", "b"], ["c", "c", "c", "c"]] 

你认为什么是最有效的方式做到这一点?

+3

是相等的值一定的连续? – anonymoose

+0

我对列表排序以使值连续 – Enesxg

回答

3

你可以使用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']] 

它只组连续相等的元素,但似乎足以在你的情况。

+1

在分组之前,可以(应该)对列表进行排序。 – DyZ

+1

这取决于确切的要求。如果它应该组合相等的连续元素,那么“否”,如果它应该组合所有相等的值(总体)并保持顺序,那么有更好的方法使用OrderedDict和Counter。为了防止顺序无关紧要,并且相等的元素不连续,排序是一种有效的策略。给出的例子最有效的方法就是使用'groupby'(没有排序)。 :) – MSeifert

+0

同意。 OP只是说他们为了方便而对列表进行了排序。 – DyZ

2

你可以使用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']] 
+0

你知道如何访问每个元素的计数器值吗?在这种情况下,4,3,和2 – Enesxg

+0

当然,只需使用:'[n for i,n in collections.Counter(lst).most_common()]' –

0

虽然我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

+0

那么,如果你关心效率,你应该使用' defaultdict',或者至少使用普通'dict'的'.setdefault'方法,而不是检查'if not in lookup:'。另外,我很好奇你为什么说这会快很多。你有时间吗?毕竟,'itertools.groupby'是用C编写的。 –

+0

对于真正的短输入而言,这只是“更”有效。如果“数据”很大或很大,这会比较慢。 – MSeifert

+0

@ juanpa.arrivillaga @MSeifert - 我用一些数字更新了我的帖子。至于为什么不使用'defaultdict' - 它不会在这里添加任何东西,实际上它只是在提取数据时添加更多步骤,因为需要将单独的列表与'lookup'中的if元素一起保存到维持秩序。我用'defaultdict'试了一下,平均结果慢了约1%。 – zwer

1

使用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']]