Skip to content

Commit f1fc73d

Browse files
committed
feat: solve booking closest hotel problem
1 parent 9f6d85a commit f1fc73d

File tree

4 files changed

+485
-0
lines changed

4 files changed

+485
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Booking.com Closest Hotels
2+
3+
## Problem Description
4+
5+
You are tasked with implementing a system to find the K closest hotels from a given starting point for Booking.com's hotel search feature. The system should efficiently process hotel locations and return the nearest hotels based on geographical distance.
6+
7+
### Input Format
8+
9+
The system receives:
10+
1. A list of hotels with their coordinates and metadata
11+
2. A starting point (latitude, longitude)
12+
3. An integer K representing the number of closest hotels to find
13+
14+
```json
15+
{
16+
"hotels": [
17+
{
18+
"hotel_id": "H1",
19+
"name": "Grand Hotel",
20+
"latitude": 52.3676,
21+
"longitude": 4.9041,
22+
"rating": 4.5,
23+
"price_per_night": 150.00,
24+
"amenities": ["wifi", "pool", "gym"]
25+
},
26+
{
27+
"hotel_id": "H2",
28+
"name": "Seaside Resort",
29+
"latitude": 52.3702,
30+
"longitude": 4.8952,
31+
"rating": 4.2,
32+
"price_per_night": 200.00,
33+
"amenities": ["wifi", "beach", "restaurant"]
34+
}
35+
// ... more hotels
36+
],
37+
"start_point": {
38+
"latitude": 52.3670,
39+
"longitude": 4.9010
40+
},
41+
"k": 5
42+
}
43+
```
44+
45+
### Requirements
46+
47+
1. Implement a function that returns the K closest hotels to the starting point, sorted by distance.
48+
2. The solution should be efficient for large datasets (millions of hotels).
49+
3. Consider additional factors that might affect hotel selection:
50+
- Hotel rating
51+
- Price per night
52+
- Available amenities
53+
- Current availability
54+
55+
4. Handle edge cases:
56+
- Multiple hotels at the same distance
57+
- Invalid coordinates
58+
- Empty hotel list
59+
- K larger than the number of available hotels
60+
- Hotels with missing or invalid data
61+
62+
### Additional Scenarios to Consider
63+
64+
1. How would you handle real-time updates to hotel availability?
65+
2. What if users want to filter results by price range or amenities?
66+
3. How would you implement caching for frequently searched locations?
67+
4. How would you handle different distance metrics (e.g., walking distance vs. straight-line distance)?
68+
5. What if you need to consider traffic conditions or transportation options?
69+
70+
### Technical Requirements
71+
72+
1. Write code that efficiently finds the K closest hotels
73+
2. Explain the runtime complexity of your solution
74+
3. Suggest optimizations for handling large datasets
75+
4. Consider how to make the system scalable for future requirements
76+
77+
### Example Output
78+
79+
```json
80+
{
81+
"closest_hotels": [
82+
{
83+
"hotel_id": "H1",
84+
"name": "Grand Hotel",
85+
"distance": 0.3, // in kilometers
86+
"rating": 4.5,
87+
"price_per_night": 150.00,
88+
"amenities": ["wifi", "pool", "gym"]
89+
},
90+
{
91+
"hotel_id": "H2",
92+
"name": "Seaside Resort",
93+
"distance": 0.5,
94+
"rating": 4.2,
95+
"price_per_night": 200.00,
96+
"amenities": ["wifi", "beach", "restaurant"]
97+
}
98+
// ... more hotels up to K
99+
],
100+
"search_metadata": {
101+
"total_hotels_considered": 1000,
102+
"search_time_ms": 45,
103+
"radius_covered_km": 2.5
104+
}
105+
}
106+
```
107+
108+
### Evaluation Criteria
109+
110+
1. Algorithm efficiency and correctness
111+
2. Code quality and organization
112+
3. Handling of edge cases
113+
4. Runtime complexity analysis
114+
5. Scalability considerations
115+
6. Additional features implemented
116+
7. Problem-solving approach
117+
118+
### Notes
119+
120+
- The Earth's curvature should be considered when calculating distances
121+
- The solution should be optimized for both time and space complexity
122+
- Consider using appropriate data structures for efficient nearest neighbor searches
123+
- The system should be able to handle frequent updates to hotel data
124+
- Think about how to make the solution maintainable and extensible
125+
126+
### Source
127+
128+
This problem is based on a real Booking.com interview experience:
129+
https://leetcode.com/discuss/post/1395715/bookingcom-backend-developer-amsterdam-j-cm4f/
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
from dataclasses import dataclass
2+
from typing import List, Dict, Optional, Set, Tuple, Any, NoReturn
3+
import math
4+
from collections import defaultdict
5+
6+
7+
@dataclass
8+
class Point:
9+
latitude: float
10+
longitude: float
11+
12+
13+
@dataclass
14+
class Hotel:
15+
hotel_id: str
16+
name: str
17+
latitude: float
18+
longitude: float
19+
rating: float
20+
price_per_night: float
21+
amenities: List[str]
22+
23+
24+
class ClosestHotelsFinder:
25+
def __init__(self) -> None:
26+
self.hotels: List[Hotel] = []
27+
self._rtree = None # Placeholder for R-tree implementation
28+
29+
def add_hotel(self, hotel_data: Dict) -> None:
30+
"""Add a hotel to the finder."""
31+
try:
32+
hotel = Hotel(**hotel_data)
33+
self._validate_coordinates(hotel.latitude, hotel.longitude)
34+
self.hotels.append(hotel)
35+
except (TypeError, ValueError) as e:
36+
raise ValueError(f"Invalid hotel data: {str(e)}")
37+
38+
def _validate_coordinates(self, latitude: float, longitude: float) -> None:
39+
"""Validate that coordinates are within valid ranges."""
40+
if not (-90 <= latitude <= 90):
41+
raise ValueError(f"Invalid latitude: {latitude}")
42+
if not (-180 <= longitude <= 180):
43+
raise ValueError(f"Invalid longitude: {longitude}")
44+
45+
def _calculate_distance(self, point1: Point, point2: Point) -> float:
46+
"""Calculate the Haversine distance between two points in kilometers."""
47+
# Earth's radius in kilometers
48+
R = 6371.0
49+
50+
lat1, lon1 = math.radians(point1.latitude), math.radians(point1.longitude)
51+
lat2, lon2 = math.radians(point2.latitude), math.radians(point2.longitude)
52+
53+
dlat = lat2 - lat1
54+
dlon = lon2 - lon1
55+
56+
a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
57+
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
58+
59+
return R * c
60+
61+
def find_closest_hotels(
62+
self,
63+
start_point: Dict[str, float],
64+
k: int,
65+
filters: Optional[Dict] = None
66+
) -> Dict[str, Any]:
67+
"""Find the K closest hotels to the starting point."""
68+
if not self.hotels:
69+
return {
70+
"closest_hotels": [],
71+
"search_metadata": {
72+
"total_hotels_considered": 0,
73+
"search_time_ms": 0,
74+
"radius_covered_km": 0
75+
}
76+
}
77+
78+
try:
79+
start = Point(**start_point)
80+
self._validate_coordinates(start.latitude, start.longitude)
81+
except (TypeError, ValueError) as e:
82+
raise ValueError(f"Invalid start point: {str(e)}")
83+
84+
if k <= 0:
85+
raise ValueError("K must be a positive integer")
86+
87+
# Apply filters if provided
88+
filtered_hotels = self._apply_filters(self.hotels, filters)
89+
90+
# Calculate distances and sort
91+
hotels_with_distances = []
92+
for hotel in filtered_hotels:
93+
hotel_point = Point(latitude=hotel.latitude, longitude=hotel.longitude)
94+
distance = self._calculate_distance(start, hotel_point)
95+
hotels_with_distances.append((hotel, distance))
96+
97+
# Sort by distance and take top K
98+
hotels_with_distances.sort(key=lambda x: x[1])
99+
closest_hotels = hotels_with_distances[:k]
100+
101+
# Prepare response
102+
result = {
103+
"closest_hotels": [
104+
{
105+
"hotel_id": hotel.hotel_id,
106+
"name": hotel.name,
107+
"distance": round(distance, 2),
108+
"rating": hotel.rating,
109+
"price_per_night": hotel.price_per_night,
110+
"amenities": hotel.amenities
111+
}
112+
for hotel, distance in closest_hotels
113+
],
114+
"search_metadata": {
115+
"total_hotels_considered": len(filtered_hotels),
116+
"search_time_ms": 0, # Would be implemented with actual timing
117+
"radius_covered_km": round(closest_hotels[-1][1], 2) if closest_hotels else 0
118+
}
119+
}
120+
121+
return result
122+
123+
def _apply_filters(self, hotels: List[Hotel], filters: Optional[Dict]) -> List[Hotel]:
124+
"""Apply filters to the hotel list."""
125+
if not filters:
126+
return hotels
127+
128+
filtered_hotels = hotels.copy()
129+
130+
if "min_rating" in filters:
131+
filtered_hotels = [h for h in filtered_hotels if h.rating >= filters["min_rating"]]
132+
133+
if "max_price" in filters:
134+
filtered_hotels = [h for h in filtered_hotels if h.price_per_night <= filters["max_price"]]
135+
136+
if "amenities" in filters:
137+
required_amenities = set(filters["amenities"])
138+
filtered_hotels = [
139+
h for h in filtered_hotels
140+
if required_amenities.issubset(set(h.amenities))
141+
]
142+
143+
return filtered_hotels
144+
145+
def load_hotels_from_file(self, file_path: str) -> None:
146+
"""Load hotels from a JSON file."""
147+
import json
148+
try:
149+
with open(file_path, 'r') as f:
150+
data = json.load(f)
151+
for hotel_data in data.get("hotels", []):
152+
self.add_hotel(hotel_data)
153+
except (FileNotFoundError, json.JSONDecodeError) as e:
154+
raise Exception(f"Error loading hotels: {str(e)}")

packages/python-exercise/python_exercise/permutations.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,27 @@
11
def permutations(nums):
2+
"""
3+
Generate all possible permutations of the given list of numbers.
4+
5+
This function uses a backtracking algorithm to generate all permutations:
6+
1. We maintain a 'start' index that divides the array into two parts:
7+
- Elements before 'start' are fixed in the current permutation
8+
- Elements from 'start' onward are candidates for the next position
9+
2. For each recursive call, we try all possible elements at the current position
10+
by swapping the element at 'start' with each element from 'start' to the end
11+
3. After each swap, we recursively generate permutations for the next position
12+
4. After the recursive call, we swap back (backtrack) to restore the original order
13+
5. When 'start' reaches the end of the array, we've completed a permutation and add it to results
14+
15+
Args:
16+
nums: A list of elements to permute
17+
18+
Returns:
19+
A list containing all possible permutations of the input list
20+
"""
221
def backtrack(start):
322
if start == len(nums):
23+
# We've reached the end of the array, so we've completed a permutation
24+
# Make a copy of the current state and add it to our results
425
result.append(nums[:])
526
return
627

0 commit comments

Comments
 (0)