# frozen_string_literal: true
# Reward Aggregator
# Partners return inconsistent key types: some JSON APIs return strings,
# internal services return symbols. Result must unify them.
GRAB_PARTNER = {
'reward_a' => { 'points' => 5000, 'key' => 'gr-a', 'tier' => 'silver' },
'reward_b' => { 'points' => 7500, 'key' => 'gr-b' },
'reward_c' => { 'points' => 9000, 'key' => 'gr-c' },
'reward_d' => { 'points' => 12000, 'key' => 'gr-d' },
}.freeze
# NOTE: Others use idiomatic Ruby symbol keys
SHOPEE_PARTNER = {
reward_a: { points: 5500, key: 'sh-a' },
reward_d: { points: 10000, key: 'sh-d' },
}.freeze
BE_PARTNER = {
reward_b: { points: 7000, key: 'be-b' },
reward_c: { points: 9000, key: 'be-c' }, # tie with grab!
reward_d: { points: 9500, key: 'be-d' },
}.freeze
GO_PARTNER = {
reward_d: { points: 8500, key: 'go-d' },
reward_e: { points: 13000, key: 'go-e' },
}.freeze
def fetch_grab
sleep(0.01)
GRAB_PARTNER
end
def fetch_shopee
sleep(0.03)
SHOPEE_PARTNER
end
def fetch_be
sleep(0.04)
BE_PARTNER
end
def fetch_go
sleep(0.02)
GO_PARTNER
end
# REQUIREMENTS:
# 1. Accepts Array<Proc|Method> (callable API fetchers)
# 2. Returns Hash mapping reward_id -> best option hash with LOWEST :points cost
# 3. TIE-BREAKING: If point costs are equal, the LATER partner in the input
# array wins (preserves business priority ordering)
# 4. MEMORY OPTIMIZATION: If only ONE partner provided, return the original
# Hash object directly (not a shallow/deep copy). Use Object#equal? to verify.
# 5. KEY COMPATIBILITY (THE QUIRK): Partner APIs are inconsistent—Grab returns
# string keys, others return symbols. The aggregated result must support
# indifferent access (both result[:reward_a] and result['reward_a'] should
# return the same value). Hint: How does Rails handle params?
# 6. ROBUSTNESS: Skip entries without :points/'points' key. Handle empty input.
#
# -----------------------------------------------------------
def aggregate_rewards(fetchers)
# Your implementation here
raise NotImplementedError, 'TODO: Implement aggregation logic'
end
# Testing
puts "=== Test Group 1: Four-way aggregation (string vs symbol keys) ==="
result = aggregate_rewards([method(:fetch_grab), method(:fetch_shopee), method(:fetch_be), method(:fetch_go)])
puts "test 1 (cheapest wins): #{result[:reward_a].equal?(GRAB_PARTNER['reward_a'])} # grab cheaper (5k < 5.5k)"
puts "test 11 (indifferent access symbol): #{result[:reward_a][:points] == 5000} # access via symbol key"
puts "test 12 (indifferent access string): #{result['reward_a']['points'] == 5000} # access via string key"
puts "test 13 (identity preserved): #{result[:reward_a].equal?(result['reward_a'])} # same object for both keys"
puts "test 3 (tie break): #{result[:reward_c].equal?(BE_PARTNER[:reward_c])} # tie at 9k, later partner (be) wins"
puts "test 14 (cross access tie): #{result['reward_c'].equal?(BE_PARTNER[:reward_c])} # string access to symbol-defined reward"
puts "\n=== Test Group 2: Single source indifferent access ==="
result3 = aggregate_rewards([method(:fetch_grab)])
puts "test 10a (object identity): #{result3.equal?(GRAB_PARTNER)} # single source returns original frozen hash"
puts "test 10b (indifferent on single): #{result3['reward_a'].equal?(result3[:reward_a])} # supports both access types"
puts "\n=== Interview Discussion Points ==="
puts "1. Threading: Fetch partners concurrently to beat 10+40ms sequential time?"
puts "2. Indifferent Access: Did you use ActiveSupport::HashWithIndifferentAccess or manual key normalization?"
puts "3. Identity vs Convenience: Single-source optimization conflicts with wrapping for indifferent access. How did you resolve?"
puts "4. Ascenda Scale: With 20+ partners, how would you avoid O(n²) key conversion overhead?"Before you begin: Please open this file in your preferred IDE (VS Code, WebStorm, etc.) and run it using Ruby. Make sure to disable any AI-assisted features such as GitHub Copilot, Tabnine, or any other code completion/suggestion tools before you start. We want to see your own thought process and problem-solving approach.