The Column Transformer

An easy Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.

Domain
Python
Difficulty
easy
Seniority
L3
Asked in
HelloFresh

Problem

An ETL step ships its per-column transforms as configuration: transforms maps a column name to the name of an operation, while rows is a list of same-keyed dicts. An operation is one of 'upper' or 'lower' to change a string's case, 'increment' to add one to a number, or 'double' to multiply a number by two. Apply each column's operation to its value in every row and return the updated rows without modifying the input.

Each column gets its function.

Example 1

Input:

rows = [{"age":30,"name":"alice"},{"age":25,"name":"bob"}], transforms = {"name":"upper"}

Output:

[{"age":30,"name":"ALICE"},{"age":25,"name":"BOB"}]

Example 2

Input:

rows = [{"age":30,"name":"alice"}], transforms = {"age":"increment","name":"upper"}

Output:

[{"age":31,"name":"ALICE"}]

Worked solution and explanation

What you're really being asked

Strip the ETL costume and this is a config-driven dispatch problem: each column names an operation, so you look up what to do per column and run it. The real work is not the operations, it is rebuilding every row as a NEW dict. Walk the row's OWN items() so untransformed columns pass through and key order survives for free, then overwrite only the columns that name a transform. Get greedy and mutate the row in place and you silently rewrite the list the caller still holds; build the row off the transforms dict instead and you drop every pass-through column and scramble the order.


Break down the requirements

Step 1: Rebuild each row as a fresh dict

Process rows one at a time and start each with an empty new_row. Walking the current row's items() (not the transforms dict) is what keeps every column and its original order, and it means you never touch the input.

Step 2: Decide per column whether to transform

For each column, ask if col in transforms. If it names no operation, copy the value straight across. This one check is what makes untransformed columns pass through unchanged.

Step 3: Dispatch on the operation name

When a column does name an operation, dispatch on the name with if/elif: 'upper' and 'lower' change a string's case, 'increment' adds one, 'double' multiplies by two. Because you iterate the row's columns, a transform naming a column the row lacks is simply never reached, so a missing column can never raise.


The solution

Copy each row, dispatch per column
def apply_transforms(rows: list, transforms: dict) -> list:
    result = []
    for row in rows:
        # Copy by walking the row's own items so order and pass-through columns are free.
        new_row = {}
        for col, value in row.items():
            if col in transforms:
                op = transforms[col]
                if op == "upper":
                    new_row[col] = value.upper()
                elif op == "lower":
                    new_row[col] = value.lower()
                elif op == "increment":
                    new_row[col] = value + 1
                elif op == "double":
                    new_row[col] = value * 2
                else:
                    new_row[col] = value
            else:
                new_row[col] = value
        result.append(new_row)
    return result

How would this grow on the job?

Common follow-up questions