0

Background:

I am attempting to determine the difference between technical drawing dimensions and actual measured dimensions. These dimensions are stored within two dictionaries: actual_data and drawing_data. Due to the fact that the actual measurements can be taken in many ways, they are stored as lists to accommodate the following cases:

  1. 1x1 list. This represents a singular dimension with no variation or tolerance specified.
  2. 1x2 list. This represents a dimension that has an upper and lower limit or tolerance.
  3. Nx1 list. This represents a list of singular dimensions taken from one surface of a large component.
  4. Nx2 list. This represents a list of dimensions that each have an upper and lower limit or tolerance.

To avoid too many conditionals, I break up all matrices into lists and assign each new list (either 1x1 or 1x2) a new key.

My Script:

import numpy as np

drawing_data = {
        'A': [394.60],
        'B': [629.85, 629.92],
        'C': [759.95, 760.00],
        'D': [839.95, 840.00],
        'E': [1779.50, 1780.50]
        }

actual_data = {
        'A': [390.00],
        'B': [629.88, 629.90],
        'C': [760.17, 760.25],
        'D': [[840.12, 840.18], [840.04, 840.06], [840.07, 840.07]],
        'E': [1780.00, 1780.00]
        }

As you can see, Dimension D has an expected measurement ranging between 839.95mm and 840.00mm. However, due to the size of the component, it is measured in three places (hence, the list of lists).

def dimensional_deviation(drawing, actual):
    
    # If the actual data is of the type Nx1 or Nx2, it is split and converted into new dimensions of the
    # type 1x1 or 1x2 - new dictionary keys are created to accommodate the new dimensions, and the 
    # original dictionary entries are deleted.
    #---------------------------------------------------------------------------------------------------
    new_dict_drawing, new_dict_actual, keys_to_remove = {}, {}, []

    for dimension in drawing:
        
        if type(actual.get(dimension)[0]) is list:

            keys_to_remove.append(dimension) # Create a list of unnecessery dimensions
            
            for i, sublist in enumerate(actual.get(dimension)):
                
                new_dict_drawing[f'{dimension}{i + 1}'] = drawing.get(dimension) # Create new dimension
                new_dict_actual[f'{dimension}{i + 1}'] = sublist # Create new dimension
                                
    for dimension in keys_to_remove: # Remove all unnecessary dimensions from the original dicts
        
        drawing.pop(dimension, None)
        actual.pop(dimension, None)

    # Merge dictionaries:    
    drawing = {**drawing, **new_dict_drawing}
    actual = {**actual, **new_dict_actual}
    #---------------------------------------------------------------------------------------------------
    
    # Determine the deviation between the drawing and actual dimensions. The average of the upper and 
    # lower bounds is used to simplify each case.
    #---------------------------------------------------------------------------------------------------
    inspection_results = {}
    
    for dimension in drawing:
        
        drawing_value, actual_value = drawing.get(dimension), actual.get(dimension)
        
        drawing_ave, actual_ave = np.mean(drawing_value), np.mean(actual_value)

        deviation = drawing_ave - actual_ave
            
    # Create new dictionary of the repair requirements:
    #---------------------------------------------------------------------------------------------------
        inspection_results[f'{dimension}'] = round(deviation, 3)
    
    return inspection_results

My Problem:

When I call the above for the first time, I get the desired output. Dimension D is broken up as expected and the deviation is calculated. However, when I call the same function a second time, everything regarding Dimension D is completely neglected as though the key did not exist:

print('First function call:')
print(dimensional_deviation(drawing=drawing_data, actual=actual_data))
print('---------------------------------------------------------------------------------------')

print('Second function call:')
print(dimensional_deviation(drawing=drawing_data, actual=actual_data))
print('---------------------------------------------------------------------------------------')

Resulting in:

First function call:
{'A': 4.6, 'B': -0.005, 'C': -0.235, 'E': 0.0, 'D1': -0.175, 'D2': -0.075, 'D3': -0.095}
--------------------------------------------------------------------------------------- 
Second function call:
{'A': 4.6, 'B': -0.005, 'C': -0.235, 'E': 0.0}
--------------------------------------------------------------------------------------- 

I believe I am overwriting my drawing_data and actual_data somewhere, but I cannot find the issue. Additionally, this is one of my first times using dictionaries and I suspect that my key creation and deletion may not be best practice.

In my comments you will see Create a list of unnecessary dimensions - an example of this would be Dimension D, as it is newly accounted for in D1, D2 and D3.

Could somebody please explain to me why I get this result on each subsequent function call after the first?

