поведение Python Groupby? - PullRequest
       21

поведение Python Groupby?

14 голосов
/ 04 июня 2011
>>from itertools import groupby
>>keyfunc = lambda x : x > 500
>>obj = dict(groupby(range(1000), keyfunc))
>>list(obj[True])
[999]
>>list(obj[False])
[]

диапазон (1000) явно сортируется по умолчанию для условия (x> 500).Я ожидал, что числа от 0 до 999 будут сгруппированы в dict по условию (x> 500).Но полученный словарь имел только 999.где остальные номера?Кто-нибудь может объяснить, что здесь происходит?

Ответы [ 3 ]

22 голосов
/ 04 июня 2011

Из документов :

Возвращенная группа сама является итератором, который разделяет базовую итерацию с groupby().Поскольку источник является общим, когда объект groupby() является расширенным, предыдущая группа больше не отображается.Итак, если эти данные понадобятся позже, они должны быть сохранены в виде списка [.]

И вы сохраняете итераторы в obj и материализуете их позже.

In [21]: dict((k, list(g)) for k, g in groupby(range(10), lambda x : x > 5))
Out[21]: {False: [0, 1, 2, 3, 4, 5], True: [6, 7, 8, 9]}
10 голосов
/ 04 июня 2011

Итератор groupby возвращает наборы результатов функции группировки и новый итератор, связанный с тем же «внешним» итератором, над которым работает оператор groupby. Когда вы применяете dict() к итератору, возвращенному groupby, не потребляя этот «внутренний» итератор, groupby должен будет продвинуть «внешний» итератор за вас. Вы должны понимать, что функция groupby не действует на последовательность, она превращает любую такую ​​последовательность в итератор для вас.

Возможно, это лучше объяснить некоторыми метафорами и рукопожатием. Пожалуйста, следуйте, пока мы формируем линию ведра.

Представьте себе итераторов как человека, который черпает воду в ведрах из колодца. У него есть неограниченное количество ведер для использования, но колодец может быть конечным. Каждый раз, когда вы просите у этого человека ведро с водой, он достает из колодца с водой новое ведро и передает его вам.

В случае groupby вы вставляете другого человека в свою начинающую цепочку ведер. Этот человек не сразу передает ведра вообще. Он передает вам результат инструкций, которые вы ему дали, плюс другого человека каждый раз, когда вы просите ведро, который затем передает вам ведра через человека groupby тому, кто спрашивает, при условии, что они соответствуют тот же результат в инструкции. groupby Проходчик сегментов прекратит передачу этих интервалов, если результат выполнения команд изменится. Так что well дает ведра для groupby, который передает это человеку по группе, group A, group B и т. Д.

В вашем примере вода пронумерована, но из колодца можно извлечь только 1000 ведер. Вот что происходит, когда вы затем передаете groupby человека на dict() вызов:

  1. Ваш dict() звонок просит groupby за ведро. Теперь groupby просит одного ведра у человека в колодце, запоминает результат выполнения данных инструкций, держась за ведро. На dict() он передаст результат инструкции (False) плюс новый человек, group A. Результат сохраняется в качестве ключа, а group A человек, который хочет вытащить ведра, сохраняется в качестве значения. Этот человек не все же просит о ведрах, потому что никто не просит его .

  2. Ваш dict() звонок запрашивает groupby другое ведро. groupby имеет эти инструкции и отправляется на поиски следующего сегмента, где меняется результат. Он все еще держался за первое ведро, никто его не просил, поэтому он выбрасывает это ведро. Вместо этого он просит следующее ведро из скважины и использует его инструкции. Результат тот же, что и раньше, поэтому он выбрасывает и это новое ведро! Больше воды течет по полу, и поэтому идут следующие 499 ведер. Только когда пропущен сегмент с номером 501, результат изменяется, поэтому теперь groupby находит другого человека, который дает инструкции (person group B), вместе с новым результатом, True, передавая эти два значения dict().

  3. Ваш dict() вызов сохраняет True в качестве ключа, а person group B в качестве значения. group B ничего не делает, никто не просит воды.

  4. Ваш dict() просит другое ведро. groupby проливает больше воды, пока в нем не окажется ведро с номером 999, и человек у колодца пожимает плечами и заявляет, что теперь колодец пуст. groupby говорит dict() колодец пуст, больше нет ведер, может, он перестанет спрашивать. Он по-прежнему удерживает ведро с номером 999, потому что ему никогда не нужно освободить место для следующего ведра из скважины.

  5. Теперь вы идете и спрашиваете dict() о том, что связано с ключом True, то есть лицом group B. Вы передаете group B на list(), и поэтому group B запрашивает все корзины group B. group B восходит к groupby, который держит только одну корзину, корзину с номером 999, и результат инструкций для этой корзины соответствует тому, что ищет group B. Итак, это одно ведро group B дает list(), затем пожимает плечами, потому что больше нет ведер, потому что groupby сказал ему об этом.

  6. Затем вы спрашиваете dict() о человеке, связанном с ключом False, который является человеком group A.К настоящему времени groupby больше нечего дать, колодец сухой, и он стоит в луже 999 ведер воды с числами, плавающими вокруг.Ваш второй list() ничего не получает.

Мораль этой истории?Немедленно попросите все ведра воды, когда говорите с groupby, потому что он разлит их всех, если вы этого не сделаете!Итераторы подобны метлам в фантазии, старательно перемещая воду без понимания, и вам лучше надеяться, что у вас кончится вода, если вы не знаете, как их контролировать.

Вот код, который будет делать то, что вы ожидаете (снемного меньше воды, чтобы предотвратить затопление):

>>> from itertools import groupby
>>> keyfunc = lambda x : x > 5
>>> obj = dict((k, list(v)) for k, v in groupby(range(10), keyfunc))
>>> obj(True)
[0, 1, 2, 3, 4, 5]
>>> obj(False)
[6, 7, 8, 9]
4 голосов
/ 04 июня 2011

Единственное, чего вам не хватает, так это того, что функция groupby выполняет итерации по заданному вами range(1000), возвращая, таким образом, 1000 значений.Вы сохраняете только последний, в вашем случае 999.Что вам нужно сделать, это перебрать возвращаемые значения и сохранить их в свой словарь:

dictionary = {}
keyfunc = lambda x : x > 500
for k, g in groupby(range(1000), keyfunc):
    dictionary[k] = list(g)

Таким образом, вы получите ожидаемый результат:информацию смотрите в документации по Python о itertools groupby .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...