Python liste anlama özelliğini kullanarak bir koşula göre elemanların dizinini bulma


119

Aşağıdaki Python kodu, bir Matlab arka planından geldiğinde çok uzun soluklu görünüyor

>>> a = [1, 2, 3, 1, 2, 3]
>>> [index for index,value in enumerate(a) if value > 2]
[2, 5]

Matlab'dayken şunları yazabilirim:

>> a = [1, 2, 3, 1, 2, 3];
>> find(a>2)
ans =
     3     6

Bunu Python'da yazmanın kısa bir yöntemi var mı, yoksa sadece uzun versiyona mı bağlıyım?


Python'un sözdiziminin gerekçesinin tüm önerileri ve açıklamaları için teşekkür ederiz.

Numpy web sitesinde aşağıdakileri bulduktan sonra, beğendiğim bir çözüm bulduğumu düşünüyorum:

http://docs.scipy.org/doc/numpy/user/basics.indexing.html#boolean-or-mask-index-arrays

Bu web sitesindeki bilgileri yukarıdaki sorunuma uygulamak aşağıdakileri verir:

>>> from numpy import array
>>> a = array([1, 2, 3, 1, 2, 3])
>>> b = a>2 
array([False, False, True, False, False, True], dtype=bool)
>>> r = array(range(len(b)))
>>> r(b)
[2, 5]

Aşağıdakiler daha sonra çalışmalıdır (ancak test etmek için elimde bir Python yorumlayıcısı yok):

class my_array(numpy.array):
    def find(self, b):
        r = array(range(len(b)))
        return r(b)


>>> a = my_array([1, 2, 3, 1, 2, 3])
>>> a.find(a>2)
[2, 5]

6
Nasıl olur [idx for idx in range(len(a)) if a[idx] > 2]? Bunun Python'da yapılması biraz garip olmasının nedeni, diğer diller kadar dizin kullanmamasıdır.
NullUserException

Yanıtlar:


77
  • Python'da bunun için indeks kullanmazsınız, sadece değerlerle ilgilenirsiniz— [value for value in a if value > 2]. Genellikle dizinlerle uğraşmak, bir şeyi en iyi şekilde yapmadığınız anlamına gelir.

  • Eğer varsa do Matlab'ınkine benzer bir API ihtiyaç şunları kullanırsınız numpy , çok boyutlu diziler ve ağır Matlab esinlenmiştir Python sayısal matematik için bir paket. Liste yerine uyuşmuş bir dizi kullanırsınız.

    >>> import numpy
    >>> a = numpy.array([1, 2, 3, 1, 2, 3])
    >>> a
    array([1, 2, 3, 1, 2, 3])
    >>> numpy.where(a > 2)
    (array([2, 5]),)
    >>> a > 2
    array([False, False,  True, False, False,  True], dtype=bool)
    >>> a[numpy.where(a > 2)]
    array([3, 3])
    >>> a[a > 2]
    array([3, 3])

2
Listeleriniz var, biri aralıklar ve diğeri açılar için, bazı eşiklerin üzerindeki aralık değerlerini filtrelemek istiyorsunuz. Bu aralıklara karşılık gelen açıları da "en iyi şekilde" nasıl filtreleyebilirsiniz?
Mehdi

3
filtered_ranges_and_angles = [(range, angle) for range, angle in zip(ranges, angles) if should_be_kept(range)]
Mike Graham

7
"Python'da, bunun için indeks kullanmazsınız, sadece değerlerle ilgilenirsiniz" bu ifade, yeterince veri analizi ve makine öğrenimi modellemesi yapmadığınızı gösterir. Belirli bir koşula dayalı bir tensörün indisleri, başka bir tensörü filtrelemek için kullanılır.
horaceT

63

Diğer yol:

>>> [i for i in range(len(a)) if a[i] > 2]
[2, 5]

Genel olarak, hazır bir işlev olmasına rağmen find, liste anlamalarının genel ve dolayısıyla çok güçlü bir çözüm olduğunu unutmayın . findPython'da bir işlev yazmanızı ve daha sonra istediğiniz gibi kullanmanızı hiçbir şey engellemez . yani:

>>> def find_indices(lst, condition):
...   return [i for i, elem in enumerate(lst) if condition(elem)]
... 
>>> find_indices(a, lambda e: e > 2)
[2, 5]

