Ermitteln des Index von Elementen basierend auf einer Bedingung mithilfe des Python-Listenverständnisses

119

Der folgende Python-Code scheint sehr langwierig zu sein, wenn er aus einem Matlab-Hintergrund stammt

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

Wenn ich in Matlab bin, kann ich schreiben:

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

Gibt es eine Kurzhandmethode, um dies in Python zu schreiben, oder bleibe ich einfach bei der Langversion?


Vielen Dank für alle Vorschläge und Erklärungen zur Begründung der Python-Syntax.

Nachdem ich auf der numpy-Website Folgendes gefunden habe, habe ich eine Lösung gefunden, die mir gefällt:

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

Das Anwenden der Informationen von dieser Website auf mein obiges Problem würde Folgendes ergeben:

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

Folgendes sollte dann funktionieren (aber ich habe keinen Python-Interpreter zur Hand, um es zu testen):

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
quelle
6
Wie wäre es [idx for idx in range(len(a)) if a[idx] > 2]? Der Grund, warum dies in Python etwas umständlich ist, liegt darin, dass Indizes nicht so häufig verwendet werden wie in anderen Sprachen.
NullUserException

Antworten:

77
  • In Python würden Sie dafür überhaupt keine Indizes verwenden, sondern sich nur mit den Werten befassen - [value for value in a if value > 2]. Normalerweise bedeutet der Umgang mit Indizes, dass Sie etwas nicht am besten tun.

  • Wenn Sie noch eine API müssen ähnlich wie Matlab, würden Sie verwenden numpy , ein Paket für mehrdimensionale Arrays und numerische Mathematik in Python , die stark von Matlab inspiriert. Sie würden ein Numpy-Array anstelle einer Liste verwenden.

    >>> 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
quelle
2
Wenn Sie Listen haben, eine für Bereiche und eine für Winkel, möchten Sie die Bereichswerte herausfiltern, die über einem bestimmten Schwellenwert liegen. Wie filtern Sie auch die Winkel, die diesen Bereichen entsprechen, "am besten"?
Mehdi
3
filtered_ranges_and_angles = [(range, angle) for range, angle in zip(ranges, angles) if should_be_kept(range)]
Mike Graham
7
"In Python würden Sie dafür überhaupt keine Indizes verwenden, sondern sich nur mit den Werten befassen." Diese Aussage zeigt, dass Sie nicht genug Datenanalyse und Modellierung des maschinellen Lernens durchgeführt haben. Indizes eines Tensors basierend auf bestimmten Bedingungen werden verwendet, um einen anderen Tensor zu filtern.
HoraceT
63

Ein anderer Weg:

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

Im Allgemeinen denken Sie daran , dass , während findeine fertig zubereitete Funktion sind Listenkomprehensionen ein General, und damit sehr leistungsfähige Lösung . Nichts hindert Sie daran, eine findFunktion in Python zu schreiben und sie später nach Ihren Wünschen zu verwenden. Dh:

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

Beachten Sie, dass ich hier Listen verwende, um Matlab nachzuahmen. Es wäre pythonischer, Generatoren und Iteratoren zu verwenden.

Eli Bendersky
quelle
2
Das OP hätte es [i for i,v in enumerate(a) if v > 2]stattdessen so schreiben können .
NullUserException
Das ist nicht kürzer, es ist länger. Ersetzen Sie im Original indexdurch iund valuemit vund zählen Sie die Zeichen.
Agf
@NullUser, agf: Sie haben Recht, aber der Hauptpunkt ist der zweite Teil :)
Eli Bendersky
1
Die Verwendung von enumerateover range(len(...))ist sowohl robuster als auch effizienter.
Mike Graham
1
@ Mike Graham: Ich stimme zu - wird die find_indiceszu verwendende Funktion ändernenumerate
Eli Bendersky
22

Bei mir funktioniert es gut:

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

Vielleicht ist eine andere Frage: "Was machen Sie mit diesen Indizes, wenn Sie sie erhalten?" Wenn Sie sie zum Erstellen einer weiteren Liste verwenden möchten, sind sie in Python ein unnötiger mittlerer Schritt. Wenn Sie alle Werte möchten, die einer bestimmten Bedingung entsprechen, verwenden Sie einfach den integrierten Filter:

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

Oder schreiben Sie Ihre eigene Liste:

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

Wenn Sie sie aus der Liste entfernen möchten, besteht die pythonische Methode nicht darin, sie unbedingt aus der Liste zu entfernen, sondern ein Listenverständnis zu schreiben, als würden Sie eine neue Liste erstellen und sie mithilfe listvar[:]der linken Seite wieder an Ort und Stelle zuweisen -Seite:

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

Matlab liefert, findweil sein Array-zentriertes Modell Elemente anhand ihrer Array-Indizes auswählt. Sie können dies sicherlich in Python tun, aber der pythonischere Weg ist die Verwendung von Iteratoren und Generatoren, wie bereits von @EliBendersky erwähnt.

PaulMcG
quelle
Paul, ich habe noch keine Notwendigkeit dafür in einem Skript / einer Funktion / einer Klasse gefunden. Es ist mehr für das interaktive Testen einer Klasse, die ich schreibe.
Lee
@Mike - danke für die Bearbeitung, aber ich meinte es wirklich a[:] = ...- siehe Alex Martellis Antwort auf diese Frage stackoverflow.com/questions/1352885/… .
PaulMcG
@Paul, ich nahm an (und hoffte!), Dass du es aus deiner Beschreibung nicht wirklich so gemeint hast, dass du "eine neue Liste erstellen" würdest; Ich finde, dass Programme leichter zu verstehen und zu pflegen sind, wenn sie vorhandene Daten sehr sparsam mutieren. Es tut mir auf jeden Fall leid, dass ich zu viel getan habe - Sie sollten Ihren Beitrag auf jeden Fall wieder so bearbeiten können, wie Sie es möchten.
Mike Graham
6

Auch wenn es eine späte Antwort ist: Ich denke, dies ist immer noch eine sehr gute Frage, und IMHO Python (ohne zusätzliche Bibliotheken oder Toolkits wie numpy) fehlt immer noch eine bequeme Methode, um auf die Indizes von Listenelementen gemäß einem manuell definierten Filter zuzugreifen.

Sie können manuell eine Funktion definieren, die diese Funktionalität bietet:

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

Ausbeuten: [0, 4]

In meiner Vorstellung wäre der perfekte Weg, eine untergeordnete Listenklasse zu erstellen und die Indizes als Klassenmethode hinzuzufügen. Auf diese Weise würde nur die Filtermethode benötigt:

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)

Ich habe hier etwas mehr zu diesem Thema ausgeführt: http://tinyurl.com/jajrr87

Gerhard Hagerer
quelle