Попытка понять, почему генератор первичного списка возвращает упорядоченные списки вплоть до n = 8194, после чего списки больше не упорядочены [дублировать]

33
задан mgilson 27 November 2013 в 23:37
поделиться

5 ответов

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

В принципе, python хэширует элементы и берет последние N бит (где N определяется размером набора) и использует эти биты в качестве индексов массива для размещения объекта в памяти. Затем объекты выводятся в том порядке, в котором они существуют в памяти. Конечно, изображение становится немного более сложным, когда вам нужно разрешать столкновения между хэшами, но это его суть.

Также обратите внимание, что порядок их распечатки определяется порядком, который вы положить их (из-за столкновений). Таким образом, если вы переупорядочиваете список, который вы переходите на set_2, вы можете получить другой порядок, если есть ключевые коллизии.

Например:

list1 = [8,16,24]
set(list1)        #set([8, 16, 24])
list2 = [24,16,8]
set(list2)        #set([24, 16, 8])

Обратите внимание: тот факт, что порядок сохраняется в этих наборах, является «совпадением» и имеет отношение к разрешению конфликтов (о котором я ничего не знаю). Дело в том, что последние 3 бита hash(8), hash(16) и hash(24) совпадают. Поскольку они одинаковы, разрешение конфликтов берет на себя и помещает элементы в «резервные» ячейки памяти вместо первого (лучшего) выбора и поэтому определяется ли 8 местоположение или 16, по которому кто-то прибыл на сторону

Если мы повторим пример с 1, 2 и 3, вы получите последовательный порядок независимо от того, какой порядок у них есть в списке входных данных :

list1 = [1,2,3]
set(list1)      # set([1, 2, 3])
list2 = [3,2,1]
set(list2)      # set([1, 2, 3])

, поскольку последние 3 бита hash(1), hash(2) и hash(3) уникальны.


1Note Реализация, описанная здесь, применима к CPython dict и set. Я думаю, что общее описание действительно для всех современных версий CPython до 3.6. Однако, начиная с CPython3.6, есть дополнительная деталь реализации, которая фактически сохраняет порядок вставки для итерации для dict. Похоже, что set до сих пор не обладает этим свойством. Структура данных описывается этой записью в блоге людьми pypy (которые начали использовать это перед людьми CPython). Исходная идея (по крайней мере для экосистемы python) заархивирована в списке рассылки python-dev .

32
ответ дан mgilson 28 August 2018 в 01:55
поделиться

Одна ключевая вещь, которая намекается на отличный ответ mgilson , но явно не упоминается ни в одном из существующих ответов:

Малые целые числа хеш для себя:

>>> [hash(x) for x in (1, 2, 3, 88)]
[1, 2, 3, 88]

Строки hash для значений, которые непредсказуемы. Фактически, с 3.3 по умолчанию, , они построены на семя, которое рандомизировано при запуске . Итак, вы получите разные результаты для каждого нового сеанса интерпретатора Python, но:

>>> [hash(x) for x in 'abcz']
[6014072853767888837,
 8680706751544317651,
 -7529624133683586553,
 -1982255696180680242]

Итак, рассмотрим простейшую возможную реализацию хеш-таблицы: просто массив из N элементов, где вставка значение означает, что он помещается в hash(value) % N (при отсутствии столкновений). И вы можете сделать приблизительное предположение о том, насколько велика N - она ​​будет немного больше, чем количество элементов в ней. При создании набора из последовательности из 6 элементов N легко может быть, например, 8.

Что происходит, когда вы храните эти 5 чисел с N = 8? Ну, hash(1) % 8, hash(2) % 8 и т. Д. Являются только самими числами, но hash(88) % 8 равно 0. Итак, массив хеш-таблицы заканчивается удерживанием 88, 1, 2, NULL, NULL, 5, NULL, 7. Поэтому вам должно быть легко понять, почему итерация набора может дать вам 88, 1, 2, 5, 7.

Конечно, Python не гарантирует , что вы получите этот заказ каждый раз. Небольшое изменение способа, которое он угадывает при правильном значении для N, может означать, что 88 заканчивается где-то другим (или заканчивается столкновением с одним из других значений). И, фактически, запуская CPython 3.7 на моем Mac, я получаю 1, 2, 5, 7, 88 .0

Между тем, когда вы создаете хэш из последовательности размером 11, а затем вставляете в него рандомизированные хэши, что происходит? Даже предполагая простейшую реализацию и предполагая, что нет столкновений, вы все еще не знаете, какой заказ вы собираетесь получить. Он будет согласован в течение одного прогона интерпретатора Python, но при следующем запуске он будет отличаться. (Если вы не установили PYTHONHASHSEED в 0 или какое-то другое значение int.) Это именно то, что вы видите.


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

0
ответ дан abarnert 28 August 2018 в 01:55
поделиться

Причиной такого поведения является то, что Python использует хеш-таблицы для реализации словаря: https://en.wikipedia.org/wiki/Hash_table#Open_addressing

Позиция ключ определяется его адресом памяти. Если вы знаете память повторного использования Python для некоторых объектов:

>>> a = 'Hello world'
>>> id(a)
140058096568768
>>> a = 'Hello world'
>>> id(a)
140058096568480

Вы можете видеть, что объект a имеет разные адреса каждый раз, когда он инициализирован.

Но для небольших целых чисел это не изменение :

>>> a = 1
>>> id(a)
40060856
>>> a = 1
>>> id(a)
40060856

Даже если мы создадим второй объект с другим именем, он будет таким же:

>>> b = 1
>>> id(b)
40060856

Этот подход позволяет сэкономить память, которую использует интерпретатор Python.

4
ответ дан Eugene Soldatov 28 August 2018 в 01:55
поделиться

Наборы Python AFAIK реализованы с использованием хэш-таблицы . Порядок, в котором элементы отображаются, зависит от используемой функции хэша. В рамках одного и того же запуска программы хеш-функция, вероятно, не изменяется, поэтому вы получаете тот же порядок.

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

3
ответ дан MAK 28 August 2018 в 01:55
поделиться

Наборы основаны на хэш-таблице. Хэш значения должен быть последовательным, поэтому порядок будет также - если два элемента хэш не совпадают с одним и тем же кодом, и в этом случае порядок вставки изменит порядок вывода.

2
ответ дан Mark Ransom 28 August 2018 в 01:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: