web-dev-qa-db-fra.com

Comment généraliser la création d'une liste avec de nombreuses variables et conditions de `if`?

Je crée une liste comme suit:

['v0' if x%4==0 else 'v1' if x%4==1 else 'v2' if x%4==2 else 'v3' for x in list_1]

Comment généraliser la création d'une telle liste, afin qu'elle puisse être facilement étendue à un plus grand nombre de variables et de conditions ultérieures?

17
Tomasz Przemski

Formatage de chaîne

Pourquoi ne pas utiliser une opération modulo ici, et faire un formatage de chaîne, comme:

['v{}'.format(x%4) for x in list_1]

Ici, nous calculons donc x%4 et ajoutons ceci à 'v 'dans la chaîne. La bonne chose est que nous pouvons facilement changer 4, en un autre numéro.

Indexation par tuple ou par liste

Si la chaîne de sortie ne suit pas une telle structure, nous pouvons construire une liste ou un tuple pour contenir les valeurs. Comme:

# in case the values do not follow a certain structure
vals = ('v0', 'v1', 'v2', 'v3')
[vals[x%4] for x in list_1]

En l'indexant de cette façon, on voit clairement quelles valeurs mapperont sur quel index. Cela fonctionne bien, étant donné le résultat de l'opération - ici x%4 - mappe sur un n (avec un petit n raisonnable).

Dictionnaire (par défaut)

Au cas où l'opération ne mapperait pas sur un n , mais toujours sur un nombre fini d'éléments hashable, nous pouvons utiliser un dictionnaire. Par exemple:

d = {0: 'v0', 1: 'v1', 2: 'v2', 3: 'v3'}

ou si nous voulons une valeur de "repli", une valeur utilisée étant donné que la recherche échoue:

from collections import defaultdict

d = defaultdict(lambda: 1234, {0: 'v0', 1: 'v1', 2: 'v2', 3: 'v3'})

où here 1234 est utilisé comme valeur de secours et nous pouvons alors utiliser:

[d[x%4] for x in list_1]

Utiliser d[x%4] sur d.get(x%4) étant donné que d est un dictionnaire, il peut être plus utile si nous voulons éviter que les recherches qui échouent passent inaperçues. Ce sera dans ce cas une erreur. Bien que les erreurs ne soient généralement pas un bon signe, il peut être préférable de générer une erreur en cas d'échec de la recherche plutôt que d'ajouter une valeur par défaut, car il peut être symptomatique que quelque chose ne fonctionne pas correctement.

32
Willem Van Onsem

Voici mes tentatives de generic solution. Tout d'abord, la configuration - 

list_1 = [1, 2, 4, 5, 10, 4, 3]

Les deux premières options sont basées sur du pur python, tandis que les deux dernières utilisent des bibliothèques numériques (numpy et pandas).


dict.get

Génère une mapping de clés en valeurs. Dans la compréhension de la liste, interrogez dict.get -

mapping = {0 : 'v0', 1 : 'v1', 2 : 'v2'}
r = [mapping.get(x % 4, 'v3') for x in list_1]

r
['v1', 'v2', 'v0', 'v1', 'v2', 'v0', 'v3']

Ici, 'v3' est la valeur par défaut qui est renvoyée lorsque le résultat de x % 4 n'existe pas en tant que clé dans mapping

Cela fonctionnerait pour tout ensemble arbitraire de conditions et de valeurs, pas uniquement la condition décrite dans la question (arithmétique modulo).


collections.defaultdict

Une solution similaire serait possible en utilisant defaultdict

from collections import defaultdict

mapping = defaultdict(lambda: 'v3', {0: 'v0', 1: 'v1', 2: 'v2', 3: 'v3'})
r = [mapping[x % 4] for x in list_1]

r
['v1', 'v2', 'v0', 'v1', 'v2', 'v0', 'v3']

Cela fonctionne de la même façon que Option 1.


numpy.char.add

Si vous utilisez numpy, une solution vectorisée impliquant l'arithmétique modulo et l'ajout diffusé pourrait vous intéresser - 

r = np.char.add('v', (np.array(list_1) % 4).astype('<U8'))

r
array(['v1', 'v2', 'v0', 'v1', 'v2', 'v0', 'v3'],
      dtype='<U9')

