Сортировка и группировка вложенных списков в Python

У меня есть следующая структура данных (список списков)

[
 ['4', '21', '1', '14', '2008-10-24 15:42:58'], 
 ['3', '22', '4', '2somename', '2008-10-24 15:22:03'], 
 ['5', '21', '3', '19', '2008-10-24 15:45:45'], 
 ['6', '21', '1', '1somename', '2008-10-24 15:45:49'], 
 ['7', '22', '3', '2somename', '2008-10-24 15:45:51']
]

я хотел бы иметь возможность

  1. Используйте функцию для изменения порядка списка, чтобы я мог группировать по каждому элементу в списке. Например, я хотел бы иметь возможность группировать по второму столбцу (чтобы все 21 были вместе)

  2. Используйте функцию для отображения только определенных значений из каждого внутреннего списка. Например, я хотел бы уменьшить этот список, чтобы он содержал только 4-е значение поля «2somename».

поэтому список будет выглядеть так

[
     ['3', '22', '4', '2somename', '2008-10-24 15:22:03'], 
     ['7', '22', '3', '2somename', '2008-10-24 15:45:51']
]

person m3clov3n    schedule 03.01.2009    source источник
comment
второстепенный момент, но вам, вероятно, следует использовать кортежи вместо внутренних списков   -  person    schedule 04.01.2009


Ответы (8)


Для первого вопроса первое, что вы должны сделать, это отсортировать список по второму полю, используя itemgetter из модуля оператора:

x = [
 ['4', '21', '1', '14', '2008-10-24 15:42:58'], 
 ['3', '22', '4', '2somename', '2008-10-24 15:22:03'], 
 ['5', '21', '3', '19', '2008-10-24 15:45:45'], 
 ['6', '21', '1', '1somename', '2008-10-24 15:45:49'], 
 ['7', '22', '3', '2somename', '2008-10-24 15:45:51']
]

from operator import itemgetter

x.sort(key=itemgetter(1))

Затем вы можете использовать функцию itertools groupby:

from itertools import groupby
y = groupby(x, itemgetter(1))

Теперь y — это итератор, содержащий кортежи (элемент, итератор элементов). Объяснять эти кортежи сложнее, чем показывать код:

for elt, items in groupby(x, itemgetter(1)):
    print(elt, items)
    for i in items:
        print(i)

Что печатает:

21 <itertools._grouper object at 0x511a0>
['4', '21', '1', '14', '2008-10-24 15:42:58']
['5', '21', '3', '19', '2008-10-24 15:45:45']
['6', '21', '1', '1somename', '2008-10-24 15:45:49']
22 <itertools._grouper object at 0x51170>
['3', '22', '4', '2somename', '2008-10-24 15:22:03']
['7', '22', '3', '2somename', '2008-10-24 15:45:51']

Для второй части вы должны использовать понимание списка, как уже упоминалось здесь:

from pprint import pprint as pp
pp([y for y in x if y[3] == '2somename'])

Что печатает:

[['3', '22', '4', '2somename', '2008-10-24 15:22:03'],
 ['7', '22', '3', '2somename', '2008-10-24 15:45:51']]
person llimllib    schedule 03.01.2009
comment
Я добавил пример понимания списка. - person jfs; 03.01.2009
comment
Этот ответ был написан давно, и в настоящее время вы должны использовать выражение генератора вместо понимания списка: pp(y for y in x if y[3] == '2somename') - person llimllib; 25.06.2017
comment
нет. Это неверно. генэкспр здесь не подходит. Попробуйте запустить код. - person jfs; 25.06.2017

Если вы присвоили его var "a"...

питон 2.х:

#1:

a.sort(lambda x,y: cmp(x[1], y[1]))

#2:

filter(lambda x: x[3]=="2somename", a)

питон 3:

#1:

a.sort(key=lambda x: x[1])
person Jimmy2Times    schedule 03.01.2009
comment
Более простой и чистый подход, чем itemgetter - person Hamman Samuel; 30.06.2016
comment
лямбда для победы. Мне очень понравилось это решение - person alfredocambera; 10.11.2016

