Quick Overview

Rank candidate items for a user by the dot product of the user's embedding with each item's embedding, returning item indices from highest to lowest score with ties broken by index. A follow-up removes items the user has already seen, testing vectorized scoring, sorting and filtering.

Rank Items by Dot-Product Similarity to a User Embedding, Excluding Seen Items

Company: Yahoo

Role: Machine Learning Engineer

Category: Coding & Algorithms

Difficulty: easy

Interview Round: Technical Screen

A recommendation model represents a user and each candidate item as embedding vectors of the same dimension. The similarity score of an item is the dot product of the user's embedding and the item's embedding; a higher score means a more similar item. Given the user's embedding and the embeddings of $N$ items, return the item indices sorted from the highest score to the lowest. Follow-up from the same round: the user has already seen some of the items. Leave those items out of the ranked result. In the interview this was to be written with NumPy. Here the inputs are plain lists. ### Function Signature ```python def rank_items(user: list[int], items: list[list[int]], seen: list[int]) -> list[int]: ``` `user` is the user's embedding of dimension `d`, `items[i]` is the embedding of item `i`, and `seen` lists the indices of the items the user has already seen. ### Rules - The score of item `i` is `sum(user[j] * items[i][j] for j in range(d))`. - Return the indices of all items that are not in `seen`, ordered by score from highest to lowest. - Items with equal scores are ordered by index, smallest first. - If `seen` is empty, every item is ranked. If every item has been seen, return an empty list. - Embedding components are integers in this version, so scores are exact and ties are well defined. ### Constraints - `1 <= d <= 128`, where `d = len(user)`, and every `items[i]` has length `d`. - `1 <= len(items) <= 10^4` - Every component of `user` and of every `items[i]` is an integer in `[-1000, 1000]`, so every score lies in `[-1.28 * 10^8, 1.28 * 10^8]`. - `0 <= len(seen) <= len(items)`; the indices in `seen` are distinct, and each lies in `[0, len(items) - 1]`. ### Examples **Example 1** ```text Input: user = [1, 2], items = [[3, 0], [1, 1], [0, 2], [-1, 4]], seen = [] Output: [3, 2, 0, 1] ``` The scores are 3, 3, 4 and 7 for items 0 to 3. Item 3 ranks first and item 2 second. Items 0 and 1 tie at 3, so the smaller index comes first. **Example 2** ```text Input: user = [1, 2], items = [[3, 0], [1, 1], [0, 2], [-1, 4]], seen = [2, 0] Output: [3, 1] ``` Same scores as Example 1, but items 2 and 0 have been seen and are removed. **Example 3** ```text Input: user = [2, -1, 0], items = [[1, 1, 5], [0, 3, 1], [2, 4, -3], [-1, -5, 7]], seen = [3] Output: [0, 2, 1] ``` The scores are 1, -3, 0 and 3. Item 3 has the highest score but has been seen, so the result ranks items 0, 2 and 1.

Overview: Rank candidate items for a user by the dot product of the user's embedding with each item's embedding, returning item indices from highest to lowest score with ties broken by index. A follow-up removes items the user has already seen, testing vectorized scoring, sorting and filtering.

Read the full Yahoo Machine Learning Engineer interview experience this question came from

