Поведение python groupby?

>>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.
где другие цифры?. Может ли кто-нибудь объяснить, что здесь происходит?

Ответ 1

Из docs:

Возвращенная группа сама является итератором, который разделяет базовый итерабель с помощью 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]}

Ответ 2

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

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

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

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

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

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

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

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

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

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

  • Затем вы спрашиваете 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]

Ответ 3

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

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

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

{False: [0, 1, 2, ...], True: [501, 502, 503, ...]}

Для получения дополнительной информации см. документы Python о itertools groupby.