1

I have the following dictionaries:

input_processed = {'units' : {'g' : 'g',
                              'dx' : 'cm',
                              'po' : 'bar',
                              'muo' : 'mPas'},
                   'g' : 1.0,
                   'dx' : [10.0, 20.0, 10.0],
                   'muo' : {'po' : [0.0, 1.0],
                            'muo' : [32.7, 32.7]},
                   'phi' : 0.05}

commands_variables = {'units' : [],
                      'g' : ['g'],
                      'dx' : ['dx'],
                      'muo' : ['po', 'muo'],
                      'phi' : ['phi']}

conversion_factors = {'g' : 9.81,
                      'cm' : 0.01,
                      'bar' : 10**5,
                      'mPas' : 10**-3}

My aim is to multiply each float in input_processed by a factor defined in conversion_factors for some variables. Since a command (highest level keys in input_processed can define multiple variables, these are mapped in commands_variables.

So first I need to check, if the variable is contained in input_processed['units'], since phi has no unit. Then for each variable in every command I need to get the unit. In the next step I get the conversion factor depending on the unit from conversion_factors. Since the values of the highest level keys in input_processed can be of the type float, list or dict, I wrote a different method for each case. This is put into a function and called in a loop over input_processed.

def convert_unit(data, command, parameter):
    for it in commands_variables[command]:
        if it in data['units']:
            variable = it
        else:
            continue

        unit = data['units'][variable]
        conversion_factor = conversion_factors[unit]

        if type(parameter) is float:
            converted = parameter*conversion_factor
        elif type(parameter) is list:
            converted = [parameter[it]*conversion_factor
                         for it in range(len(parameter))]
        elif type(parameter) is OrderedDict:
            for key, value in parameter.items():
                value = parameter[key]
                converted = [value[it]*conversion_factor
                             for it in range(len(value))]

        return converted

input_converted = {}
for key, value in input_processed.items():
    input_converted[key] = convert_unit(input_processed, key, value)

The desired output is for the function

9.81
[0.1, 0.2, 0.1]
{'po' : [0.0, 100000.0], 'muo' : [0.0327, 0.0327]}
0.05

and for the main program

input_processed = {'units' : {'g' : 'g',
                              'dx' : 'cm',
                              'po' : 'bar',
                              'muo' : 'mPas'},
                   'g' : 9.81,
                   'dx' : [0.1, 0.2, 0.1],
                   'muo' : {'po' : [0.0, 100000.0],
                            'muo' : [0.0327, 0.0327]},
                   'phi' : 0.05}

I managed to convert floats and lists but no dictionaries. In this shortened problem statement, I had the error that the local variable 'converted' is referenced before the assignment.

If somebody has an easier way to convert the quantities, I would appreciate that.

numströ
  • 13
  • 3
  • it would be much easier if your input data had an easier structure. the problem with the 'referenced before assignment' error can be solved by using `dict` instead of `OrderedDict` in your code. – Felix Mar 14 '16 at 03:07

1 Answers1

0

The main issue with your existing code is that your

for key, value in parameter.items():

loop doesn't accumulated the changed items in the nested dictionary, so the converted produced by that part of the code will only be the converted last item in parameter; also that section only handles nested dictionary items that are lists, it will fail on plain floats.

FWIW, your return converted at the bottom of the main for loop looks a bit suspicious: an unconditional return inside a loop will cause the function to return at the end of the first loop iteration. That's actually ok here, since we do want to exit the loop after we get a successful match it in data['units'], but it still makes it a bit tricky for someone reading your code. :)

A minor issue is the use of type for type testing. It's recommended to use isinstance instead. See Differences between isinstance() and type() in python for details. As the answers there mention it's best to avoid type checking in Python as much as possible, since that interferes with duck typing.

Anyway, here's a modified version of your function. My code was tested on Python 2.6.6, but it should perform correctly on Python 3... I think. :)

from pprint import pprint

conversion_factors = {
    'g' : 9.81,
    'cm' : 0.01,
    'bar' : 10**5,
    'mPas' : 10**-3
}

input_processed = {
    'units' : {
        'g' : 'g',
        'dx' : 'cm',
        'po' : 'bar',
        'muo' : 'mPas'
    },
    'g' : 1.0,
    'dx' : [10.0, 20.0, 10.0],
    'muo' : {
        'po' : [0.0, 1.0],
        'muo' : [32.7, 32.7]
    },
    'phi' : 0.05
}

def convert_dict(indata):
    units = indata['units']

    def convert(key, item):
        factor = conversion_factors.get(units.get(key))
        if factor is not None:
            if isinstance(item, list):
                item = [u * factor for u in item]
            else:
                item *= factor
        return item

    outdata = {'units': units.copy()}
    for k, v in indata.items():
        if k == 'units':
            continue
        if isinstance(v, dict):
            outdata[k] = newd = {}
            for k1, v1 in v.items():
                newd[k1] = convert(k1, v1)
        else:
            outdata[k] = convert(k, v)

    return outdata

input_converted = convert_dict(input_processed)
pprint(input_converted)

output

{'dx': [0.10000000000000001, 0.20000000000000001, 0.10000000000000001],
 'g': 9.8100000000000005,
 'muo': {'muo': [0.032700000000000007, 0.032700000000000007],
         'po': [0.0, 100000.0]},
 'phi': 0.050000000000000003,
 'units': {'dx': 'cm', 'g': 'g', 'muo': 'mPas', 'po': 'bar'}}

FWIW, although pprint.pprint is ok for displaying dictionaries, you can get prettier output using json.dumps.

Community
  • 1
  • 1
PM 2Ring
  • 54,345
  • 6
  • 82
  • 182