Implement lazy array and KV store
Company: Databricks
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates understanding of lazy evaluation and functional transformation chaining for a LazyArray alongside in-memory data structure design and stateful API behavior for a key-value store with hit counting, within the Coding & Algorithms domain covering functional programming, data structures, and API design.
Part 1: Lazy Array Query Processor
Constraints
- 0 <= len(nums) <= 2000
- 0 <= len(operations) <= 2000
- All operation names are valid and use the formats described above
- Array values and operation parameters fit in 32-bit signed integers
- `get` indices are integers; if the requested index is out of range, return `None`
Examples
Input: ([1, 2, 3, 4], [('map_mul', 2), ('filter_gt', 4), ('to_list',)])
Expected Output: [[6, 8]]
Explanation: The pipeline is `*2` then `>4`, so the final list is `[6, 8]`.
Input: ([1, 2, 3, 4, 5], [('filter_odd',), ('get', 1), ('map_add', 10), ('reduce_sum',), ('to_list',)])
Expected Output: [3, 39, [11, 13, 15]]
Explanation: After `filter_odd`, the lazy array is `[1, 3, 5]`, so `get(1)` is `3`. Then `map_add 10` makes it `[11, 13, 15]`, whose sum is `39`.
Input: ([], [('map_add', 5), ('to_list',), ('reduce_sum',), ('get', 0)])
Expected Output: [[], 0, None]
Explanation: Edge case: an empty input stays empty under all transformations.
Input: ([2, 4, 6], [('filter_odd',), ('get', 0), ('to_list',)])
Expected Output: [None, []]
Explanation: Filtering odd numbers removes every element, so index 0 does not exist.
Hints
- Do not update the array immediately for `map` and `filter`. Store the operations in a list first.
- When a terminal operation arrives, build a generator/iterator pipeline from the original array. For `get(i)`, stop as soon as you find the i-th surviving element.
Part 2: In-Memory Key-Value Store with Hit Counts
Constraints
- 0 <= len(operations) <= 100000
- Keys are strings of length 1 to 50
- Values fit in 32-bit signed integers
- All operations are valid and are one of `put`, `get`, `delete`, or `getHitCount`
- Average-time efficiency is expected for each basic operation
Examples
Input: ([('put', 'a', 10), ('get', 'a'), ('get', 'a'), ('getHitCount', 'a')],)
Expected Output: [10, 10, 2]
Explanation: Two successful `get` operations increment the hit count from 0 to 2.
Input: ([('get', 'x'), ('delete', 'x'), ('getHitCount', 'x')],)
Expected Output: [None, False, None]
Explanation: Edge case: missing keys return `None` for `get` and `getHitCount`, and `False` for `delete`.
Input: ([('put', 'a', 1), ('get', 'a'), ('put', 'a', 99), ('getHitCount', 'a'), ('get', 'a'), ('getHitCount', 'a')],)
Expected Output: [1, 1, 99, 2]
Explanation: Overwriting changes the stored value to `99` but preserves the existing hit count.
Input: ([('put', 'k', 5), ('get', 'k'), ('delete', 'k'), ('getHitCount', 'k'), ('put', 'k', 7), ('getHitCount', 'k'), ('get', 'k'), ('getHitCount', 'k')],)
Expected Output: [5, True, None, 0, 7, 1]
Explanation: After deletion, reinserting `k` starts a new hit count at 0.
Input: ([],)
Expected Output: []
Explanation: Edge case: no operations produce no output.
Hints
- A dictionary is the natural choice for the main store. You can keep another dictionary for hit counts.
- Decide the overwrite behavior first. Here, overwriting updates the value but keeps the hit count for that key unchanged.