Если я правильно понял ваш вопрос, следующий код должен выполнить эту работу:

l = [
 ['4', '21', '1', '14', '2008-10-24 15:42:58'], 
 ['3', '22', '4', '2somename', '2008-10-24 15:22:03'], 
 ['5', '21', '3', '19', '2008-10-24 15:45:45'], 
 ['6', '21', '1', '1somename', '2008-10-24 15:45:49'], 
 ['7', '22', '3', '2somename', '2008-10-24 15:45:51']
]

def compareField(field):
   def c(l1,l2):
      return cmp(l1[field], l2[field])
   return c

# Use compareField(1) as the ordering criterion, i.e. sort only with
# respect to the 2nd field
l.sort(compareField(1))
for row in l: print row

print
# Select only those sublists for which 4th field=='2somename'
l2somename = [row for row in l if row[3]=='2somename']
for row in l2somename: print row

Выход:

['4', '21', '1', '14', '2008-10-24 15:42:58']
['5', '21', '3', '19', '2008-10-24 15:45:45']
['6', '21', '1', '1somename', '2008-10-24 15:45:49']
['3', '22', '4', '2somename', '2008-10-24 15:22:03']
['7', '22', '3', '2somename', '2008-10-24 15:45:51']

['3', '22', '4', '2somename', '2008-10-24 15:22:03']
['7', '22', '3', '2somename', '2008-10-24 15:45:51']
person Federico A. Ramponi    schedule 03.01.2009
comment
Аргумент cmp для сортировки удаляется в версиях 2.6/3.0, поэтому предпочтительнее использовать параметр key, который извлекает ключ сортировки, а в противном случае — +1. - person Aaron Maenpaa; 03.01.2009
comment
удален 'cmp=', в любом случае должен быть первым аргументом. Кстати, я использую python 2.6.1, и все работает нормально... - person Federico A. Ramponi; 03.01.2009

Используйте функцию для изменения порядка списка, чтобы я мог группировать по каждому элементу в списке. Например, я хотел бы иметь возможность группировать по второму столбцу (чтобы все 21 были вместе)

Списки имеют встроенный метод сортировки, и вы можете предоставить функцию, которая извлекает ключ сортировки.

>>> import pprint
>>> l.sort(key = lambda ll: ll[1])
>>> pprint.pprint(l)
[['4', '21', '1', '14', '2008-10-24 15:42:58'],
 ['5', '21', '3', '19', '2008-10-24 15:45:45'],
 ['6', '21', '1', '1somename', '2008-10-24 15:45:49'],
 ['3', '22', '4', '2somename', '2008-10-24 15:22:03'],
 ['7', '22', '3', '2somename', '2008-10-24 15:45:51']]

Используйте функцию для отображения только определенных значений из каждого внутреннего списка. Например, я хотел бы уменьшить этот список, чтобы он содержал только 4-е значение поля «2somename».

Это похоже на задание для понимания списков

>>> [ll[3] for ll in l]
['14', '2somename', '19', '1somename', '2somename']
person Aaron Maenpaa    schedule 03.01.2009
comment
Замените [ll[3] for ll in l] на [ll for ll in l if ll[3] == '2somename'] и исправьте вывод. - person jfs; 03.01.2009

Если вы будете много сортировать и фильтровать, вам могут понравиться некоторые вспомогательные функции.

m = [
 ['4', '21', '1', '14', '2008-10-24 15:42:58'], 
 ['3', '22', '4', '2somename', '2008-10-24 15:22:03'], 
 ['5', '21', '3', '19', '2008-10-24 15:45:45'], 
 ['6', '21', '1', '1somename', '2008-10-24 15:45:49'], 
 ['7', '22', '3', '2somename', '2008-10-24 15:45:51']
]

# Sort and filter helpers.
sort_on   = lambda pos:     lambda x: x[pos]
filter_on = lambda pos,val: lambda l: l[pos] == val

# Sort by second column
m = sorted(m, key=sort_on(1))

