Nhóm Python bởi


125

Giả sử rằng tôi có một tập hợp cặp dữ liệu trong đó chỉ mục 0 là giá trị và chỉ mục 1 là loại:

input = [
          ('11013331', 'KAT'), 
          ('9085267',  'NOT'), 
          ('5238761',  'ETH'), 
          ('5349618',  'ETH'), 
          ('11788544', 'NOT'), 
          ('962142',   'ETH'), 
          ('7795297',  'ETH'), 
          ('7341464',  'ETH'), 
          ('9843236',  'KAT'), 
          ('5594916',  'ETH'), 
          ('1550003',  'ETH')
        ]

Tôi muốn nhóm chúng theo loại của chúng (theo chuỗi được lập chỉ mục đầu tiên) như vậy:

result = [ 
           { 
             type:'KAT', 
             items: ['11013331', '9843236'] 
           },
           {
             type:'NOT', 
             items: ['9085267', '11788544'] 
           },
           {
             type:'ETH', 
             items: ['5238761', '962142', '7795297', '7341464', '5594916', '1550003'] 
           }
         ] 

Làm thế nào tôi có thể đạt được điều này một cách hiệu quả?

Câu trả lời:


153

Thực hiện theo 2 bước. Đầu tiên, hãy tạo một từ điển.

>>> input = [('11013331', 'KAT'), ('9085267', 'NOT'), ('5238761', 'ETH'), ('5349618', 'ETH'), ('11788544', 'NOT'), ('962142', 'ETH'), ('7795297', 'ETH'), ('7341464', 'ETH'), ('9843236', 'KAT'), ('5594916', 'ETH'), ('1550003', 'ETH')]
>>> from collections import defaultdict
>>> res = defaultdict(list)
>>> for v, k in input: res[k].append(v)
...

Sau đó, chuyển đổi từ điển đó sang định dạng mong đợi.

>>> [{'type':k, 'items':v} for k,v in res.items()]
[{'items': ['9085267', '11788544'], 'type': 'NOT'}, {'items': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}, {'items': ['11013331', '9843236'], 'type': 'KAT'}]

Nó cũng có thể với itertools.groupby nhưng nó yêu cầu đầu vào phải được sắp xếp trước.

>>> sorted_input = sorted(input, key=itemgetter(1))
>>> groups = groupby(sorted_input, key=itemgetter(1))
>>> [{'type':k, 'items':[x[0] for x in v]} for k, v in groups]
[{'items': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}, {'items': ['11013331', '9843236'], 'type': 'KAT'}, {'items': ['9085267', '11788544'], 'type': 'NOT'}]

Lưu ý cả hai điều này không tôn trọng thứ tự ban đầu của các phím. Bạn cần một OrderDict nếu bạn cần giữ thứ tự.

>>> from collections import OrderedDict
>>> res = OrderedDict()
>>> for v, k in input:
...   if k in res: res[k].append(v)
...   else: res[k] = [v]
... 
>>> [{'type':k, 'items':v} for k,v in res.items()]
[{'items': ['11013331', '9843236'], 'type': 'KAT'}, {'items': ['9085267', '11788544'], 'type': 'NOT'}, {'items': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}]

Làm thế nào điều này có thể được thực hiện nếu tuple đầu vào có một khóa và hai hoặc nhiều giá trị, như thế này: [('11013331', 'red', 'KAT'), ('9085267', 'blue' 'KAT')]trong đó phần tử cuối cùng của tuple là khóa và hai giá trị đầu tiên là giá trị. Kết quả sẽ như thế này: result = [{type: 'KAT', các mục: [( '11.013.331', đỏ), ( '9.085.267', xanh dương)]}]
user1144616

1
from operator import itemgetter
Baumann

1
bước 1 có thể được thực hiện mà không cần nhập:d= {}; for k,v in input: d.setdefault(k, []).append(v)
ecoe

Tôi đang làm việc trên một chương trình MapReduce bằng python, tôi chỉ tự hỏi có cách nào để nhóm theo các giá trị trong danh sách mà không cần xử lý từ điển hoặc thư viện bên ngoài như gấu trúc không? Nếu không, thì làm cách nào để loại bỏ các mục và nhập vào kết quả của tôi?
Kourosh

54

