Is there a better way to write nested if statements in python?

Question:

Is there a more pythonic way to do nested if else statements than this one:

def convert_what(numeral_sys_1, numeral_sys_2):

    if numeral_sys_1 == numeral_sys_2:      
        return 0
    elif numeral_sys_1 == "Hexadecimal":
        if numeral_sys_2 == "Decimal":
            return 1
        elif numeral_sys_2 == "Binary":
            return 2
    elif numeral_sys_1 == "Decimal":
        if numeral_sys_2 == "Hexadecimal":
            return 4
        elif numeral_sys_2 == "Binary":
            return 6
    elif numeral_sys_1 == "Binary":
        if numeral_sys_2 == "Hexadecimal":
            return 5
        elif numeral_sys_2 == "Decimal":
            return 3
    else:
        return 0

This script is a part of a simple converter.

Asked By: Module_art

||

Answers:

Insert all the valid combinations to a dictionary of tuples, and if the combination is not there, return 0:

def convert_what(numeral_sys_1, numeral_sys_2):
    numeral_dict = {
        ("Hexadecimal", "Decimal"    ) : 1,
        ("Hexadecimal", "Binary"     ) : 2,
        ("Decimal",     "Hexadecimal") : 4, 
        ("Decimal",     "Binary"     ) : 6,
        ("Binary",      "Hexadecimal") : 5,
        ("Binary",      "Decimal"    ) : 3
    }
    return numeral_dict.get((numeral_sys_1, numeral_sys_2), 0)

If you are planning to use the function in a loop, it may be a better idea to define the dictionary outside the function, so it wouldn’t be recreated on every call to the function.

Answered By: Aryerez

An Idea is using a list and get index of result, ie.

def convert_what(numeral_sys_1, numeral_sys_2):
    if numeral_sys_1 == numeral_sys_2:      
        return 0
    return ["HexadecimalDecimal", "HexadecimalBinary", "BinaryDecimal", "DecimalHexadecimal", "BinaryHexadecimal", "DecimalBinary" ].index(numeral_sys_1 + numeral_sys_2) + 1
Answered By: none

If you are sure there is no other value could have been set to numeral_sys_1 and numeral_sys_2 variables this is the simplest and cleanest solution.

On the other hand, you have to extend the dictionary with its combinations with available values, if you have any other value than “Hexadecimal”, “Decimal” and “Binary”

The logic here is; if variable tuples in dictionary keys are not equal to given variable tuple, .get() method returns “0”. If given variable tuple match any key in dictionary thus return value of the matching key.

def convert_what(numeral_sys_1, numeral_sys_2):
    return {
        ("Hexadecimal", "Decimal") : 1, 
        ("Hexadecimal", "Binary") : 2, 
        ("Binary", "Decimal") : 3,
        ("Decimal", "Hexadecimal") : 4,
        ("Binary", "Hexadecimal") : 5, 
        ("Decimal", "Binary") : 6, 
     }.get((numeral_sys_1, numeral_sys_2), 0)

There is also using generator could be a solution. Looks much smarter, but I think hard codded dictionary would faster than using a generator for this simple requirement.

Answered By: Sencer H.

While @Aryerez and @SencerH.’s answers work, each possible value of numeral_sys_1 has to be repeatedly written for each possible value of numeral_sys_2 when listing the value pairs, making the data structure harder to maintain when the number of possible values increases. You can instead use a nested dict in place of your nested if statements instead:

mapping = {
    'Hexadecimal': {'Decimal': 1, 'Binary': 2},
    'Binary': {'Decimal': 3, 'Hexadecimal': 5},
    'Decimal': {'Hexadecimal': 4, 'Binary': 6}
}
def convert_what(numeral_sys_1, numeral_sys_2):
    return mapping.get(numeral_sys_1, {}).get(numeral_sys_2, 0)

Alternatively, you can generate the pairs of values for the mapping with the itertools.permutations method, the order of which follows that of the input sequence:

mapping = dict(zip(permutations(('Hexadecimal', 'Decimal', 'Binary'), r=2), (1, 2, 4, 6, 3, 5)))
def convert_what(numeral_sys_1, numeral_sys_2):
    return mapping.get((numeral_sys_1, numeral_sys_2), 0)
Answered By: blhsing

In general I would run with dictionary solution for nested if task. Some particular cases may bring to another approach. Like this one:

def convert_what(numeral_sys_1, numeral_sys_2):

    num = ['Hexadecimal','Decimal','Binary']
    tbl = [[0,1,2],
           [4,0,6],
           [5,3,0]]
    try:
        return tbl[num.index(numeral_sys_1)][num.index(numeral_sys_2)]
    except ValueError:
        return 0
Answered By: Anatoliy R

How about something like:

def convert_what(numeral_sys_1, numeral_sys_2):
    src = numeral_sys_1, numeral_sys_2
    if src == "Hexadecimal", "Decimal":
        return 1
    if src == "Hexadecimal", "Binary"
        return 2
    # You get the gist.... 
    if src == "Decimal", "Binary":
        return 6
    return 0 
Answered By: AnonymousAlex