# Filter on 4th column, where value = '2somename'
m = filter(filter_on(3,'2somename'),m)
person Triptych    schedule 03.01.2009
comment
sort_on == operator.itemgetter - person jfs; 03.01.2009
comment
Пожалуйста, используйте DEF вместо лямбда-выражений. - person S.Lott; 03.01.2009
comment
@ s.lott - почему здесь defs вместо lambdas? - person Triptych; 04.01.2009
comment
@Triptych: потому что лямбды с именем такие же, как определения, но более запутанные и абсолютно бесполезные. - person nosklo; 05.01.2009
comment
Мех. В этом случае я думаю, что лямбда более читабельна. И больше путаницы, безусловно, субъективно! - person Triptych; 05.01.2009
comment
defs здесь будет четыре строки для каждой функции вместо одной. - person Thomas Ahle; 03.04.2014

Для части (2), где x - ваш массив, я думаю, вы хотите,

[y for y in x if y[3] == '2somename']

Который вернет список только ваших списков данных, четвертое значение которых равно «2somename»... Хотя кажется, что Камиль дает вам лучший совет по переходу на SQL...

person Community    schedule 03.01.2009

Похоже, вы пытаетесь использовать список в качестве базы данных.

В настоящее время Python включает привязки sqlite в основной дистрибутив. Если вам не нужно постоянство, очень легко создать базу данных sqlite в памяти (см. in-memory-database">Как мне создать базу данных sqllite3 в памяти?).

Затем вы можете использовать операторы SQL для выполнения всей этой сортировки и фильтрации, не изобретая велосипед.

person Kamil Kisiel    schedule 03.01.2009
comment
Камиль, вы правы. Однако я изучаю Python и хотел делать что-то, используя списки, чтобы узнать о них больше. Я проверю это, хотя спасибо - person m3clov3n; 03.01.2009

Вы просто создаете индексы в своей структуре, верно?

>>> from collections import defaultdict
>>> def indexOn( things, pos ):
...     inx= defaultdict(list)
...     for t in things:
...             inx[t[pos]].append(t)
...     return inx
... 
>>> a=[
...  ['4', '21', '1', '14', '2008-10-24 15:42:58'], 
...  ['3', '22', '4', '2somename', '2008-10-24 15:22:03'], 
...  ['5', '21', '3', '19', '2008-10-24 15:45:45'], 
...  ['6', '21', '1', '1somename', '2008-10-24 15:45:49'], 
...  ['7', '22', '3', '2somename', '2008-10-24 15:45:51']
... ]

Вот ваш первый запрос, сгруппированный по позиции 1.

>>> import pprint
>>> pprint.pprint( dict(indexOn(a,1)) )
{'21': [['4', '21', '1', '14', '2008-10-24 15:42:58'],
        ['5', '21', '3', '19', '2008-10-24 15:45:45'],
        ['6', '21', '1', '1somename', '2008-10-24 15:45:49']],
 '22': [['3', '22', '4', '2somename', '2008-10-24 15:22:03'],
        ['7', '22', '3', '2somename', '2008-10-24 15:45:51']]}

Вот ваш второй запрос, сгруппированный по позиции 3.

>>> dict(indexOn(a,3))
{'19': [['5', '21', '3', '19', '2008-10-24 15:45:45']], '14': [['4', '21', '1', '14', '2008-10-24 15:42:58']], '2somename': [['3', '22', '4', '2somename', '2008-10-24 15:22:03'], ['7', '22', '3', '2somename', '2008-10-24 15:45:51']], '1somename': [['6', '21', '1', '1somename', '2008-10-24 15:45:49']]}
>>> pprint.pprint(_)
{'14': [['4', '21', '1', '14', '2008-10-24 15:42:58']],
 '19': [['5', '21', '3', '19', '2008-10-24 15:45:45']],
 '1somename': [['6', '21', '1', '1somename', '2008-10-24 15:45:49']],
 '2somename': [['3', '22', '4', '2somename', '2008-10-24 15:22:03'],
               ['7', '22', '3', '2somename', '2008-10-24 15:45:51']]} 
person S.Lott    schedule 03.01.2009