Quick Overview

Calculate cart totals from item coupons with exact half-even cent rounding, unmatched-coupon handling, and integer monetary totals.

Apply Item Coupons with Exact Per-Item Cent Rounding

Company: Palantir

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Online Assessment

Apply item-specific percentage coupons to a shopping cart and return its subtotal, total discount, and final total. Prices and all returned monetary amounts are integer cents. ### Function Contract Implement `apply_discounts_and_calculate_total(items, discounts) -> dict`. Each item is an object with `name` (string) and `price` (nonnegative integer cents). Each discount is an object with `name` (the matching item name) and `percent_off` (an integer percentage). Return exactly these integer-valued keys: - `subtotal`: sum of original item prices. - `total_discount`: sum of the individually rounded item discount amounts. - `final_total`: `subtotal - total_discount`. ### Rounding and Matching A matching coupon's unrounded discount is `price * percent_off / 100`. Round that final amount to the nearest integer cent, breaking exact half-cent ties toward the even integer, matching Python's ties-to-even `round` convention. Do not round any earlier calculation, and do not round only the cart-wide sum. Treat the percentage calculation exactly, without relying on binary floating-point error to resolve a tie. There is at most one coupon per item name. Matching is exact and case-sensitive. A coupon applies to every cart row with that name; unmatched coupons are ignored. Items without a coupon have zero discount. This initial version has no 80-percent cap. ### Constraints and Clarifications - Both arrays may be empty and have at most `100000` entries each. - Names are nonempty ASCII strings of length at most 100. - `0 <= price <= 1000000000`. - `0 <= percent_off <= 100`. - Bounds, integral percentages, and exact arithmetic are explicit portable practice conventions. - Cart totals may exceed signed 32-bit range. ### Examples ```text items = [{"name": "Bananas", "price": 399}, {"name": "Bread", "price": 250}] discounts = [{"name": "Bananas", "percent_off": 10}] Output: {"subtotal": 649, "total_discount": 40, "final_total": 609} ``` ```text items = [{"name": "A", "price": 5}, {"name": "B", "price": 7}] discounts = [{"name": "A", "percent_off": 50}, {"name": "B", "percent_off": 50}] Output: {"subtotal": 12, "total_discount": 6, "final_total": 6} ``` The discounts `2.5` and `3.5` cents round to `2` and `4`, respectively. ```hint Keep the matching and rounding steps separate First find the coupon for the item name, then calculate and round that item's final discount exactly once. ```

Overview: Calculate cart totals from item coupons with exact half-even cent rounding, unmatched-coupon handling, and integer monetary totals.

Read the full Palantir Software Engineer interview experience this question came from

You are given a shopping cart and a list of percentage coupons. Every price and every amount you return is an integer number of cents. `items` is a list of `[name, price]` pairs, one per cart row, in cart order: `name` is the row's item name and `price` is its original price in cents. The same name may appear on several rows. `discounts` is a list of `[name, percent_off]` pairs, where `percent_off` is an integer percentage. There is at most one coupon per name. Return a dictionary with exactly these three integer-valued keys: - `subtotal`: the sum of the original prices of every cart row. - `total_discount`: the sum of the individually rounded item discount amounts. - `final_total`: `subtotal - total_discount`. Matching and rounding: - A cart row is discounted when a coupon's name equals the row's name exactly; matching is exact and case-sensitive. A coupon applies to every cart row with that name. A row with no matching coupon has a discount of 0, and a coupon that matches no row is ignored. - A matched row's unrounded discount is `price * percent_off / 100`. Round that final amount to the nearest integer cent, breaking an exact half-cent tie toward the even integer, matching Python's ties-to-even `round` convention: a discount of 2.5 cents rounds to 2 and a discount of 3.5 cents rounds to 4. - Do not round any earlier calculation, and do not round only the cart-wide sum: every row is rounded on its own before the discounts are added together. Treat the percentage calculation exactly, without relying on binary floating-point error to resolve a tie. - This initial version has no 80-percent cap; `percent_off` may be anything from 0 through 100. The result is a single dictionary of three sums, so the only output semantics are the three key names and their exact integer values. Cart totals may exceed signed 32-bit range: with up to 100000 rows priced up to 1000000000 cents each, `subtotal`, `total_discount` and `final_total` can reach 10^14, which is larger than 2^31 - 1. Use `long` in Java and `long long` in C++. Example 1: ```text items = [["Bananas", 399], ["Bread", 250]] discounts = [["Bananas", 10]] Output: {"subtotal": 649, "total_discount": 40, "final_total": 609} ``` Bananas: 399 * 10 / 100 = 39.9 cents, which rounds to 40. Bread has no coupon, so its discount is 0. Example 2: ```text items = [["A", 5], ["B", 7]] discounts = [["A", 50], ["B", 50]] Output: {"subtotal": 12, "total_discount": 6, "final_total": 6} ``` The discounts 2.5 and 3.5 cents round to 2 and 4, respectively.

