filtrar elementos en un diccionario de Python donde las claves contienen una cadena específica

97

Soy un codificador de C que desarrolla algo en Python. Sé cómo hacer lo siguiente en C (y, por lo tanto, en lógica similar a C aplicada a Python), pero me pregunto cuál es la forma de hacerlo en 'Python'.

Tengo un diccionario d, y me gustaría operar en un subconjunto de los elementos, solo aquellos cuya clave (cadena) contiene una subcadena específica.

es decir, la lógica C sería:

for key in d:
    if filter_string in key:
        # do something
    else
        # do nothing, continue

Me imagino que la versión de Python sería algo así como

filtered_dict = crazy_python_syntax(d, substring)
for key,value in filtered_dict.iteritems():
    # do something

Encontré muchas publicaciones aquí sobre el filtrado de diccionarios, pero no pude encontrar una que involucrara exactamente esto.

Mi diccionario no está anidado y estoy usando Python 2.7

memorándum
fuente

Respuestas:

187

¿Qué tal una comprensión de dict :

filtered_dict = {k:v for k,v in d.iteritems() if filter_string in k}

Una vez que lo vea, debería ser autoexplicativo, ya que se lee bastante bien en inglés.

Esta sintaxis requiere Python 2.7 o superior.

En Python 3, solo hay dict.items(), no, iteritems()por lo que usaría:

filtered_dict = {k:v for (k,v) in d.items() if filter_string in k}
Jonathon Reinhart
fuente
1
¿Por qué no filtered_dict = {k:d[k] for k in d if filter_string in k}?
thefourtheye
5
@thefourtheye Voy a adivinar que el mío es más rápido, ya que no implica la d[k]búsqueda.
Jonathon Reinhart
Además, dice # do somethingen los comentarios, pero dejamos algunas claves aquí.
thefourtheye
¿Tenemos iteritemsen Python 3? No lo creo. Entonces, mi versión sería compatible, ¿no?
thefourtheye
1
En Python 3, reemplazaría iteritemscon items, que es lo mismo que Python 2.7 iteritems.
Jonathon Reinhart
18

Elija lo que sea más legible y fácil de mantener. El hecho de que pueda escribirlo en una sola línea no significa que deba hacerlo. Su solución existente está cerca de lo que usaría aparte de los iteritems de usuario para omitir la búsqueda de valor, y odio los if anidados si puedo evitarlos:

for key, val in d.iteritems():
    if filter_string not in key:
        continue
    # do something

Sin embargo, si realmente desea algo que le permita iterar a través de un dictado filtrado, entonces no haría el proceso de dos pasos de construir el dictado filtrado y luego iterar a través de él, sino que usaría un generador, porque lo que es más pitónico (y asombroso) que un generador?

Primero creamos nuestro generador, y un buen diseño dicta que lo hagamos lo suficientemente abstracto como para ser reutilizable:

# The implementation of my generator may look vaguely familiar, no?
def filter_dict(d, filter_string):
    for key, val in d.iteritems():
        if filter_string not in key:
            continue
        yield key, val

Y luego podemos usar el generador para resolver su problema de manera agradable y limpia con un código simple y comprensible:

for key, val in filter_dict(d, some_string):
    # do something

En resumen: los generadores son increíbles.

Brendan F
fuente
11

Puede utilizar la función de filtro incorporada para filtrar diccionarios, listas, etc. en función de condiciones específicas.

filtered_dict = dict(filter(lambda item: filter_str in item[0], d.items()))

La ventaja es que puede usarlo para diferentes estructuras de datos.

Pulkit
fuente
Tenga en cuenta que items:debería estar item:en la definición lambda.
bkribbs
Gracias @bkribbs por señalar el error. Lo he rectificado ahora.
Pulkit
8
input = {"A":"a", "B":"b", "C":"c"}
output = {k:v for (k,v) in input.items() if key_satifies_condition(k)}
jspurim
fuente
3
Mi método de uso iteritems()será más eficiente que items().
Jonathon Reinhart
@Jonathin Reinhart No lo sabía. Gracias.
jspurim
2
Solo en Python 2.7. En Python 3 solo existe items(), que actúa como Python 2.7 iteritems.
Jonathon Reinhart
1
La pregunta es explícitamente para Python 2.7
Brendan F
7

Jonathon le dio un enfoque utilizando dict comprensiones en su respuesta . Aquí hay un enfoque que se ocupa de su parte de hacer algo .

Si quieres hacer algo con los valores del diccionario, no necesitas comprensión del diccionario en absoluto:

Que estoy usando iteritems() desde que etiquetó su pregunta con

results = map(some_function, [(k,v) for k,v in a_dict.iteritems() if 'foo' in k])

Ahora el resultado estará en una lista que se some_functionaplicará a cada par clave / valor del diccionario, que tiene fooen su clave.

Si solo desea lidiar con los valores e ignorar las claves, simplemente cambie la comprensión de la lista:

results = map(some_function, [v for k,v in a_dict.iteritems() if 'foo' in k])

some_function puede ser cualquier invocable, por lo que una lambda también funcionaría:

results = map(lambda x: x*2, [v for k,v in a_dict.iteritems() if 'foo' in k])

La lista interna en realidad no es necesaria, ya que también puede pasar una expresión generadora al mapa:

>>> map(lambda a: a[0]*a[1], ((k,v) for k,v in {2:2, 3:2}.iteritems() if k == 2))
[4]
Burhan Khalid
fuente
interesante. ¿Cómo se definiría alguna_función? en el primer caso (k, v), ¿solo se necesitan dos parámetros? primero clave luego valor?
memorando
Sí, solo un llamable. Entonces map(lambda a: a[0]*a[1], ((k,v) for k,v in {2:2, 3:2}.iteritems() if k == 2)), esto te dará [4].
Burhan Khalid
Esto es correcto, pero más pitónico que usar mapes una lista de comprensión. [f(v) for k, v in d.iteritems() if substring in k]Creo que es mucho más legible y más eficiente.
Davidmh
@memo No se necesitarían dos parámetros, se necesitaría un solo parámetro con dos elementos. También hay un mapa de estrellas que se descompondrá en dos argumentos, sin embargo, es un iterador perezoso (debe iterarse antes de que se ejecute, es decir, results = list(starmap(...))o for result in starmap(...): ...).
nmclean