fork download
  1. """Professional Services Request Prioritization
  2.  
  3. Order: high -> medium -> low; within a priority, fewer estimated_hours first;
  4. full ties keep their original order.
  5. Works on plain Python 3 (no external libraries), so it runs on ideone as-is.
  6. """
  7.  
  8. # Lower number = handled earlier. A dict makes the rule easy to read and extend.
  9. PRIORITY_RANK = {"high": 0, "medium": 1, "low": 2}
  10.  
  11.  
  12. def _sort_key(request):
  13. """Validate one request and return its (priority_rank, hours) sort key."""
  14. # Required fields must exist
  15. for field in ("id", "customer", "priority", "estimated_hours"):
  16. if field not in request:
  17. raise ValueError(f"Request is missing '{field}': {request}")
  18.  
  19. # Normalise priority so "High" / " HIGH " still work
  20. priority = str(request["priority"]).strip().lower()
  21. if priority not in PRIORITY_RANK:
  22. raise ValueError(
  23. f"Unknown priority '{request['priority']}' in {request['id']}; "
  24. f"expected one of {list(PRIORITY_RANK)}"
  25. )
  26.  
  27. # Hours must be a non-negative number (bool is an int in Python, so reject it)
  28. hours = request["estimated_hours"]
  29. if isinstance(hours, bool) or not isinstance(hours, (int, float)) or hours < 0:
  30. raise ValueError(f"Invalid estimated_hours '{hours}' in {request['id']}")
  31.  
  32. return PRIORITY_RANK[priority], hours
  33.  
  34.  
  35. def prioritize_requests(requests):
  36. """Return a NEW list of requests in the order they should be handled.
  37.  
  38. sorted() is stable: items with equal keys keep their original relative order,
  39. which satisfies the "preserve original order on a full tie" rule for free.
  40. The input list is not modified.
  41. """
  42. return sorted(requests, key=_sort_key)
  43.  
  44.  
  45. # ----------------------------- tests -----------------------------
  46.  
  47. def ids(requests):
  48. return [r["id"] for r in requests]
  49.  
  50.  
  51. def make(req_id, priority, hours, customer="Acme"):
  52. return {"id": req_id, "customer": customer, "priority": priority, "estimated_hours": hours}
  53.  
  54.  
  55. def test_sample_input():
  56. sample = [
  57. make("REQ-101", "medium", 3, "Alpha Ltd"),
  58. make("REQ-102", "high", 5, "Beta Corp"),
  59. make("REQ-103", "low", 2, "Alpha Ltd"),
  60. make("REQ-104", "high", 4, "Gamma Inc"),
  61. ]
  62. assert ids(prioritize_requests(sample)) == ["REQ-104", "REQ-102", "REQ-101", "REQ-103"]
  63.  
  64.  
  65. def test_same_priority_orders_by_hours():
  66. reqs = [make("A", "medium", 8), make("B", "medium", 1), make("C", "medium", 4)]
  67. assert ids(prioritize_requests(reqs)) == ["B", "C", "A"]
  68.  
  69.  
  70. def test_tie_preserves_original_order():
  71. # Same priority AND same hours -> must stay in input order (stability)
  72. reqs = [make("FIRST", "high", 3), make("SECOND", "high", 3), make("THIRD", "high", 3)]
  73. assert ids(prioritize_requests(reqs)) == ["FIRST", "SECOND", "THIRD"]
  74.  
  75.  
  76. def test_priority_beats_hours():
  77. # A low-priority 1-hour job must not jump ahead of a high-priority 40-hour job
  78. reqs = [make("LOW", "low", 1), make("HIGH", "high", 40)]
  79. assert ids(prioritize_requests(reqs)) == ["HIGH", "LOW"]
  80.  
  81.  
  82. def test_empty_list():
  83. assert prioritize_requests([]) == []
  84.  
  85.  
  86. def test_single_request_and_zero_hours():
  87. reqs = [make("ONLY", "low", 0)]
  88. assert ids(prioritize_requests(reqs)) == ["ONLY"]
  89.  
  90.  
  91. def test_priority_case_and_whitespace_insensitive():
  92. reqs = [make("L", "LOW", 1), make("H", " High ", 9)]
  93. assert ids(prioritize_requests(reqs)) == ["H", "L"]
  94.  
  95.  
  96. def test_float_hours():
  97. reqs = [make("A", "high", 2.5), make("B", "high", 2.25)]
  98. assert ids(prioritize_requests(reqs)) == ["B", "A"]
  99.  
  100.  
  101. def test_input_not_modified():
  102. reqs = [make("A", "low", 1), make("B", "high", 1)]
  103. snapshot = list(reqs)
  104. prioritize_requests(reqs)
  105. assert reqs == snapshot
  106.  
  107.  
  108. def test_invalid_data_raises():
  109. bad_inputs = [
  110. [make("X", "urgent", 2)], # unknown priority
  111. [make("X", "high", -1)], # negative hours
  112. [make("X", "high", "3")], # hours not a number
  113. [make("X", "high", True)], # bool is not valid hours
  114. [{"id": "X", "customer": "A", "priority": "high"}], # missing field
  115. ]
  116. for bad in bad_inputs:
  117. try:
  118. prioritize_requests(bad)
  119. except ValueError:
  120. continue
  121. raise AssertionError(f"Expected ValueError for {bad}")
  122.  
  123.  
  124. def run_tests():
  125. tests = [v for k, v in globals().items() if k.startswith("test_") and callable(v)]
  126. for test in tests:
  127. test()
  128. print(f"PASS {test.__name__}")
  129. print(f"\nAll {len(tests)} tests passed")
  130.  
  131.  
  132. if __name__ == "__main__":
  133. sample_requests = [
  134. make("REQ-101", "medium", 3, "Alpha Ltd"),
  135. make("REQ-102", "high", 5, "Beta Corp"),
  136. make("REQ-103", "low", 2, "Alpha Ltd"),
  137. make("REQ-104", "high", 4, "Gamma Inc"),
  138. ]
  139. print("Handling order:")
  140. for position, req in enumerate(prioritize_requests(sample_requests), start=1):
  141. print(f"{position}. {req['id']} {req['customer']:<10} "
  142. f"{req['priority']:<6} {req['estimated_hours']}h")
  143. print()
  144. run_tests()
Success #stdin #stdout 0.07s 14152KB
stdin
numbers = (1,2,3,3,2,4,5,4,6,6,7,8,8,9,0)
stdout
Handling order:
1. REQ-104  Gamma Inc  high   4h
2. REQ-102  Beta Corp  high   5h
3. REQ-101  Alpha Ltd  medium 3h
4. REQ-103  Alpha Ltd  low    2h

PASS  test_sample_input
PASS  test_same_priority_orders_by_hours
PASS  test_tie_preserves_original_order
PASS  test_priority_beats_hours
PASS  test_empty_list
PASS  test_single_request_and_zero_hours
PASS  test_priority_case_and_whitespace_insensitive
PASS  test_float_hours
PASS  test_input_not_modified
PASS  test_invalid_data_raises

All 10 tests passed