2010-05-14 5 views
1

Supposons que j'aie une liste d'éléments et que je veuille sélectionner aléatoirement un élément de la liste qui satisfait un prédicat. Quelle est la manière pythonique de le faire?façon pythonique de choisir une valeur aléatoire qui satisfait un certain prédicat

Je fais actuellement une compréhension suivie d'une random.choice() mais qui est inutilement inefficace:

intlist = [1,2,3,4,5,6,7,8,9] 
evenlist = [ i for i in intlist if i % 2 == 0 ] 
randomeven = random.choice(evenlist) 

Merci!

+0

même le sélecteur _random_ doit savoir quoi choisir. cette ligne de code supplémentaire vous blesse? – mykhal

+0

Je dirais que cela dépend du nombre d'éléments exclus par votre prédicat. Si elle est inférieure à 50%, alors choisir d'abord un élément de manière aléatoire et tester le prédicat par la suite pourrait être plus efficace. D'un autre côté, si seulement quelques éléments correspondent à votre prédicat, il pourrait être préférable de les filtrer auparavant. –

+0

@Felix_Kling Même si vous excluez moins de 50% des éléments de la liste, il n'y a aucun moyen de savoir combien de temps cela prendra pour l'exécuter. –

Répondre

2

La façon dont vous l'avez écrit ci-dessus est en fait bon python idiomatiques. Si nous analysons l'algorithme, nous verrons qu'il fait essentiellement ceci:

  1. Faire une liste d'éléments qui satisfont le prédicat. (Croît linéairement avec n)
  2. Choisir un élément aléatoire dans cette liste. (Temps constant)

La seule autre façon de procéder serait de choisir un élément au hasard, de décider s'il satisfait le prédicat, et de choisir à nouveau si ce n'est pas le cas. Cet algorithme est un peu plus complexe. Dans le cas où 90% de la liste satisfait le prédicat, cela fonctionnera beaucoup plus vite que votre solution. Dans le cas où seulement 10% de la liste satisfait le prédicat, il fonctionnera beaucoup plus lentement, car il y a de fortes chances qu'il choisisse aléatoirement un élément donné et vérifie si le prédicat est satisfait sur cet élément plus d'une fois. Maintenant, vous pouvez envisager de mémoriser votre prédicat, mais vous allez toujours sélectionner beaucoup de données aléatoires. Cela se résume à ceci: à moins que votre solution ne soit particulièrement inadaptée à vos données, restez-y, parce que c'est génial. Personnellement, je réécris comme ceci:

intlist = range(1,10) 
randomeven = random.choice([i for i in intlist if i % 2 == 0]) 

C'est un peu plus concis, mais il va courir exactement le même que votre code existant.

+0

Parfois, vous devez rejeter l'exemple de rejet, comme si vous ne pouviez pas énumérer tous les choix possibles, mais vous ne pouviez pas utiliser 'random.choice' dans ce cas ... –

1

Je ne pouvais pas trouver une sorte de fonction de random.selectspecific(list, predicate) dans la documentation, donc je voudrais essayer quelque chose comme ce qui suit:

import random 
def selectspecific(l, predicate): 
    result = random.choice(l) 
    while (not predicate(result)): 
     result = random.choice(l) 
    return result 
+0

agréable.il peut être modifié pour être en conformité avec le principe DRY en utilisant la boucle True/Break – mykhal

+3

, il n'y a aucun moyen de savoir combien cette solution va coûter, il pourrait prendre "forever" pour prendre un élément valide de la liste. Le filtrage semble d'abord être la meilleure solution. –

+0

Si aucun élément de la liste ne renvoie True à partir de 'predicate()', alors cette boucle sera infinie. – tdedecko

1
import random 

intlist = [1,2,3,4,5,6,7,8,9] 
randomeven = random.choice(filter(lambda x: x % 2 == 0, intlist))                                       
+0

Dang, tu m'as battu de 10 secondes ... – computergeek6

+0

J'ai posté la même réponse avec itertools.ifilter. Cependant, cela ne fonctionne pas car random.choice doit connaître la longueur réelle des données. Bien sûr! – joaquin

+2

Ceci est juste une version légèrement moins lisible du code dans sa question. Un filtre avec un lambda est effectivement identique à une compréhension de liste. – Benson

0

Comment pythonic est-ce?

from itertools import ifilterfalse 
from random import choice 
print choice([ i for i in ifilterfalse(lambda x: x%2, range(10)) ]) 
0

Si vous souhaitez encapsuler davantage, vous pouvez créer une méthode pour gérer la sélection, qui accepterait une méthode de prédicat. Vous pouvez ensuite utiliser cette méthode de prédicat avec filter().

import random 
def selectSpecific(intlist, predicate): 
    filteredList = filter(predicate, intlist) 
    result = random.choice(filteredList) 
    return result 

Vous pouvez passer un prédicat à selectSpecific() comme lambda ou toute autre méthode. Exemple:

intlist = range(1,10) 
selectSpecific(intlist, lambda x: x % 2 == 0) 

def makeEven(n): 
    if n % 2 == 0: 
    return n 

selectSpecific(intlist, makeEven) 
Questions connexes