Получение уникальных значений из списка в python
Я хочу получить уникальные значения из следующего списка:
['nowplaying', 'PBS', 'PBS', 'nowplaying', 'job', 'debate', 'thenandnow']
Вывод, который мне требуется, это:
['nowplaying', 'PBS', 'job', 'debate', 'thenandnow']
Этот код работает:
output = []
for x in trends:
if x not in output:
output.append(x)
print(output)
есть ли лучшее решение, которое я должен использовать?
Переведено автоматически
Ответ 1
Сначала правильно объявите свой список, разделяя его запятыми. Вы можете получить уникальные значения, преобразовав список в набор.
mylist = ['nowplaying', 'PBS', 'PBS', 'nowplaying', 'job', 'debate', 'thenandnow']
myset = set(mylist)
print(myset)
Если вы будете использовать его в дальнейшем как список, вам следует преобразовать его обратно в список, выполнив:
mynewlist = list(myset)
Другой возможностью, вероятно, более быстрой, было бы использовать set с самого начала вместо list . Тогда ваш код должен быть:
output = set()
for x in trends:
output.add(x)
print(output)
Как уже указывалось, наборы не сохраняют исходный порядок. Если вам это нужно, вам следует поискать реализацию ordered set (подробнее см. Этот вопрос).
Ответ 2
Чтобы соответствовать типу, который я бы использовал:
mylist = list(set(mylist))
Ответ 3
Если нам нужно сохранить порядок элементов, как насчет этого:
used = set()
mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']
unique = [x for x in mylist if x not in used and (used.add(x) or True)]
И еще одно решение с использованием reduce
временного used
параметра и без него.
mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']
unique = reduce(lambda l, x: l.append(x) or l if x not in l else l, mylist, [])
ОБНОВЛЕНИЕ - декабрь 2020 г. - Возможно, лучший подход!
Начиная с python 3.7, стандартный dict сохраняет порядок вставки.
Изменено в версии 3.7: порядок словаря гарантированно соответствует порядку вставки. Такое поведение было деталью реализации CPython из версии 3.6.
Таким образом, это дает нам возможность использовать dict.fromkeys()
для устранения дублирования!
ПРИМЕЧАНИЕ: Спасибо @rlat за то, что предоставили нам этот подход в комментариях!
mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']
unique = list(dict.fromkeys(mylist))
С точки зрения скорости - для меня это достаточно быстрый и читабельный подход, который стал моим новым любимым подходом!
ОБНОВЛЕНИЕ - март 2019 г.
И третье решение, которое является аккуратным, но немного медленным, поскольку .index
равно O (n).
mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']
unique = [x for i, x in enumerate(mylist) if i == mylist.index(x)]
ОБНОВЛЕНИЕ - октябрь 2016 г.
Еще одно решение с reduce
, но на этот раз без .append
, которое делает его более удобочитаемым для человека и более понятным.
mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']
unique = reduce(lambda l, x: l+[x] if x not in l else l, mylist, [])
#which can also be writed as:
unique = reduce(lambda l, x: l if x in l else l+[x], mylist, [])
ПРИМЕЧАНИЕ: Имейте в виду, что чем более удобочитаемым мы становимся, тем более неэффективным становится скрипт. За исключением только dict.fromkeys()
подхода, специфичного для python 3.7+.
import timeit
setup = "mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']"
#10x to Michael for pointing out that we can get faster with set()
timeit.timeit('[x for x in mylist if x not in used and (used.add(x) or True)]', setup='used = set();'+setup)
0.2029558869980974
timeit.timeit('[x for x in mylist if x not in used and (used.append(x) or True)]', setup='used = [];'+setup)
0.28999493700030143
# 10x to rlat for suggesting this approach!
timeit.timeit('list(dict.fromkeys(mylist))', setup=setup)
0.31227896199925453
timeit.timeit('reduce(lambda l, x: l.append(x) or l if x not in l else l, mylist, [])', setup='from functools import reduce;'+setup)
0.7149233570016804
timeit.timeit('reduce(lambda l, x: l+[x] if x not in l else l, mylist, [])', setup='from functools import reduce;'+setup)
0.7379565160008497
timeit.timeit('reduce(lambda l, x: l if x in l else l+[x], mylist, [])', setup='from functools import reduce;'+setup)
0.7400134069976048
timeit.timeit('[x for i, x in enumerate(mylist) if i == mylist.index(x)]', setup=setup)
0.9154880290006986
ОТВЕТЫ НА КОММЕНТАРИИ
Потому что @monica задала хороший вопрос о том, "как это работает?". Для всех, у кого возникли проблемы с пониманием этого. Я постараюсь дать более глубокое объяснение о том, как это работает и какое волшебство здесь происходит ;)
Итак, она сначала спросила:
Я пытаюсь понять, почему
unique = [used.append(x) for x in mylist if x not in used]
не работает.
Ну, это действительно работает
>>> used = []
>>> mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']
>>> unique = [used.append(x) for x in mylist if x not in used]
>>> print used
[u'nowplaying', u'PBS', u'job', u'debate', u'thenandnow']
>>> print unique
[None, None, None, None, None]
Проблема в том, что мы просто не получаем желаемых результатов внутри unique
переменной, а только внутри used
переменной. Это происходит потому, что во время понимания списка .append
изменяет used
переменную и возвращает None
.
Итак, чтобы получить результаты в unique
переменной и при этом использовать ту же логику с .append(x) if x not in used
, нам нужно переместить этот .append
вызов в правую часть списка понимания и просто вернуть x
в левую часть.
Но если мы слишком наивны и просто используем:
>>> unique = [x for x in mylist if x not in used and used.append(x)]
>>> print unique
[]
Мы ничего не получим взамен.
Опять же, это происходит потому, что .append
метод возвращает None
, и это придает нашему логическому выражению следующий вид:
x not in used and None
В принципе, это всегда:
- вычисляется как
False
когдаx
находится вused
, - вычисляется как
None
когдаx
отсутствует вused
.
И в обоих случаях (False
/None
) это будет обработано как falsy
значение, и в результате мы получим пустой список.
Но почему это оценивается как None
когда x
нет в used
? Кто-то может спросить.
Ну, это потому, что именно так работают операторы короткого замыкания в Python.
Выражение
x and y
сначала вычисляет x; если x равно false, возвращается его значение; в противном случае вычисляется y и возвращается результирующее значение.
Поэтому, когда x
не используется (т.Е. Когда его True
) будет вычислена следующая часть или выражение (used.append(x)
) и будет возвращено его значение (None
).
Но это то, чего мы хотим, чтобы получить уникальные элементы из списка с дубликатами, мы хотим .append
помещать их в новый список только тогда, когда они попадались нам впервые.
Итак, мы действительно хотим оценивать used.append(x)
только тогда, когда x
нет в used
, может быть, если есть способ превратить это None
значение в truthy
единицу, у нас все будет хорошо, верно?
Ну, да, и вот тут в игру вступают операторы 2-го типа short-circuit
.
Выражение
x or y
сначала вычисляет x; если x равно true, возвращается его значение; в противном случае вычисляется y и возвращается результирующее значение.
Мы знаем, что это .append(x)
будет всегда falsy
, поэтому, если мы просто добавим одно or
рядом с ним, мы всегда получим следующую часть. Вот почему мы пишем:
x not in used and (used.append(x) or True)
таким образом, мы можем вычислять used.append(x)
и получать True
в результате, только когда первая часть выражения (x not in used)
является True
.
Аналогичный способ можно увидеть во 2-м подходе с reduce
методом.
(l.append(x) or l) if x not in l else l
#similar as the above, but maybe more readable
#we return l unchanged when x is in l
#we append x to l and return l when x is not in l
l if x in l else (l.append(x) or l)
где мы:
- Добавляйте
x
вl
и возвращайте это значение,l
когдаx
его нет вl
. Благодаряor
инструкции.append
вычисляется иl
возвращается после этого. - Возвращает
l
нетронутыми, когдаx
находится вl
Ответ 4
Список на Python:
>>> a = ['a', 'b', 'c', 'd', 'b']
Чтобы получить уникальные элементы, просто преобразуйте их в набор (который при необходимости вы можете преобразовать обратно в список):
>>> b = set(a)
>>> print(b)
{'b', 'c', 'd', 'a'}