Si vous souhaitez obtenir une liste comme résultat final, vous pouvez appeler r.tolist(). Notez que cette solution est optimisée pour votre cas d'utilisation particulier. Une approche plus générique serait réalisée avec numpy en utilisant np.where/np.select.


pd.Series.mod + pd.Series.radd

Une solution similaire fonctionnerait également avec pandasmod + radd

r = pd.Series(list_1).mod(4).astype(str).radd('v')
r

0    v1
1    v2
2    v0
3    v1
4    v2
5    v0
6    v3
dtype: object

r.tolist()
['v1', 'v2', 'v0', 'v1', 'v2', 'v0', 'v3']
20
coldspeed

Dans l'exemple donné, il est clair que nous pouvons "compresser" les conditions, ce qui conduit aux solutions spécifiques présentées ici. Dans le cas général cependant, nous ne pouvons pas supposer qu’il existe un «truc» pour écrire rapidement toutes les conditions possibles sur une seule ligne.

J'écrirais toutes les conditions dans une fonction:

def conditions(x):
    if x == <option a>:
        return <result a>
    Elif x == <option b>:
        return <result b>
    .
    .
    .
    else:
        return <default option>

Si vous utilisez uniquement des opérations de comparaison, vous pouvez simplement utiliser un collections.defaultdict, comme indiqué dans les autres réponses. Si les conditions sont plus complexes, vous devrez probablement écrire la fonction entière comme indiqué.

Maintenant, pour votre compréhension de la liste, vous pouvez simplement faire:

values = [conditions(x) for x in my_list_of_values]
2
Jonathan Mosenkis
def condition(rule, out):
  return lambda x: out(x) if rule(x) else None

def rule1(x): return x%4 == 0
def out1(x): return 'v0'

def rule2(x): return x%4 == 1
def out2(x): return 'v1'

def rule3(x): return x%4 == 2
def out3(x): return 'v2'

lastrule = lambda x: True
lastout = lambda x: 'v3'

check1 = condition(rule1, out1)
check2 = condition(rule2, out2)
check3 = condition(rule3, out3)
check_last = condition(lastrule, lastout)

def tranform(*check_list):
  def trans_value(x):
    for trans in check_list:
      if trans(x) is not None:
        return trans(x)
  return trans_value

list_1=[4,5,6,7,8]      
print([tranform(check1, check2, check3, check_last)(x) for x in list_1])

Pour les longues vérifications, il peut être plus facile de commencer par dresser une liste de conditions. Supposons que la formule et la sortie conditionnelles sont les deux fonctions de x, pas d'autres paramètres d'entrée. La méthode ci-dessous permet d’économiser du texte tout en maintenant l’additivité pour les contrôles longs.

Pour obtenir une méthode encore plus générique (conditions plus complexes, plusieurs paramètres), certaines procédures composées pourraient s'avérer utiles (par exemple, both(greater, either(smaller, identity)) et le programme entier doit être restructuré à nouveau, ce qui signifie que l'additivité du programme n'est pas encore idéale, car pas assez générique pour le moment.

outconstants = ['v0', 'v1', 'v2', 'v3']
# for this specific example. In general, only outf is needed (see below)
n = len(outconstant)

outf = lambda out: lambda x: out
outs = [outf(out) for out in outconstants]
# define your own outf formula, if not output constant
# define multiple formulas and put into list, if different type of outputs are needed

rights = map(lambda constant: lambda x: constant, range(n-1)) 
lefts = [lambda x: x%4 for _ in range(n-1)]
# right and left formulas can be also defined separately and then put into list

def identity(a, b): return lambda x: a(x) == b(x) 
# define other rules if needed and form them into rules list with proper orders
# e.g., def greater(a, b): return lambda x: a(x) > b(x), ...


lastrule=lambda x: True

rules = list(map(identity, lefts, rights))
rules.append(lastrule)
# in complex case, each unique rule needs to be defined separately and put into list
# if new rule is needed, define it and append here before lastrule (additive)

def transform(rules, outs):
    def trans_value(x):
        for rule, out in Zip(rules, outs):
            if rule(x):
                return out(x)
    return trans_value

list_1=[4,5,6,7,8]
print([transform(rules, outs)(x) for x in list_1])
2
englealuze