Created
December 10, 2024 11:16
-
-
Save rjvitorino/ef5905749489173598c4c51462bd19e9 to your computer and use it in GitHub Desktop.
Cassidy's interview question of the week: a function that finds the maximum number of gifts that can be wrapped using a single strip of wrapping paper of a given width
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import Sequence | |
from array import array | |
from itertools import accumulate | |
def wrap_gifts(gift_lengths: Sequence[int], paper_width: int) -> int: | |
""" | |
Find the maximum number of gifts that can be wrapped using a single strip of wrapping paper. | |
Args: | |
gift_lengths (Sequence[int]): The lengths of the gifts. | |
paper_width (int): The width of the wrapping paper. | |
Returns: | |
int: The maximum number of gifts that can be wrapped. | |
Raises: | |
ValueError: If paper_width is negative or gift_lengths contains negative values. | |
""" | |
# Fast path for common cases | |
if not gift_lengths: | |
return 0 | |
if paper_width <= 0: | |
raise ValueError("Paper width cannot be negative") | |
# Use array for memory efficiency | |
gifts = array("i", sorted(gift_lengths)) | |
# Early exit if smallest gift doesn't fit | |
if gifts[0] > paper_width: | |
return 0 | |
# Use prefix sum for efficient length calculation: | |
# [1, 2, 3, 4] results in [1, 3, 6, 10] meaning each | |
# element represents the sum of all previous + current one | |
prefix_sums = array("i", accumulate(gifts)) | |
# Binary search optimization | |
left, right = 0, len(gifts) | |
# left: starts at 0 (no gifts) | |
# right: starts at total number of gifts | |
# These represent the search range for the maximum number of gifts that can fit | |
while left < right: | |
# Continues until the search range converges to a single point | |
# When left == right, we've found our answer | |
mid = (left + right + 1) >> 1 | |
# >> is a bit shift operator, equivalent to dividing by 2 but faster | |
if prefix_sums[mid - 1] <= paper_width: | |
left = mid | |
else: | |
right = mid - 1 | |
return left | |
def test_wrap_gifts(): | |
"""Test cases for wrap_gifts function with performance metrics.""" | |
test_cases = [ | |
([2, 3, 4, 5], 7, 2), # Either [2, 5] or [3, 4] | |
([1, 1, 1, 1, 1, 1, 1], 3, 3), # [1, 1, 1] | |
([1, 2, 3, 4, 5], 6, 3), # [1, 2, 3] | |
([5, 5, 5, 5], 10, 2), # [5, 5] | |
([6, 7, 8], 5, 0), # No gift fits | |
([3, 2, 1], 6, 3), # [1, 2, 3] after sorting | |
] | |
for gifts, width, expected in test_cases: | |
result = wrap_gifts(gifts, width) | |
assert ( | |
result == expected | |
), f"Failed: gifts={gifts}, width={width}, got={result}, expected={expected}" | |
print("All tests passed!") | |
if __name__ == "__main__": | |
test_wrap_gifts() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment