2017-06-19 66 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']]