Estoy trabajando en un pequeño y divertido proyecto de Python, y estoy atascado tratando de adaptar mi bucle If al Diccionario, pero no estoy seguro de cómo implementar esta función, ya que necesito varias condiciones para probar.

Tengo seis booleanos (bool1-bool6) que obviamente pueden ser T o F, y necesito probar todas las combinaciones posibles de estos booleanos, para poder decirle a mi programa dónde dibujar las imágenes.

Hay 64 combinaciones posibles.

Podemos hacer esto con 3 booleanos para simplificarlo. Hay 8 combinaciones posibles para 3 booleanos.

Si imaginamos que 1 = verdadero y 0 = falso, entonces las combinaciones posibles se pueden representar como tales.

000
001
010
011
100
101
110
111

Un bucle if para representar esto sería,

if (bool1==false and bool2==false and bool3==false)
      do stuff
elif (bool1==false and bool2==false and bool3==true)
     do stuff
elif (bool1==false and bool2==true and bool3==false)
     do stuff

and so on...

Por favor, a menos que pueda encontrar una manera de simplificar este proceso (entendiendo que necesito verificar TODAS las combinaciones POSIBLES de booleanos), no hay necesidad de criticar mi pregunta. Simplemente no estoy seguro de cómo progresar desde aquí, y agradecería enormemente alguna ayuda.

He escrito una declaración 64 If Loop, y actualmente estoy trabajando en esa solución, aunque tanto yo como estoy seguro de que mi CPU preferiría un método más rápido.

0
Owen Jim 29 oct. 2017 a las 13:42

3 respuestas

La mejor respuesta

Este es un ejemplo perfecto para usar la representación de bits de enteros

Tienes tus seis variables booleanas bool1 hasta bool6.

Lo que quieres es una representación de bits como el siguiente ejemplo:

101101

Donde el bit más a la izquierda representa bool6 y el más a la derecha para bool1. Entonces obtienes el pedido bool6, bool5, bool4, ..., bool1.

Ahora asigna una variable con el estado, representada por esas variables booleanas:

state = bool6 << 5
state = state ^ (bool5 << 4)
state = state ^ (bool4 << 3)
state = state ^ (bool3 << 2)
state = state ^ (bool2 << 1)
state = state ^ (bool1)

Encontrará una explicación para << aquí

Lo que sucedió ahora es que definió una nueva variable entera, representada por sus diferentes valores booleanos.

Usando la representación de bits

Dependiendo de su posterior implementación do_something, usted define los parámetros del método de esta manera:

cases = {
    0b000011 : "drawing parameter",
    0b001011 : "drawing parameter",
    0b111000 : "another drawing parameter",
}

Donde, las teclas del diccionario son sus bits desde 000000 hasta 111111

Además, puede verificar si cada caso tiene un parámetro definido:

for n in range(1<<6):
    if not n in cases:
       raise LookupError('cases do not contain index %d' % n)

Entonces, más adelante, puede usar estas representaciones de bits como en el siguiente.

for c in cases:
    if state == c: # if the checked case is equal to the state, then use the parameters
       parameters = cases[c]
       do_something(parameters)
1
maik-s 31 oct. 2017 a las 11:53

Para 6 booleanos, hay 2 ^ 6 combinaciones que corresponden directamente a los bits en los números desde 0 hasta 2^6-1 inclusive. Si necesita definir parámetros especiales para una sola llamada de función, puede resolver esto teniendo una lista o diccionario de parámetros, y luego llamar a esa función con cada parámetro:

cases = {
    0b000000: ((arg1a, arg2y), {'key1': kwarg1, 'key2': kwarg2}),
    0b000001: ((arg1b, arg2x), {'key23': kwarg23, 'key7': kwarg7}),
    # ...
    0b111111: ((arg1a, arg2z), {'key4': kwarg4, 'key2': kwarg2}),
}

for n, (args, kwargs) in cases.items():
    print("Test case {:d} ({:#08b}):".format(n,n))
    test_fun(*args, **kwargs)