Constraints

  • Both arrays may be empty and have at most 100000 entries each.
  • Each item is a pair [name, price]; each discount is a pair [name, percent_off].
  • Names are nonempty ASCII strings of length at most 100.
  • 0 <= price <= 1000000000.
  • 0 <= percent_off <= 100, and percent_off is an integer.
  • There is at most one coupon per item name; matching is exact and case-sensitive, and unmatched coupons are ignored.
  • This initial version has no 80-percent cap.
  • Cart totals may exceed signed 32-bit range (use long in Java and long long in C++).
  • Prices and all returned monetary amounts are integer cents; the three returned keys are exactly subtotal, total_discount and final_total.

Examples

Input: ([], [])

Expected Output: {'subtotal': 0, 'total_discount': 0, 'final_total': 0}

Explanation: Minimum valid input: an empty cart and no coupons give three zeros.

Input: ([], [['Bread', 50]])

Expected Output: {'subtotal': 0, 'total_discount': 0, 'final_total': 0}

Explanation: An empty cart with a coupon: the coupon matches no row and is ignored.

Hints

  1. Keep the matching and the rounding steps separate: first find the coupon that belongs to an item's exact name, then calculate and round that one item's final discount exactly once.
  2. Each matched row is rounded on its own and only the rounded amounts are summed; rounding the cart-wide total instead can give a different answer, so decide which quantity you are rounding before you write the loop.
  3. The statement forbids letting binary floating-point error settle an exact half-cent tie, and every input value is an integer, so an exact treatment of price * percent_off / 100 is available.

Loading coding console...

Show the approach

Approach

Algorithm: build a lookup from coupon name to percent_off (there is at most one coupon per name, so a hash map suffices), then scan the cart once. Every row adds its price to subtotal. If the row's exact, case-sensitive name is present in the lookup, compute that row's discount with integer arithmetic only: let n = price * percent_off, r = n % 100 and q = (n - r) / 100, so the exact unrounded discount is q + r/100 with 0 <= r < 100. Round half to even: if 2r > 100 the fractional part exceeds one half and the rounded discount is q + 1; if 2r < 100 it is q; if 2r == 100 the tie goes to the even integer, which is q when q is even and q + 1 when q is odd. Add the rounded value to total_discount.

Invariant: after k rows have been processed, subtotal is the sum of the first k prices and total_discount is the sum of the first k independently rounded discounts. Nothing else is accumulated, so final_total = subtotal - total_discount holds by definition once the scan ends.

Correctness: prices and percentages are nonnegative integers, so n is a nonnegative integer and the pair (q, r) is exactly its Euclidean division by 100; the branch above reproduces ties-to-even rounding of the exact rational n/100 without touching floating point, so no tie is ever decided by representation error. Rounding per row rather than rounding the cart-wide sum is exactly what the statement mandates, and the two genuinely differ: three rows of 3 cents at 50 percent give 2 + 2 + 2 = 6 per row, while rounding the summed 4.5 would give 4.

Edge cases: an empty cart, an empty coupon list, or both, return three zeros; a coupon matching no row contributes nothing; a name differing only in case ('bread' vs 'Bread') does not match; price 0 or percent_off 0 yields a zero discount; percent_off 100 discounts the row entirely; duplicate rows with the same name are discounted and rounded independently. Because 100000 rows priced up to 1000000000 push the sums to 10^14, all accumulators are 64-bit (long in Java, long long in C++) and stay well inside the 2^53 exact-integer range in JavaScript.

Time complexity:
O(n + m) expected, where n is the number of cart rows and m the number of coupons: one hash-map build plus one linear cart scan with O(1) expected lookups.
Space complexity:
O(m) for the coupon lookup, plus O(1) for the three accumulators and the returned dictionary.