Menemukan indeks elemen berdasarkan kondisi menggunakan pemahaman daftar python

119

Kode Python berikut tampaknya sangat bertele-tele ketika datang dari latar belakang Matlab

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

Saat di Matlab saya bisa menulis:

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

Apakah ada metode tangan pendek untuk menulis ini dengan Python, atau apakah saya tetap menggunakan versi panjang?


Terima kasih atas semua saran dan penjelasan tentang alasan sintaksis Python.

Setelah menemukan yang berikut di situs web numpy, saya rasa saya telah menemukan solusi yang saya suka:

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

Menerapkan informasi dari situs web itu ke masalah saya di atas, akan memberikan yang berikut:

>>> 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]

Berikut ini seharusnya berfungsi (tetapi saya belum memiliki juru bahasa Python untuk mengujinya):

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]
Lee
sumber
6
Bagaimana dengan [idx for idx in range(len(a)) if a[idx] > 2]? Alasan mengapa hal ini agak canggung untuk dilakukan dengan Python adalah karena ia tidak menggunakan indeks sebanyak bahasa lain.
NullUserException

Jawaban:

77
  • Dengan Python, Anda tidak akan menggunakan indeks untuk ini sama sekali, tetapi hanya berurusan dengan nilainya— [value for value in a if value > 2]. Biasanya berurusan dengan indeks berarti Anda tidak melakukan sesuatu dengan cara terbaik.

  • Jika Anda memang membutuhkan API yang mirip dengan Matlab, Anda akan menggunakan numpy , sebuah paket untuk array multidimensi dan matematika numerik dengan Python yang sangat terinspirasi oleh Matlab. Anda akan menggunakan array numpy daripada daftar.

    >>> 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])
Mike Graham
sumber
2
Anda memiliki daftar, satu untuk rentang dan satu untuk sudut, Anda ingin memfilter nilai rentang yang berada di atas beberapa ambang batas. Bagaimana Anda juga memfilter sudut yang sesuai dengan rentang tersebut dengan cara "cara terbaik"?
Mehdi
3
filtered_ranges_and_angles = [(range, angle) for range, angle in zip(ranges, angles) if should_be_kept(range)]
Mike Graham
7
"Dengan Python, Anda tidak akan menggunakan indeks untuk ini sama sekali, tetapi hanya berurusan dengan nilai" pernyataan ini menunjukkan Anda belum melakukan cukup analisis data dan pemodelan pembelajaran mesin. Indeks satu tensor berdasarkan kondisi tertentu digunakan untuk memfilter tensor lainnya.
horaceT
63

Cara lain:

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

Secara umum, ingatlah bahwa sementara findadalah fungsi yang sudah dimasak, pemahaman daftar adalah solusi umum, dan dengan demikian sangat kuat . Tidak ada yang menghalangi Anda untuk menulis findfungsi dengan Python dan menggunakannya nanti sesuai keinginan. Yaitu:

>>> 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]

Perhatikan bahwa saya menggunakan daftar di sini untuk meniru Matlab. Akan lebih Pythonic menggunakan generator dan iterator.

Eli Bendersky
sumber
2
OP bisa saja menuliskannya sebagai [i for i,v in enumerate(a) if v > 2]gantinya.
NullUserException
Itu tidak lebih pendek, itu lebih panjang. Gantikan indexdengan idan valuedengan vdalam aslinya dan hitung karakternya.
agf
@NullUser, agf: Anda benar, tetapi poin utamanya adalah bagian kedua :)
Eli Bendersky
1
Menggunakan enumeratelebih dari range(len(...))keduanya lebih kuat dan lebih efisien.
Mike Graham
1
@ Mike Graham: Saya setuju - akan mengubah find_indicesfungsi yang akan digunakanenumerate
Eli Bendersky
22

Bagi saya ini bekerja dengan baik:

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

Mungkin pertanyaan lain adalah, "apa yang akan Anda lakukan dengan indeks tersebut setelah Anda mendapatkannya?" Jika Anda akan menggunakannya untuk membuat daftar lain, maka dengan Python, itu adalah langkah tengah yang tidak perlu. Jika Anda menginginkan semua nilai yang cocok dengan kondisi tertentu, cukup gunakan filter bawaan:

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

Atau tulis komprhension daftar Anda sendiri:

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

Jika Anda ingin menghapusnya dari daftar, maka cara Pythonic tidak selalu menghapus dari daftar, tetapi menulis pemahaman daftar seolah-olah Anda sedang membuat daftar baru, dan menetapkan kembali di tempat menggunakan listvar[:]di sebelah kiri -sisi:

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

Matlab memasok findkarena model larik-sentrisnya bekerja dengan memilih item menggunakan indeks lariknya. Anda dapat melakukan ini dengan Python, tentu saja, tetapi cara yang lebih Pythonic adalah menggunakan iterator dan generator, seperti yang telah disebutkan oleh @EliBendersky.

PaulMcG
sumber
Paul, saya belum menemukan kebutuhan untuk ini di skrip / fungsi / kelas. Ini lebih untuk pengujian interaktif kelas yang saya tulis.
Lee
@ Mike - terima kasih atas pengeditannya, tetapi saya benar-benar bermaksud a[:] = ...- lihat jawaban Alex Martelli untuk pertanyaan ini stackoverflow.com/questions/1352885/… .
PaulMcG
@Paul, saya berasumsi (dan berharap!) Anda tidak benar-benar bersungguh-sungguh dari deskripsi Anda bahwa Anda akan "membuat daftar baru"; Saya menemukan bahwa program cenderung lebih mudah untuk dipahami dan dipelihara ketika mereka mengubah data yang ada dengan sangat hemat. Bagaimanapun, saya minta maaf untuk melangkahi - Anda pasti dapat mengedit posting Anda kembali ke apa pun yang Anda inginkan.
Mike Graham
6

Bahkan jika itu adalah jawaban yang terlambat: Saya pikir ini masih merupakan pertanyaan yang sangat bagus dan IMHO Python (tanpa pustaka tambahan atau toolkit seperti numpy) masih kekurangan metode yang nyaman untuk mengakses indeks elemen daftar menurut filter yang ditentukan secara manual.

Anda dapat menentukan fungsi secara manual, yang menyediakan fungsionalitas itu:

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))

Hasil: [0, 4]

Dalam imajinasi saya, cara yang sempurna adalah membuat daftar kelas anak dan menambahkan fungsi indeks sebagai metode kelas. Dengan cara ini, hanya metode filter yang dibutuhkan:

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)

Saya menjelaskan sedikit lebih banyak tentang topik itu di sini: http://tinyurl.com/jajrr87

Gerhard Hagerer
sumber