ChaddRobertson
  • 605
  • 3
  • 11
  • 30
  • The `pop()` method modifies the dictionary that was passed in; if you print `drawing_data` and `actual_data` after the call, you'll see the dimension is missing – Jiří Baum Aug 11 '21 at 09:58
  • I get the same behaviour when using ```del dict[key]``` - is this to be expected? – ChaddRobertson Aug 11 '21 at 10:00
  • @sabik Why would this have an impact on a completely separate function call if I'm passing the same initial parameters? – ChaddRobertson Aug 11 '21 at 10:02
  • The function modifies the original variable – Jiří Baum Aug 11 '21 at 10:05
  • The dictionaries aren't copied when they're passed to the function, you are operating on the actual original dictionaries. If the first function modifies them then you'll be passing the modified versions into the second function. – Kemp Aug 11 '21 at 10:05
  • @Kemp Understood. So creating a temporary dictionary would result in the desired output? Just to make sure I'm not going crazy, this is different to that of lists and arrays, correct? – ChaddRobertson Aug 11 '21 at 10:08
  • Passing a list in to a function will have the same result - modifying the list in the function will modify the original. – Kemp Aug 11 '21 at 10:09
  • 1
    If you don't want to modify the original you could either do a deep copy of the dictionary (via `copy.deepcopy` or similar) and pass that in or build a new dictionary in the function. – Kemp Aug 11 '21 at 10:09
  • @Kemp That solved the issue - thank you very much. Learn something new every day. Will add an answer. – ChaddRobertson Aug 11 '21 at 10:22

1 Answers1

0

The issue was that the original dictionaries were being modified (see above comments):

import numpy as np

drawing_data = {
        'A': [394.60],
        'B': [629.85, 629.92],
        'C': [759.95, 760.00],
        'D': [839.95, 840.00],
        'E': [1779.50, 1780.50]
        }

actual_data = {
        'A': [390.00],
        'B': [629.88, 629.90],
        'C': [760.17, 760.25],
        'D': [[840.12, 840.18], [840.04, 840.06], [840.07, 840.07]],
        'E': [1780.00, 1780.00]
        }
#-------------------------------------------------------------------------------------------------------

# The 'dimensional deviation' function takes the drawing data and actual data as arguments, returning a
# dictionary of dimensions and whether they require rectification or not (based one the drawing data).
#-------------------------------------------------------------------------------------------------------
def dimensional_deviation(drawing, actual):

    temp_dict_drawing = {}
    for key in drawing:
        temp_dict_drawing[key] = drawing.get(key)

    temp_dict_actual = {}
    for key in drawing:
        temp_dict_actual[key] = actual.get(key)
    
    # If the actual data is of the type Nx1 or Nx2, it is split and converted into new dimensions of the
    # type 1x1 or 1x2 - new dictionary keys are created to accommodate the new dimensions, and the 
    # original dictionary entries are deleted.
    #---------------------------------------------------------------------------------------------------
    new_dict_drawing, new_dict_actual, keys_to_remove = {}, {}, []

    for dimension in temp_dict_drawing:
        
        if type(temp_dict_actual.get(dimension)[0]) is list:

            keys_to_remove.append(dimension) # Create a list of unnecessery dimensions
            
            for i, sublist in enumerate(temp_dict_actual.get(dimension)):
                
                new_dict_drawing[f'{dimension}{i + 1}'] = temp_dict_drawing.get(dimension) # Create new dimension
                new_dict_actual[f'{dimension}{i + 1}'] = sublist # Create new dimension
                                
    for dimension in keys_to_remove: # Remove all unnecessary dimensions from the original dicts
        
        temp_dict_drawing.pop(dimension)
        temp_dict_actual.pop(dimension)

    # Merge dictionaries:    
    drawing_new = {**temp_dict_drawing, **new_dict_drawing}
    actual_new = {**temp_dict_actual, **new_dict_actual}
    #---------------------------------------------------------------------------------------------------
    
    # Determine the deviation between the drawing and actual dimensions. The average of the upper and 
    # lower bounds is used to simplify each case.
    #---------------------------------------------------------------------------------------------------
    inspection_results = {}
    
    for dimension in drawing_new:
        
        drawing_value, actual_value = drawing_new.get(dimension), actual_new.get(dimension) # Fetch the data

        drawing_ave, actual_ave = np.mean(drawing_value), np.mean(actual_value) # Calculate averages

        deviation = drawing_ave - actual_ave
            
    # Create new dictionary of the repair requirements:
    #---------------------------------------------------------------------------------------------------
        inspection_results[f'{dimension}'] = round(deviation, 3)
    
    return inspection_results

Alternatively, copy.deepcopy() also yielded the correct results:

Deep copy of a dict in python

ChaddRobertson
  • 605
  • 3
  • 11
  • 30
  • 1
    I don't believe it will matter here, but you should be aware that both dictionaries will have references to the same underlying lists, meaning that you can modify your new dictionary freely, but modifications to the lists would be reflected in both dictionaries. Essentially you've done the same as `copy.copy` would do. A deepcopy would also make copies of the lists. – Kemp Aug 11 '21 at 11:03
  • So if I'm understanding you correctly, with my implementation, a modification to ```temp_dict_actual.get('A')[0]``` would also result in the modification of ```actual.get('A')[0]```? – ChaddRobertson Aug 11 '21 at 11:08
  • 1
    Yep. It's hard to format code in comments, but `a = {4: ['x', 'y']}; b = {4: a[4]}; b[4].append('z'); print(a[4])` would give output `['x', 'y', 'z']`. – Kemp Aug 11 '21 at 13:30