itertoolsMô-đun tích hợp của Python thực sự có một groupbychức năng, nhưng để các phần tử được nhóm lại trước tiên phải được sắp xếp sao cho các phần tử được nhóm nằm liền kề trong danh sách:

from operator import itemgetter
sortkeyfn = itemgetter(1)
input = [('11013331', 'KAT'), ('9085267', 'NOT'), ('5238761', 'ETH'), 
 ('5349618', 'ETH'), ('11788544', 'NOT'), ('962142', 'ETH'), ('7795297', 'ETH'), 
 ('7341464', 'ETH'), ('9843236', 'KAT'), ('5594916', 'ETH'), ('1550003', 'ETH')] 
input.sort(key=sortkeyfn)

Bây giờ đầu vào trông giống như:

[('5238761', 'ETH'), ('5349618', 'ETH'), ('962142', 'ETH'), ('7795297', 'ETH'),
 ('7341464', 'ETH'), ('5594916', 'ETH'), ('1550003', 'ETH'), ('11013331', 'KAT'),
 ('9843236', 'KAT'), ('9085267', 'NOT'), ('11788544', 'NOT')]

groupbytrả về một chuỗi 2 bộ, có dạng (key, values_iterator). Những gì chúng tôi muốn là biến điều này thành một danh sách các phần trong đó 'type' là khóa và 'items' là danh sách các phần tử thứ 0 của các bộ giá trị được trả về bởi value_iterator. Như thế này:

from itertools import groupby
result = []
for key,valuesiter in groupby(input, key=sortkeyfn):
    result.append(dict(type=key, items=list(v[0] for v in valuesiter)))

Bây giờ resultchứa chính tả mong muốn của bạn, như đã nêu trong câu hỏi của bạn.

Tuy nhiên, bạn có thể cân nhắc việc chỉ tạo một câu lệnh duy nhất trong số này, được khóa theo loại và mỗi giá trị chứa danh sách các giá trị. Trong biểu mẫu hiện tại của bạn, để tìm các giá trị cho một loại cụ thể, bạn sẽ phải lặp lại danh sách để tìm ra lệnh chứa khóa 'loại' phù hợp và sau đó lấy phần tử 'items' từ nó. Nếu bạn sử dụng một chính tả duy nhất thay vì danh sách gồm 1 mục, bạn có thể tìm thấy các mục cho một loại cụ thể bằng một lần tra cứu có khóa duy nhất trong chính tả. Sử dụng groupby, điều này sẽ trông giống như:

result = {}
for key,valuesiter in groupby(input, key=sortkeyfn):
    result[key] = list(v[0] for v in valuesiter)

resultbây giờ chứa resmệnh lệnh này (tương tự như lệnh mặc định trung gian trong câu trả lời của @ KennyTM):

{'NOT': ['9085267', '11788544'], 
 'ETH': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 
 'KAT': ['11013331', '9843236']}

