Python: элегантные и эффективные способы замаскировать список

Пример:

from __future__ import division
import numpy as np

n = 8
"""masking lists"""
lst = range(n)
print lst

# the mask (filter)
msk = [(el>3) and (el<=6) for el in lst]
print msk

# use of the mask
print [lst[i] for i in xrange(len(lst)) if msk[i]]

"""masking arrays"""
ary = np.arange(n)
print ary

# the mask (filter)
msk = (ary>3)&(ary<=6)
print msk

# use of the mask
print ary[msk]                          # very elegant  

и результаты:

>>> 
[0, 1, 2, 3, 4, 5, 6, 7]
[False, False, False, False, True, True, True, False]
[4, 5, 6]
[0 1 2 3 4 5 6 7]
[False False False False  True  True  True False]
[4 5 6]

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

>>> lst[msk]
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
TypeError: only integer arrays with one element can be converted to an index

Вопрос в том, чтобы найти элегантную маскировку для lists.

Обновления:
Ответ jamylak был принят для представления compress, однако моменты, упомянутые Joel Cornett, сделали решение завершенным до желаемой формы, которая меня интересует.

>>> mlist = MaskableList
>>> mlist(lst)[msk]
>>> [4, 5, 6]

person Developer    schedule 23.04.2012    source источник


Ответы (6)


Вы ищете itertools.compress

Пример из документации

Эквивалентно:

def compress(data, selectors):
    # compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F
    return (d for d, s in izip(data, selectors) if s)
person jamylak    schedule 23.04.2012

Если вы используете Numpy, вы можете легко сделать это с помощью массива Numpy без установки какой-либо другой библиотеки:

>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>> msk = [ True, False, False,  True,  True,  True,  True, False, False, False]
>> a = np.array(a) # convert list to numpy array
>> result = a[msk] # mask a
>> result.tolist()
[0, 3, 4, 5, 6]
person biendltb    schedule 13.08.2018

Поскольку jamylak уже ответил на вопрос практическим ответом, вот мой пример списка со встроенной поддержкой маскирования (совершенно ненужной, кстати):

from itertools import compress
class MaskableList(list):
    def __getitem__(self, index):
        try: return super(MaskableList, self).__getitem__(index)
        except TypeError: return MaskableList(compress(self, index))

Применение:

>>> myList = MaskableList(range(10))
>>> myList
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> mask = [0, 1, 1, 0]
>>> myList[mask]
[1, 2]

Обратите внимание, что compress останавливается, когда заканчиваются данные или маска. Если вы хотите сохранить часть списка, которая выходит за пределы длины маски, вы можете попробовать что-то вроде:

from itertools import izip_longest

[i[0] for i in izip_longest(myList, mask[:len(myList)], fillvalue=True) if i[1]]
person Joel Cornett    schedule 23.04.2012
comment
+1 Спасибо, что обратились к такому же использованию маскирования в списках, как и в массивах, предложив MaskableList. Это выглядит очень интересно и работает очень хорошо, как мое желание. Небольшое замечание: они немного медленнее по сравнению с маскированием массива. Я добавил ваши баллы в качестве обновлений. - person Developer; 23.04.2012
comment
Я попробовал ваше решение MaskableList, но у меня есть некоторые проблемы с его повторным созданием. Для каждого элемента в цикле я хочу замаскировать это новым списком: for i in arange(0,n): fts = MaskableList(F) sorter = argsort(A) result[i] = zip(fts[sorter],A[sorter]), но на каждой итерации fts[sorter] содержит одни и те же значения, тогда как sorter каждый раз разный. Обычно я использую python скорее как язык сценариев, и поэтому я не очень хорошо знаком с объектами. - person Milla Well; 26.01.2013
comment
@Разработчик: я не тестировал это специально, но одна из причин, по которой MaskableList может быть значительно медленнее, заключается в том, что происходит довольно дорогая обработка исключений. Попробуйте переключить try...except так, чтобы он пытался маскировать по умолчанию. - person Joel Cornett; 28.01.2013
comment
@MillaWell: я не знаком с argsort. Кроме того, что такое A и каково содержимое F? - person Joel Cornett; 28.01.2013
comment
@JoelCornett: argsort сортирует массив и возвращает список индексов оригинала. A=[3.5,2.0,1.1,4.0]; argsort(A) вернет [2,1,0,3]. F — это просто немаскируемый список, скажем, [A,B,C,D], поэтому zip(fts[sorter],A[sorter]) должен выводить: {"A":1.1,"B":2.0,"C":3.5,"D":4.0} - person Milla Well; 01.02.2013
comment
@MillaWell: Ну, твоя первая проблема в том, что MaskableList не делает того, что ты думаешь. Он возвращает результат бинарной маски (1, 0 или True/False) в списке. Он не будет переупорядочивать элементы в соответствии со списком индексов. Во-вторых, zip(fts[sorter], A[sorter]) выведет список кортежей, а у вас дикт. - person Joel Cornett; 02.02.2013
comment
@MillaWell: если бы у меня был список myList и список индексов b = argsort(A), я бы сделал newList = [myList[i] for i in b] для достижения желаемого результата. - person Joel Cornett; 02.02.2013
comment
Сначала мне нужно исправить мой пример, желаемый результат, конечно, {C:1.1,B:2.0,A:3.5,D:4.0}. И второе: я на самом деле использую цикл для достижения этого, но мне было любопытно, почему нет возможности индексировать 1-d array. Это выглядело бы намного умнее, так как мне не пришлось бы использовать другой цикл for - person Milla Well; 02.02.2013
comment
@MillaWell: Хммм ... Ну, технически говоря, даже самая быстрая индексация массива потребует от вас использования цикла на каком-то уровне работы (будь то в Python или в базовом коде C, зависит от вашего использования map(), itertools и других оптимизированные инструменты). Поскольку это кажется очень длинной веткой комментариев, могу ли я предложить вам задать вопрос, касающийся нашего разговора? Я не очень хорошо знаком с numpy, и решение, которое вы ищете, может уже быть там. - person Joel Cornett; 02.02.2013
comment
@JoelCornett Я задал вопрос здесь: stackoverflow. com/questions/14664333/ еще раз спасибо за ваши идеи. - person Milla Well; 02.02.2013

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

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

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

Для меня я бы предпочел устранять неполадки в чьем-то коде, который использует длинную форму

print [lst[i] for i in xrange(len(lst)) if msk[i]]

чем маска короткой нотации numpy. Мне не нужно иметь никаких специальных знаний о конкретном пакете Python, чтобы интерпретировать его.

person Jim    schedule 03.12.2015

Следующее прекрасно работает в Python 3:

np.array(lst)[msk]

Если вам нужен список в качестве результата:

np.array(lst)[msk].tolist()
person Jake Drew    schedule 21.01.2021

Вы также можете просто использовать list и zip

  1. определить функцию
def masklist(mylist,mymask):
    return [a for a,b in zip(mylist,mymask) if b]
  1. используй это!
n = 8
lst = range(n)
msk = [(el>3) and (el<=6) for el in lst]
lst_msk = masklist(lst,msk)
print(lst_msk)
person brodegon    schedule 27.05.2020