Si no necesita argumentos de palabras clave, puede simplificar eso fácilmente.

Si necesita hacer cosas más específicas, puede tener lambdas en el diccionario en lugar de conjuntos de parámetros:

cases = {
    0b000000: lambda: test_fun(1,2, foo='bar'),
    0b000001: lambda: test_fun(3,2, moo='bar'),
    # ...
    0b111111: lambda: test_fun(8,3, foo='bar'),
    }

for n, fun in cases.items():
    print("Test case {:d} ({:#08b}):".format(n,n))
    fun()

También puede convertir esos diccionarios en listas y los índices dict en comentarios:

cases = [
    # 0b000000
    ((arg1a, arg2y), {'key1': kwarg1, 'key2': kwarg2}),
    # 0b000001
    ((arg1b, arg2x), {'key23': kwarg23, 'key7': kwarg7}),
    # ...
    # 0b111111
    ((arg1a, arg2z), {'key4': kwarg4, 'key2': kwarg2}),
]

for n, (args, kwargs) in enumerate(cases):
    print("Test case {:d} ({:#08b}):".format(n,n))
    test_fun(*args, **kwargs)

En todos los casos, me aseguraría de colocar una verificación de coherencia directamente después de la definición de cases, como

# for the `cases` dictionary
for n in range(1<<6):
    if not n in cases:
        raise LookupError("dict variable cases does not contain %d" % n)

# for the `cases` list
assert(len(cases) == (1<<6))

Si puede derivar fácilmente los conjuntos de parámetros del número de índice, puede evitar definir esa lista completa o dictar casos de prueba y simplemente calcular los valores de los parámetros sobre la marcha:

for n in range(1<<6):
    param1 = 23
    if (n & (1<<0)):
        param1 = 42
    # ...
    param6 = bool(n & (1<<5))

    # ...
    test_fun(param1, ..., param6)

O puede poner la lógica de generación de parámetros en una función generadora, opcionalmente reemplazando la expresión de tupla en la declaración de rendimiento con un (posiblemente reversed()) list.

def all_bool_tuples_from_bits(n):
    for k in range(1<<n):
        yield (bool(k&(1<<i)) for i in range(n))

print("Run test_fun with the arguments")
for args in all_bool_tuples_from_bits(6):
    test_fun(*args)

print("Run test_fun with the arguments in explicit variables")
for p0, p1, p2, p3, p4, p5 in all_bool_tuples_from_bits(6):
    test_fun(p0, p1, p2, p3, p4, p5)

print("Run test_fun with the arguments while numbering the test cases")
for n, args in enumerate(all_bool_tuples_from_bits(6)):
    print("Test case {:d} ({:#08b}):".format(n,n))
    test_fun(*args)

Espero que esto te dé algunas ideas para trabajar.

Con respecto al uso de la CPU, no me preocuparía en absoluto por solo 64 casos de prueba diferentes, cada uno de los cuales genera un archivo PDF que seguramente será mucho más intensivo en la CPU que la iteración en 64 casos de prueba.

La verdadera preocupación es que podrías olvidarte de enumerar un caso en esa enorme construcción if elif elif elif y no notarlo tú mismo. Por lo tanto, me gustaría usar el código para que la computadora se asegure de que se examinen todas las combinaciones. Por solo 6 parámetros booleanos, usaría el generador.

0
ndim 30 oct. 2017 a las 01:36

El enfoque int-to-binary es inteligente pero no generaliza. Hay una manera más fácil que también se generaliza a los tipos de datos que no sean booleanos:

import itertools
for i in itertools.product((0, 1,), repeat=3):
    ... stuff ...

Esto devolverá tuplas como (0, 0, 0), (0, 0, 1) ... (1, 1, 1).

Puede cambiar el primer parámetro sin nombre a cualquier iterable (xrange(), conjunto, matriz, etc.) y el parámetro repeat a la longitud deseada. Funciona para cualquier tipo de datos.

0
Chris Johnson 30 oct. 2017 a las 01:59