(Nếu bạn muốn giảm điều này thành một lớp lót, bạn có thể:

result = dict((key,list(v[0] for v in valuesiter)
              for key,valuesiter in groupby(input, key=sortkeyfn))

hoặc sử dụng biểu mẫu đọc hiểu chính tả mới:

result = {key:list(v[0] for v in valuesiter)
              for key,valuesiter in groupby(input, key=sortkeyfn)}

Tôi đang làm việc trên một chương trình MapReduce bằng python, tôi chỉ tự hỏi có cách nào để nhóm theo các giá trị trong danh sách mà không cần xử lý từ điển hoặc thư viện bên ngoài như gấu trúc không? Nếu không, thì làm cách nào để loại bỏ các mục và nhập vào kết quả của tôi?
Kourosh

@Kourosh - Đăng dưới dạng một câu hỏi mới, nhưng hãy nhớ cho biết ý của bạn bằng cách "loại bỏ các mục và nhập kết quả của tôi" và "không xử lý từ điển".
PaulMcG

7

Tôi cũng thích nhóm gấu trúc đơn giản . nó mạnh mẽ, đơn giản và đầy đủ nhất cho tập dữ liệu lớn

result = pandas.DataFrame(input).groupby(1).groups


3

Câu trả lời này tương tự như câu trả lời của @ PaulMcG nhưng không yêu cầu sắp xếp đầu vào.

Đối với những người lập trình chức năng, groupBycó thể được viết trong một dòng (không bao gồm nhập khẩu!), Và không giống như itertools.groupbynó không yêu cầu đầu vào được sắp xếp:

from functools import reduce # import needed for python3; builtin in python2
from collections import defaultdict

def groupBy(key, seq):
 return reduce(lambda grp, val: grp[key(val)].append(val) or grp, seq, defaultdict(list))

(Lý do cho ... or grptrong lambdalà cho điều này reduce()để làm việc, lambdanhu cầu quay trở lại đối số đầu tiên của nó, bởi vì list.append()luôn luôn trả về Nonecác orsẽ luôn luôn trở lại grp. Tức là nó là một hack để có được xung quanh hạn chế python rằng một lambda chỉ có thể đánh giá một biểu thức duy nhất.)

Điều này trả về một dict có các khóa được tìm thấy bằng cách đánh giá hàm đã cho và giá trị của nó là danh sách các mục gốc theo thứ tự ban đầu. Đối với ví dụ của OP, việc gọi hàm này là groupBy(lambda pair: pair[1], input)sẽ trả về câu lệnh này:

{'KAT': [('11013331', 'KAT'), ('9843236', 'KAT')],
 'NOT': [('9085267', 'NOT'), ('11788544', 'NOT')],
 'ETH': [('5238761', 'ETH'), ('5349618', 'ETH'), ('962142', 'ETH'), ('7795297', 'ETH'), ('7341464', 'ETH'), ('5594916', 'ETH'), ('1550003', 'ETH')]}

Và theo câu trả lời của @ PaulMcG, định dạng yêu cầu của OP có thể được tìm thấy bằng cách gói nó trong một danh sách dễ hiểu. Vì vậy, điều này sẽ làm điều đó:

result = {key: [pair[0] for pair in values],
          for key, values in groupBy(lambda pair: pair[1], input).items()}

Rất ít mã, nhưng vẫn có thể hiểu được. Cũng tốt vì nó không phát minh lại bánh xe.
devdanke

2

Hàm sau sẽ nhanh chóng ( không cần sắp xếp ) nhóm các bộ giá trị có độ dài bất kỳ bằng một khóa có bất kỳ chỉ mục nào:

# given a sequence of tuples like [(3,'c',6),(7,'a',2),(88,'c',4),(45,'a',0)],
# returns a dict grouping tuples by idx-th element - with idx=1 we have:
# if merge is True {'c':(3,6,88,4),     'a':(7,2,45,0)}
# if merge is False {'c':((3,6),(88,4)), 'a':((7,2),(45,0))}
def group_by(seqs,idx=0,merge=True):
    d = dict()
    for seq in seqs:
        k = seq[idx]
        v = d.get(k,tuple()) + (seq[:idx]+seq[idx+1:] if merge else (seq[:idx]+seq[idx+1:],))
        d.update({k:v})
    return d

Trong trường hợp câu hỏi của bạn, chỉ mục của khóa bạn muốn nhóm lại là 1, do đó:

group_by(input,1)

cho

{'ETH': ('5238761','5349618','962142','7795297','7341464','5594916','1550003'),
 'KAT': ('11013331', '9843236'),
 'NOT': ('9085267', '11788544')}

đó không phải là đầu ra chính xác mà bạn yêu cầu, nhưng cũng có thể phù hợp với nhu cầu của bạn.


Tôi đang làm việc trên một chương trình MapReduce bằng python, tôi chỉ tự hỏi có cách nào để nhóm theo các giá trị trong danh sách mà không cần xử lý từ điển hoặc thư viện bên ngoài như gấu trúc không? Nếu không, thì làm cách nào để loại bỏ các mục và nhập vào kết quả của tôi?
Kourosh

0
result = []
# Make a set of your "types":
input_set = set([tpl[1] for tpl in input])
>>> set(['ETH', 'KAT', 'NOT'])
# Iterate over the input_set
for type_ in input_set:
    # a dict to gather things:
    D = {}
    # filter all tuples from your input with the same type as type_
    tuples = filter(lambda tpl: tpl[1] == type_, input)
    # write them in the D:
    D["type"] = type_
    D["itmes"] = [tpl[0] for tpl in tuples]
    # append D to results:
    result.append(D)

result
>>> [{'itmes': ['9085267', '11788544'], 'type': 'NOT'}, {'itmes': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}, {'itmes': ['11013331', '9843236'], 'type': 'KAT'}]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.