In my opinion, this convert_what function itself is not very pythonic. I guess the code which calls this code has a bunch of if statements too and does the converting depending on the return value of convert_what(). I suggest something like this:

First step, make one function for every combination:

def hex_dec(inp):
    return 1234  # todo implement
# do the same for hex_bin, bin_dec, dec_hex, bin_hex, dec_bin

Second step, put the function objects in a dict. Note that there are no () after the function names, because we want to store the function object and not call it yet:

converter_funcs = {
    ("Hexadecimal", "Decimal"): hex_dec,
    ("Hexadecimal", "Binary"): hex_bin,
    ("Binary", "Decimal"): bin_dec,
    ("Decimal", "Hexadecimal"): dec_hex,
    ("Binary", "Hexadecimal"): bin_hex,
    ("Decimal", "Binary"): dec_bin,
}

Third and last step, implement a convert function. The if statement checks if both systems are the same. Then, we get the right function from our dict and call it:

def convert(input_number, from_sys, to_sys):
    if from_sys == to_sys:
        return input_number
    func = converter_funcs[(from_sys, to_sys)]
    return func(input_number)
Answered By: bb1950328

This is done by switch-case statements in most of other languages. In python, I use a simple function with an expression dictionary.

Code:

def convert_what(numeral_sys_1, numeral_sys_2):
    myExpressions = {"Hexadecimal" : {"Decimal" : 1, "Binary" : 2},
                    "Decimal" : {"Hexadecimal" : 4, "Binary" : 6}, 
                    "Binary" : {"Hexadecimal" : 5, "Decimal" : 3}}
    return (myExpressions.get(numeral_sys_1, {})).get(numeral_sys_2, 0)

Output:

> convert_what("Hexadecimal", "Decimal")
> 1
> convert_what("Binary", "Binary")
> 0
> convert_what("Invalid", "Hexadecimal")
> 0
Answered By: furkanayd

An alternative way using nested list. Hope it helps!!

def convert_what(numeral_sys_1, numeral_sys_2):

    l1 = [["Hexadecimal","Decimal"],["Hexadecimal","Binary"],
            ["Decimal","Hexadecimal"],["Decimal","Binary"],
            ["Binary","Hexadecimal"],["Binary","Decimal"]]

    return l1.index([numeral_sys_1, numeral_sys_2]) + 1 if [numeral_sys_1,numeral_sys_2] in l1 else 0
Answered By: Sapan Zaveri

I like to keep code dry:

def convert_what_2(numeral_sys_1, numeral_sys_2):
    num_sys = ["Hexadecimal", "Decimal", "Binary"]
    r_value = {0: {1: 1, 2: 2},
               1: {0: 4, 2: 6},
               2: {0: 5, 1: 3} }
    try:
        value = r_value[num_sys.index(numeral_sys_1)][num_sys.index(numeral_sys_2)]
    except KeyError: # Catches when they are equal or undefined
        value = 0
    return value
Answered By: Mikeologist

Using some techniques that the other answers provide and combine them:

def convert(key1, key2):
    keys = ["Hexadecimal", "Decimal", "Binary"]
    combinations = {(0, 1): 1, (0, 2): 2, (1, 0): 4, (1, 2): 6, (2, 0): 5, (2, 1): 3} # use keys indexes to map to combinations
    try:
        return combinations[(keys.index(key1), keys.index(key2))]
    except (KeyError, ValueError): # if value is not in list, return as 0
        return 0
Answered By: Michael Yang

Although not sure if this approach is faster, but could be done using numpy as well:

conditions = [
    ("Hexadecimal", "Decimal"), ("Hexadecimal", "Binary"),
    ("Binary", "Decimal"), ("Decimal", "Hexadecimal"), ("Binary", "Hexadecimal"), ("Decimal", "Binary")]
choices = [1,2,3,4,5,6]

and can be used as :

 np.select(conditions, choices, default=0)
Answered By: vp7

As @Sadap said,

In my opinion, this convert_what function itself is not very pythonic. I guess the code which calls this code has a bunch of if statements too and does the converting depending on the return value of convert_what(). I suggest something like this:

If you’re implementing base conversion for integers, you’re probably going through a common representation anyway: int. A separate function for each pair of bases isn’t necessary, and the two bases involved don’t even need to know about each other.

Input

Create a mapping from the name of a number system to its base:

BINARY = "Binary"
DECIMAL = "Decimal"
HEXADECIMAL = "Hexadecimal"

BASES = {
    BINARY: 2,
    DECIMAL: 10,
    HEXADECIMAL: 16,
}

allowing you to read inputs with int(text, BASES[numeral_sys_1]).

Output

Create a mapping from the name of a number system to a format specifier:

FORMATTERS = {
    BINARY: "b",
    DECIMAL: "d",
    HEXADECIMAL: "x",
}

allowing you to write outputs with format(n, FORMATTERS[numeral_sys_2]).

Example use

def convert(text, numeral_sys_1, numeral_sys_2):
    n = int(text, BASES[numeral_sys_1])
    return format(n, FORMATTERS[numeral_sys_2])

Either dict can also be made more general by making the values functions instead, if you need to support a different set of formats than int(x, base), or more output bases than built-in integer formatting supports.

Answered By: Ry-
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.