Matlab'ı taklit etmek için buradaki listeleri kullandığımı unutmayın. Üreteçleri ve yineleyicileri kullanmak daha Pythonic olurdu.


2
Operatör bunu onun [i for i,v in enumerate(a) if v > 2]yerine yazabilirdi.
NullUserException

Bu daha kısa değil, daha uzun. Orijinaldeki indexile ive valueile değiştirin ve vkarakterleri sayın.
agf

@NullUser, agf: haklısın, ama asıl konu ikinci kısım :)
Eli Bendersky

1
enumerateOver kullanmak range(len(...))hem daha sağlam hem de daha verimlidir.
Mike Graham

1
@Mike Graham: Kabul ediyorum - değişecek find_indices kullanılacak işlevienumerate
Eli Bendersky

22

Benim için iyi çalışıyor:

>>> import numpy as np
>>> a = np.array([1, 2, 3, 1, 2, 3])
>>> np.where(a > 2)[0]
[2 5]

6

Belki başka bir soru şudur: "Bu endeksleri aldıktan sonra ne yapacaksın?" Bunları başka bir liste oluşturmak için kullanacaksanız, Python'da bunlar gereksiz bir orta adımdır. Belirli bir koşulla eşleşen tüm değerleri istiyorsanız, yerleşik filtreyi kullanın:

matchingVals = filter(lambda x : x>2, a)

Veya kendi listenizi yazın:

matchingVals = [x for x in a if x > 2]

Bunları listeden çıkarmak istiyorsanız, Pythonic yolu listeden illa ki çıkarılmak zorunda değildir, ancak yeni bir liste oluşturuyormuşsunuz gibi bir liste anlayışı yazmak listvar[:]ve sol taraftaki -yan:

a[:] = [x for x in a if x <= 2]

Matlab find, dizi merkezli modeli, dizi indekslerini kullanarak öğeleri seçerek çalıştığı için sağlar. Bunu kesinlikle Python'da yapabilirsiniz , ancak daha Pythonic yolu @EliBendersky tarafından daha önce bahsedildiği gibi yineleyiciler ve üreteçler kullanmaktır.


Paul, henüz bir betik / işlev / sınıfta buna ihtiyaç duymadım. Daha çok yazdığım bir sınıfın interaktif testi için.
Lee

@Mike - düzenleme için teşekkürler, ama gerçekten demek istedim a[:] = ...- Alex Martelli'nin bu soruya verdiği cevaba bakın stackoverflow.com/questions/1352885/… .
PaulMcG

@Paul, açıklamanızdan "yeni bir liste oluşturacağınızı" gerçekten kastetmediğini varsaydım (ve umuyordum!); Programların mevcut verileri çok idareli bir şekilde değiştirdiklerinde anlama ve sürdürme eğiliminde olduğunu görüyorum. Her halükarda, abarttığım için özür dilerim - kesinlikle yayınınızı istediğiniz gibi düzenleyebilmelisiniz.
Mike Graham

6

Geç cevap olsa bile: Sanırım bu hala çok iyi bir soru ve IMHO Python (numpy gibi ek kitaplıklar veya araç setleri olmadan) hala manuel olarak tanımlanmış bir filtreye göre liste öğelerinin dizinlerine erişmek için uygun bir yöntemden yoksun.

Bu işlevi sağlayan bir işlevi manuel olarak tanımlayabilirsiniz:

def indices(list, filtr=lambda x: bool(x)):
    return [i for i,x in enumerate(list) if filtr(x)]

print(indices([1,0,3,5,1], lambda x: x==1))

Verim: [0, 4]

Benim hayal gücüme göre mükemmel bir yol, bir alt sınıf liste yapmak ve indeksleri eklemek sınıf yöntemi olarak işlev görmek olurdu. Bu şekilde yalnızca filtre yöntemine ihtiyaç duyulur:

class MyList(list):
    def __init__(self, *args):
        list.__init__(self, *args)
    def indices(self, filtr=lambda x: bool(x)):
        return [i for i,x in enumerate(self) if filtr(x)]

my_list = MyList([1,0,3,5,1])
my_list.indices(lambda x: x==1)

Bu konuda biraz daha detaylandırdım: http://tinyurl.com/jajrr87

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.