组合条件语句排列的最佳方法

Best way to combine a permutation of conditional statements

因此,我要根据 4 个条件变量执行一系列操作 - 比方说 x、y、z 和 t。这些变量中的每一个都有一个可能的 True 或 False 值。因此,总共有 16 种可能的排列。我需要为每个排列执行不同的操作。

最好的方法是什么而不是制作一个巨大的 if-else 结构。

让我们看一个简化的例子。如果我尝试将所有不同的排列包含到一个大的 if-else 结构中,我的代码将是这样的。

if (x == True):
    if (y == True):
        if (z == True):
            if (t == True):
                print ("Case 1")
            else:
                print ("Case 2")
        else:
            if (t == True):
                print ("Case 3")
            else:
                print ("Case 4")
    else:
        if (z == True):
            if (t == True):
                print ("Case 5")
            else:
                print ("Case 6")
        else:
            if (t == True):
                print ("Case 7")
            else:
                print ("Case 8")
else:
    if (y == True):
        if (z == True):
            if (t == True):
                print ("Case 9")
            else:
                print ("Case 10")
        else:
            if (t == True):
                print ("Case 11")
            else:
                print ("Case 12")
    else:
        if (z == True):
            if (t == True):
                print ("Case 13")
            else:
                print ("Case 14")
        else:
            if (t == True):
                print ("Case 15")
            else:
                print ("Case 16")

有什么办法可以简化这个吗?显然,我对每种情况的 objective 比仅打印 "Case 1".

更复杂

您可以将所有值放入一个元组中并使用 16 个元组比较。

if   (x, y, z, t) == (True,  True,  True,  True):  print("Case 1")
elif (x, y, z, t) == (True,  True,  True,  False): print("Case 2")
elif (x, y, z, t) == (True,  True,  False, True):  print("Case 3")
elif (x, y, z, t) == (True,  True,  False, False): print("Case 4")
elif (x, y, z, t) == (True,  False, True,  True):  print("Case 5")
elif (x, y, z, t) == (True,  False, True,  False): print("Case 6")
elif (x, y, z, t) == (True,  False, False, True):  print("Case 7")
elif (x, y, z, t) == (True,  False, False, False): print("Case 8")
elif (x, y, z, t) == (False, True,  True,  True):  print("Case 9")
elif (x, y, z, t) == (False, True,  True,  False): print("Case 10")
elif (x, y, z, t) == (False, True,  False, True):  print("Case 11")
elif (x, y, z, t) == (False, True,  False, False): print("Case 12")
elif (x, y, z, t) == (False, False, True,  True):  print("Case 13")
elif (x, y, z, t) == (False, False, True,  False): print("Case 14")
elif (x, y, z, t) == (False, False, False, True):  print("Case 15")
elif (x, y, z, t) == (False, False, False, False): print("Case 16")

这可以转换为 dict 查找或使用巧妙的二进制打包技巧,但这里的优点是 (a) 它简单易读; (b) 不需要 lambda 或函数;并且 (c) 您可以将任何东西放入 16 个箱子中。

您可以直接从布尔值的二进制操作中获取您的案例编号:

case = (x^1) << 3 | (y^1) << 2 | (z^1) << 1 | (t^1) + 1
print(f'Case {case}')

如果您查看 ,您会发现 x, y, z, t 只是您案例编号的 'bits'(其中 True=0False=1)。 . 所以我构建了一个 int 设置这些位(然后添加 1 因为你从 1 开始计数)。

如果编号是任意的,您可以将其简化为 x << 3 | y << 2 | z << 1 | t 并从那里开始。

这很容易扩展到更多的布尔变量。

然后根据案例编号做一些事情我建议你创建一个包含 case 作为键和函数或数据或任何值的字典。类似于:

case_functions = {1: func_1, 2: func_2, ...}
res = case_functions(case)(some argument)

只需使用 TrueFalse 值的二进制:

x = True
y = True
z = True
t = True
total = bin(x + 2 * y + 4 * z + 8 * t)
print(total)
print(int(total, 2))

输出:

0b1111

15

鉴于

x = False
y = True
z = False
t = True
total = bin(x + 2 * y + 4 * z + 8 * t)
print(total)
print(int(total, 2))

产量:

0b1010

10

