У меня есть список списков на Python:
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
И я хочу удалить из него повторяющиеся элементы. Было бы, если бы это был обычный список, а не списки, которые я мог бы использовать set
. Но, к сожалению, этот список не хэшируемый и не может составлять набор списков. Только кортежей. Итак, я могу превратить все списки в кортежи, затем использовать набор и вернуться к спискам. Но это не так быстро.
Как это сделать наиболее эффективно?
Результатом приведенного выше списка должен быть:
k = [[5, 6, 2], [1, 2], [3], [4]]
Я не забочусь о сохранении порядка.
Примечание: этот вопрос похож, но не совсем то, что мне нужно. Искал SO, но не нашел точной копии.
Бенчмаркинг:
import itertools, time
class Timer(object):
def __init__(self, name=None):
self.name = name
def __enter__(self):
self.tstart = time.time()
def __exit__(self, type, value, traceback):
if self.name:
print '[%s]' % self.name,
print 'Elapsed: %s' % (time.time() - self.tstart)
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] * 5
N = 100000
print len(k)
with Timer('set'):
for i in xrange(N):
kt = [tuple(i) for i in k]
skt = set(kt)
kk = [list(i) for i in skt]
with Timer('sort'):
for i in xrange(N):
ks = sorted(k)
dedup = [ks[i] for i in xrange(len(ks)) if i == 0 or ks[i] != ks[i-1]]
with Timer('groupby'):
for i in xrange(N):
k = sorted(k)
dedup = list(k for k, _ in itertools.groupby(k))
with Timer('loop in'):
for i in xrange(N):
new_k = []
for elem in k:
if elem not in new_k:
new_k.append(elem)
"loop in" (квадратичный метод) быстрее всех для коротких списков. Для длинных списков это быстрее, чем у всех, кроме метода группировки. Имеет ли это смысл?
Для короткого списка (тот, что в коде) 100000 итераций:
[set] Elapsed: 1.3900001049
[sort] Elapsed: 0.891000032425
[groupby] Elapsed: 0.780999898911
[loop in] Elapsed: 0.578000068665
Для более длинного списка (тот, что в коде продублирован 5 раз):
[set] Elapsed: 3.68700003624
[sort] Elapsed: 3.43799996376
[groupby] Elapsed: 1.03099989891
[loop in] Elapsed: 1.85900020599
Ответы:
itertools
часто предлагает самые быстрые и эффективные решения такого рода проблем, и с ними стоит хорошо ознакомиться! -)Изменить : как я упоминал в комментарии, обычные усилия по оптимизации сосредоточены на больших входных данных (подход большого О), потому что это намного проще, поскольку он обеспечивает хорошую отдачу от усилий. Но иногда (в основном для «трагически критических узких мест» в глубоких внутренних циклах кода, которые раздвигают границы пределов производительности), может потребоваться более подробное рассмотрение, обеспечение распределения вероятностей, решение, какие показатели производительности следует оптимизировать (возможно, верхняя граница или 90-й центиль важнее среднего или медианного значения, в зависимости от приложения), выполняя, возможно, эвристические проверки в начале для выбора различных алгоритмов в зависимости от характеристик входных данных и т. д.
Тщательные измерения «точечной» производительности (код A и код B для конкретного входа) являются частью этого чрезвычайно дорогостоящего процесса, и
timeit
здесь помогает стандартный библиотечный модуль . Однако его проще использовать в приглашении оболочки. Например, вот небольшой модуль, демонстрирующий общий подход к этой проблеме, сохраните его какnodup.py
:Обратите внимание на проверку работоспособности (выполняется, когда вы просто делаете
python nodup.py
) и базовую технику подъема (сделайте постоянные глобальные имена локальными для каждой функции для скорости), чтобы уравнять все.Теперь мы можем запустить проверку крошечного списка примеров:
подтверждение того, что квадратичный подход имеет достаточно малые константы, чтобы сделать его привлекательным для крошечных списков с небольшим количеством повторяющихся значений. С кратким списком без дубликатов:
квадратичный подход неплох, но лучше сортировка и группировка. И т. Д. И т. Д.
Если (как подсказывает одержимость производительностью) эта операция выполняется в основном внутреннем цикле вашего приложения, выходящего за границы, стоит попробовать тот же набор тестов на других репрезентативных образцах входных данных, возможно, обнаружив какую-то простую меру, которая может эвристически позволить вам выберите тот или иной подход (но, конечно, мера должна быть быстрой).
Также стоит подумать о том, чтобы сохранить другое представление
k
- почему это должен быть список списков, а не набор кортежей? Если задача удаления дубликатов выполняется часто и профилирование показывает, что это узкое место в производительности программы, например, постоянное сохранение набора кортежей и получение из него списка списков только в том случае и там, где это необходимо, может быть в целом быстрее.источник
Делаем это вручную, создаем новый
k
список и добавляем записи, которые пока не найдены:Просто для понимания, и вы сохраняете порядок первого появления каждого элемента, если это будет полезно, но я предполагаю, что он квадратичен по сложности, поскольку вы ищете все
new_k
для каждого элемента.источник
k = ([[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] +[[x] for x in range(1000)]) *5
прекрасно покажет квадратичное поведениеЯ не знаю, обязательно ли это быстрее, но вам не обязательно использовать кортежи и наборы.
источник
random
и установите времяtime
.Все
set
связанные решения этой проблемы до сих пор требуют создания целогоset
перед итерацией.Можно сделать это «ленивым» и в то же время сохранить порядок, перебирая список списков и добавляя к «увиденному»
set
. Затем выдает список, только если он не найден в этом трекереset
.Этот
unique_everseen
рецепт доступен вitertools
документации . Он также доступен в стороннейtoolz
библиотеке:Обратите внимание, что
tuple
преобразование необходимо, потому что списки не хешируются.источник
Даже ваш «длинный» список довольно короткий. Кроме того, вы выбрали их, чтобы они соответствовали фактическим данным? Производительность будет зависеть от того, как на самом деле выглядят эти данные. Например, у вас есть короткий список, который повторяется снова и снова, чтобы составить более длинный список. Это означает, что квадратичное решение линейно в ваших тестах, но не в действительности.
Для действительно больших списков лучше всего использовать код набора - он линейный (хотя и требует много места). Методы sort и groupby - O (n log n), а метод loop in, очевидно, является квадратичным, поэтому вы знаете, как они будут масштабироваться, когда n станет действительно большим. Если это реальный размер данных, которые вы анализируете, то кого это волнует? Он крошечный.
Между прочим, я вижу заметное ускорение, если я не сформирую промежуточный список для создания набора, то есть если я заменю
с участием
Реальное решение может зависеть от дополнительной информации: вы уверены, что список списков действительно является тем представлением, которое вам нужно?
источник
Список кортежей и {} можно использовать для удаления дубликатов
источник
Создайте словарь с кортежем в качестве ключа и распечатайте ключи.
источник
Это должно работать.
источник
Как ни странно, приведенные выше ответы удаляют «дубликаты», но что, если я хочу также удалить дублированное значение? Следующее должно быть полезно и не создает новый объект в памяти!
и o / p:
источник
Другое, вероятно, более общее и простое решение - создать словарь с ключом строковой версии объектов и получить значения () в конце:
Загвоздка в том, что это работает только для объектов, строковое представление которых является достаточно хорошим уникальным ключом (что верно для большинства собственных объектов).
источник