Join tables to map userId to name
Company: Karat
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates a candidate's skills in relational data joining, data integration, and numeric reconciliation for mapping userId to customerName across multiple tables.
Constraints
- 1 <= len(customers), len(orders), len(products) <= 100000
- quantity is an integer string that fits in 32-bit signed range
- unitPrice and orderTotal are valid numeric strings (integer or decimal)
- orders.email may be None or ""
- If multiple customers match, choose the lexicographically smallest customerName
- Inputs are well-formed so that each order can be matched by email or by total
Examples
Input: ([['Alice','a@x.com','Book','2'],['Bob','b@x.com','Pen','5']], [['u1','a@x.com','20'],['u2',None,'10']], [['Book','10'],['Pen','2']])
Expected Output: {'u1': 'Alice', 'u2': 'Bob'}
Explanation: u1 matches by email -> Alice. u2 has no email, total 10 matches Bob because 5*2=10.
Input: ([['Amy','amy@mail','Widget','1'],['Zoe','zoe@mail','Widget','1'],['Bob','bob@mail','Gadget','2']], [['1',None,'9.99'],['2','','9.99'],['3',None,'9.990'],['4','bob@mail','10']], [['Widget','9.99'],['Gadget','5']])
Expected Output: {'1': 'Amy', '2': 'Amy', '3': 'Amy', '4': 'Bob'}
Explanation: Orders 1-3 match by total 9.99; both Amy and Zoe match, pick lexicographically smallest -> Amy. Order 4 matches by email -> Bob.
Input: ([['Carl','c@mail','Service','3']], [['X',None,'59.85']], [['Service','19.95']])
Expected Output: {'X': 'Carl'}
Explanation: Email missing, match by total: 3*19.95 = 59.85 -> Carl.
Input: ([['Dana','d@mail','Item','1']], [['u','unknown@mail','7']], [['Item','7']])
Expected Output: {'u': 'Dana'}
Explanation: Email is present but doesn't match any customer, so fallback to total: 1*7=7 -> Dana.
Input: ([['Ann','dup@mail','A','1'],['Ana','dup@mail','B','2']], [['id','dup@mail','3']], [['A','3'],['B','1.5']])
Expected Output: {'id': 'Ana'}
Explanation: Two customers share the same email; by email rule, choose lexicographically smallest name among matches: 'Ana' < 'Ann'.
Solution
def solution(customers, orders, products):
"""Return mapping {userId: customerName} by joining customers/orders/products.
Tie-breaker: if multiple customers match (by email or by total), choose
lexicographically smallest customerName.
"""
from decimal import Decimal
# productName -> unitPrice
price_by_product = {}
for row in products:
product, unit_price = row
price_by_product[product] = Decimal(unit_price)
# email -> lexicographically smallest customerName with that email
name_by_email = {}
# total (Decimal) -> lexicographically smallest customerName with that computed total
name_by_total = {}
for row in customers:
customer_name, email, product_name, qty_s = row
qty = Decimal(qty_s)
unit_price = price_by_product[product_name]
total = qty * unit_price
# email map
if email is not None and email != "":
prev = name_by_email.get(email)
if prev is None or customer_name < prev:
name_by_email[email] = customer_name
# total map
prev_t = name_by_total.get(total)
if prev_t is None or customer_name < prev_t:
name_by_total[total] = customer_name
result = {}
for row in orders:
user_id, email, order_total_s = row
chosen = None
if email is not None and email != "":
chosen = name_by_email.get(email)
if chosen is None:
order_total = Decimal(order_total_s)
chosen = name_by_total[order_total]
result[user_id] = chosen
return result
Time complexity: O(C + P + O) where C=len(customers), P=len(products), O=len(orders). Space complexity: O(C + P) for lookup maps.