A recommendation model represents a user and each candidate item as embedding vectors of the same dimension `d`. The similarity score of an item is the dot product of the user's embedding and the item's embedding; a higher score means a more similar item. Implement `rank_items(user, items, seen)`. `user` is the user's embedding of dimension `d`, `items[i]` is the embedding of item `i` (there are `N = len(items)` items), and `seen` lists the indices of the items the user has already seen. The inputs are plain lists of integers. ### Rules - The score of item `i` is `sum(user[j] * items[i][j] for j in range(d))`. - Return the indices of all items that are not in `seen`, ordered by score from highest to lowest. - Items with equal scores are ordered by index, smallest first. - If `seen` is empty, every item is ranked. If every item has been seen, return an empty list. - Embedding components are integers, so scores are exact and ties are well defined. - Every score lies in `[-1.28 * 10^8, 1.28 * 10^8]`, so no value exceeds `2^31 - 1`; a 32-bit signed integer holds every score. ### Constraints - `1 <= d <= 128`, where `d = len(user)`, and every `items[i]` has length `d`. - `1 <= len(items) <= 10^4` - Every component of `user` and of every `items[i]` is an integer in `[-1000, 1000]`, so every score lies in `[-1.28 * 10^8, 1.28 * 10^8]`. - `0 <= len(seen) <= len(items)`; the indices in `seen` are distinct, and each lies in `[0, len(items) - 1]`. ### Example 1 ```text Input: user = [1, 2], items = [[3, 0], [1, 1], [0, 2], [-1, 4]], seen = [] Output: [3, 2, 0, 1] ``` The scores are 3, 3, 4 and 7 for items 0 to 3. Item 3 ranks first and item 2 second. Items 0 and 1 tie at 3, so the smaller index comes first. ### Example 2 ```text Input: user = [2, -1, 0], items = [[1, 1, 5], [0, 3, 1], [2, 4, -3], [-1, -5, 7]], seen = [3] Output: [0, 2, 1] ``` The scores are 1, -3, 0 and 3. Item 3 has the highest score but has been seen, so the result ranks items 0, 2 and 1.

Constraints

  • `1 <= d <= 128`, where `d = len(user)`, and every `items[i]` has length `d`.
  • `1 <= len(items) <= 10^4`
  • Every component of `user` and of every `items[i]` is an integer in `[-1000, 1000]`, so every score lies in `[-1.28 * 10^8, 1.28 * 10^8]`.
  • `0 <= len(seen) <= len(items)`; the indices in `seen` are distinct, and each lies in `[0, len(items) - 1]`.

Examples

Input: ([1, 2], [[3, 0], [1, 1], [0, 2], [-1, 4]], [])

Expected Output: [3, 2, 0, 1]

Explanation: Source Example 1: scores 3, 3, 4, 7; items 0 and 1 tie at 3 and keep smallest-index-first order; seen is empty, so every item is ranked.

Input: ([1, 2], [[3, 0], [1, 1], [0, 2], [-1, 4]], [2, 0])

Expected Output: [3, 1]

Explanation: Source Example 2: same scores as Example 1; the unsorted seen list [2, 0] removes items 2 and 0.

Hints

  1. Each item's score depends only on the user vector and that item's own embedding, so every score can be computed independently.
  2. The required order has two keys: the score (higher first) and, for equal scores, the item index (smaller first).
  3. Items are excluded by index, not by embedding or score: two items with identical embeddings are still different items.

Loading coding console...

Show the approach

Approach

Algorithm: put the seen indices in a set (or boolean array) so each exclusion check is O(1). For every item whose index is not seen, compute its integer dot product with the user vector. Sort those indices by the composite key (score descending, index ascending) and return them.

Invariant and correctness: the source defines the output as every unseen index, ordered by score from highest to lowest with equal scores broken by smaller index first. The composite key encodes exactly that order, and because indices are distinct it is a strict total order, so the sorted list is the unique correct answer. Exclusion is by index, so items with duplicate embeddings or equal scores are kept or dropped individually, and the order in which seen lists its indices does not matter. Filtering happens before ranking, so removing a high-scoring seen item never shifts a different item out of the result.

Edge cases: an empty seen ranks every item; when every item is seen the result is an empty list; a zero user vector makes every score 0, so the result is plain ascending index order. Scores are bounded by 128 * 1000 * 1000 = 1.28 * 10^8 in absolute value, so they fit in 32-bit integers (the Java and C++ references accumulate in 64-bit anyway) and are exact in JavaScript doubles.

Time complexity:
O(N * d + N log N)
Space complexity:
O(N)