现在您可以轻松地使用 int(total, 2) 值来确定您正在处理哪种情况

因此您可以将代码转换为单级缩进:

case = int(total, 2)
if case == 0:
    print('case 0')
elif case == 1:
    print('case 1')
elif case == 2:
    print('case 2')
...

您可以使用案例映射到结果:

cases = { (True,  True,  True,  True):  "Case 1",
      (True,  True,  True,  False): "Case 2",
      (True,  True,  False, True): "Case 3",
      (True,  True,  False, False):"Case 4",
      (True,  False, True,  True): "Case 5",
      (True,  False, True,  False):"Case 6",
      (True,  False, False, True): "Case 7",
      (True,  False, False, False):"Case 8",
      (False, True,  True,  True): "Case 9",
      (False, True,  True,  False):"Case 10",
      (False, True,  False, True): "Case 11",
      (False, True,  False, False):"Case 12",
      (False, False, True,  True): "Case 13",
      (False, False, True,  False):"Case 14",
      (False, False, False, True): "Case 15",
      (False, False, False, False):"Case 16"}

print(cases[(x,y,z,t])

如果您想为每种情况做一些事情else/different,您可以向该映射添加一个函数。

cases = { (True,  True,  True,  True):  foo_func,
      (True,  True,  True,  False): bar_func,
         ...}

result = cases[(x,y,x,t)](*args)

您也可以使用其中一种掩码解决方案来缩短代码,或者如果您有太多案例要写出来,但对于较小的案例集,这种显式表示将更清晰且更易于维护。

当有这么多情况时,我通常更喜欢编写使代码更易于维护的辅助函数,例如:

def compare(xyzt, binaryval):
    boolval = tuple(digit == '1' for digit in binaryval)
    return all(a == b for a, b in zip(xyzt, boolval))

那么你的if语句可以写成:

xyzt = (x, y, z, t)
if   compare(xyzt, '1111'): ...
elif compare(xyzt, '1110'): ...
elif compare(xyzt, '1100'): ...
etc.

这样可以更轻松地验证您是否考虑了所有情况。

我认为这是处理程序注册的好地方。这不会为您提供 最短的 代码,但我认为它为您提供了更易于阅读和更易于维护的代码,这是对 "simpler" 的一种解释。我会这样做:

registry.py

handlers = dict()
def register(x, y, z, t):
    if (x, y, z, t) in handlers:
        raise ValueError("Handler already registered for {}/{}/{}/{}".format(
                x, y, z, t))
    def insert(f):
        handlers[(x, y, z, t)] = f
    return insert

def handle(x, y, z, t):
    if (x, y, z, t) not in handlers:
        raise KeyError("No handler registered for {}/{}/{}/{}".format(
                x, y, z, t))
    return handlers[(x, y, z, t)]()

handlers.py

from delegation import register, handle

@register(x=True, y=True, z=False, t=True)
def some_descriptive_name():
    print("hi!")

@register(x=True, y=False, z=True, t=False)
def another_good_name():
    print("Yes hello.")

# etc.

main.py

from handlers import handle

x, y, z, t = True, False, False, True
handle(x, y, z, t)

这让您可以准确地看到每个处理程序将被激活的条件。将您的处理程序分离到它们自己的功能中也可以进行更清晰的测试。我添加了一项检查以确保您不会多次尝试处理相同的条件,如果未处理一组条件,我会添加一条错误消息。添加检查以确保所有 个案例也得到处理会很容易。

如果您的操作需要使用变量(除了四个条件),您也可以这样做;只需更改 handle 的签名和 return 值,如下所示:

def handle(x, y, z, t, *args, **kwargs):
    ...
    return handlers[(x, y, z, t)](*args, **kwargs)

当然,还要向处理程序添加参数。

这是一个灵活的解决方案,提供可扩展性和一定程度的简单性。

首先,您需要创建每个输出 运行 的方法。这些是您的 print("case X") 语句

的 "complicated" 版本
#Define your method outcomes here...
#Note that this follows a binary layout starting with 
# a + b + c + d = false
def action1():      #binary 0 (a'b'c'd')
    print("case 1")
def action2():      #binary 1 (a'b'c'd)
    print("case 2")
def action3():      #binary 2 (a'b'cd')
   print("case 3")
def action4():      #binary 3 (a'b'cd)
    print("case 4")
def action5():      #binary 4 (a'bc'd')
    print("case 5") #etc...
def action6():
    print("case 6")
def action7():
    print("case 7")
def action8():
    print("case 8")
def action9():
    print("case 9")
def action10():
    print("case 10")
def action11():
    print("case 11")
def action12():
    print("case 12")
def action13():
    print("case 13")
def action14():
    print("case 14")
def action15():
    print("case 15")
def action16():
    print("case 16")
def actionDefault():
    print("Error!")

然后,您可以通过创建一个方法名称列表,然后创建一个在调用时引用该方法列表的方法来轻松地引用这些特定的操作方法。

import itertools #Generates all permutations
import sys       #Allows us to get the current module

#Returns the index of the actionList we should execute
def evaluateActionIndex(varList): 
    allcombinations = itertools.product([False, True], repeat=len(varList))
    i = 0
    for subset in allcombinations: #for each of the possible combinations...
        if list(subset) == varList: #Check to see if we want to execute this index.
            return i
        i = i + 1                  #Increment the target index
    return -1                      #Execute default method (-1 index)

def performAction(index):
    actionList = [action1.__name__, action2.__name__, action3.__name__, action4.__name__, 
                  action5.__name__, action6.__name__, action7.__name__, action8.__name__,
                  action9.__name__, action10.__name__, action11.__name__, action12.__name__,
                  action13.__name__, action14.__name__, action15.__name__, action16.__name__,
                  actionDefault.__name__]
    method = getattr(sys.modules[__name__], actionList[index])  #Get method by name
    method()                                                    #Execute Method

我们可以使用以下方法执行一些操作:

#Mock up some control inputs
a = False
b = True
c = False
d = False
controlVariables = [a, b, c, d] #All Your Control Variables

#Execute control sequence
performAction(evaluateActionIndex(controlVariables))

我已经对此进行了测试,它可以有效地工作。您可以根据需要向 controlVariables 列表中添加任意数量的控制变量。

扩展@Reedinationer 的回答:

# your functions
def f0(): print('case 1')
def f1(): print('case 2')
def f2(): print('case 3')
#.
#.
def f15(): print('case 16')

list_of_functions = [f0, f1, f2] # assuming there are 16 functions in total

x = False
y = False
z = False
t = False
total = bin(x + 2 * y + 4 * z + 8 * t)
index = int(total, 2)

list_of_functions[index]() # will print('case 1')

在 python 2.7 和 3.7

上测试

太棒了。位!很干净。

我一直在寻找解决这个问题的方法。

这是一个 javascript 版本:

//assuming you have your variables in an array
let q = evaluatedQuery = ["wd:Q82955", "wd:Q212238", "", "wd:Q116"]

//lenght of the binary string
let possibleCases = evaluatedQuery.length
let binaryCase = ""


for (let i = 0; i < possibleCases; i++) {

    // this "!!" makes a value truthy or falsy,
    // and converts that to an integer "!!q[i] ^ 0"

    binaryCase = `${binaryCase}${!!q[i] ^ 0}`

}

//this finds out which of (q*q = 16) cases its gonna be
let parsedBinaryCase = parseInt(binaryCase, 2) + 1

//this converts it to an array for easy handling
let binaryCaseArr = binaryCase.split("")

//this filers out falsy values by taking falsy values index
let boundQueryElements = evaluatedQuery.filter((el, i) => {
    return !binaryCaseArr[i] != !!el ^ 0 
})

console.log(binaryCase) //output: 1101
console.log(parsedBinaryCase) //output: 14
console.log(boundQueryElements) //output: ['wd:Q82955','wd:Q212238','wd:Q116']

//and this is a clean way to handle those 16 cases
//in this example it would go to case 14
switch (parsedBinaryCase) {
    case 1:
        break
    case 2:
        break
    case 3:
        break
    case 4:
        break
    case 5:
        break
    case 6:
        break
    case 7:
        break
    case 8:
        break
    case 9:
        break
    case 10:
        break
    case 11:
        break
    case 12:
        break
    case 13:
        break
    case 14:
     // for (let el in boundQueryElements) {
     // }
        break
    case 15:
        break
    case 16:
        break
    default:
}

它就像 'flattens' 树结构。