Skip to content

Commit 9bc6bf3

Browse files
committed
refactor: Enhance docs, code, add tests in SkylineProblem
1 parent a0b6c52 commit 9bc6bf3

File tree

2 files changed

+233
-118
lines changed

2 files changed

+233
-118
lines changed
Lines changed: 147 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,45 @@
1+
12
package com.thealgorithms.others;
23

34
import java.util.ArrayList;
5+
import java.util.List;
6+
import java.util.Objects;
47

58
/**
6-
* The {@code SkylineProblem} class is used to solve the skyline problem using a
7-
* divide-and-conquer approach.
8-
* It reads input for building data, processes it to find the skyline, and
9-
* prints the skyline.
9+
* <h2>Skyline Problem</h2>
10+
* <p>
11+
* Solves the classic skyline problem using a divide-and-conquer approach. Given
12+
* a list of buildings (each defined by left, height, right),
13+
* computes the silhouette (skyline) formed by these buildings when viewed from
14+
* a distance.
15+
* </p>
16+
* <p>
17+
* Usage example:
18+
*
19+
* <pre>
20+
* SkylineProblem sp = new SkylineProblem();
21+
* sp.building = new SkylineProblem.Building[3];
22+
* sp.add(1, 10, 5);
23+
* sp.add(2, 15, 7);
24+
* sp.add(3, 12, 9);
25+
* List<SkylineProblem.Skyline> skyline = sp.findSkyline(0, 2);
26+
* </pre>
27+
* </p>
28+
* <p>
29+
* This class is not thread-safe.
30+
* </p>
1031
*/
1132
public class SkylineProblem {
1233

13-
Building[] building;
14-
int count;
34+
/**
35+
* Array of buildings to process. Must be initialized before use.
36+
*/
37+
public Building[] building;
38+
39+
/**
40+
* Number of buildings added so far.
41+
*/
42+
public int count;
1543

1644
/**
1745
* Adds a building with the given left, height, and right values to the
@@ -20,101 +48,121 @@ public class SkylineProblem {
2048
* @param left The left x-coordinate of the building.
2149
* @param height The height of the building.
2250
* @param right The right x-coordinate of the building.
51+
* @throws IllegalArgumentException if left >= right or height < 0
52+
* @throws IllegalStateException if building array is not initialized or is
53+
* full
2354
*/
2455
public void add(int left, int height, int right) {
56+
if (building == null) {
57+
throw new IllegalStateException("Building array not initialized");
58+
}
59+
if (count >= building.length) {
60+
throw new IllegalStateException("Building array is full");
61+
}
62+
if (left >= right) {
63+
throw new IllegalArgumentException("Left coordinate must be less than right coordinate");
64+
}
65+
if (height < 0) {
66+
throw new IllegalArgumentException("Height must be non-negative");
67+
}
2568
building[count++] = new Building(left, height, right);
2669
}
2770

2871
/**
2972
* Computes the skyline for a range of buildings using the divide-and-conquer
3073
* strategy.
3174
*
32-
* @param start The starting index of the buildings to process.
33-
* @param end The ending index of the buildings to process.
75+
* @param start The starting index of the buildings to process (inclusive).
76+
* @param end The ending index of the buildings to process (inclusive).
3477
* @return A list of {@link Skyline} objects representing the computed skyline.
78+
* @throws IllegalArgumentException if indices are out of bounds or building
79+
* array is null
3580
*/
36-
public ArrayList<Skyline> findSkyline(int start, int end) {
81+
public List<Skyline> findSkyline(int start, int end) {
82+
if (building == null) {
83+
throw new IllegalArgumentException("Building array is not initialized");
84+
}
85+
if (start < 0 || end >= count || start > end) {
86+
throw new IllegalArgumentException("Invalid start or end index");
87+
}
3788
// Base case: only one building, return its skyline.
3889
if (start == end) {
39-
ArrayList<Skyline> list = new ArrayList<>();
90+
List<Skyline> list = new ArrayList<>();
4091
list.add(new Skyline(building[start].left, building[start].height));
41-
list.add(new Skyline(building[end].right, 0)); // Add the end of the building
92+
list.add(new Skyline(building[start].right, 0)); // Add the end of the building
4293
return list;
4394
}
4495

4596
int mid = (start + end) / 2;
46-
47-
ArrayList<Skyline> sky1 = this.findSkyline(start, mid); // Find the skyline of the left half
48-
ArrayList<Skyline> sky2 = this.findSkyline(mid + 1, end); // Find the skyline of the right half
97+
List<Skyline> sky1 = this.findSkyline(start, mid); // Find the skyline of the left half
98+
List<Skyline> sky2 = this.findSkyline(mid + 1, end); // Find the skyline of the right half
4999
return this.mergeSkyline(sky1, sky2); // Merge the two skylines
50100
}
51101

52102
/**
53103
* Merges two skylines (sky1 and sky2) into one combined skyline.
54104
*
55-
* @param sky1 The first skyline list.
56-
* @param sky2 The second skyline list.
105+
* @param sky1 The first skyline list. Not modified.
106+
* @param sky2 The second skyline list. Not modified.
57107
* @return A list of {@link Skyline} objects representing the merged skyline.
108+
* @throws NullPointerException if either argument is null
58109
*/
59-
public ArrayList<Skyline> mergeSkyline(ArrayList<Skyline> sky1, ArrayList<Skyline> sky2) {
60-
int currentH1 = 0;
61-
int currentH2 = 0;
62-
ArrayList<Skyline> skyline = new ArrayList<>();
63-
int maxH = 0;
64-
65-
// Merge the two skylines
66-
while (!sky1.isEmpty() && !sky2.isEmpty()) {
67-
if (sky1.get(0).coordinates < sky2.get(0).coordinates) {
68-
int currentX = sky1.get(0).coordinates;
69-
currentH1 = sky1.get(0).height;
70-
71-
if (currentH1 < currentH2) {
72-
sky1.remove(0);
73-
if (maxH != currentH2) {
74-
skyline.add(new Skyline(currentX, currentH2));
75-
}
76-
} else {
77-
maxH = currentH1;
78-
sky1.remove(0);
79-
skyline.add(new Skyline(currentX, currentH1));
80-
}
81-
} else {
82-
int currentX = sky2.get(0).coordinates;
83-
currentH2 = sky2.get(0).height;
84-
85-
if (currentH2 < currentH1) {
86-
sky2.remove(0);
87-
if (maxH != currentH1) {
88-
skyline.add(new Skyline(currentX, currentH1));
89-
}
90-
} else {
91-
maxH = currentH2;
92-
sky2.remove(0);
93-
skyline.add(new Skyline(currentX, currentH2));
94-
}
110+
public List<Skyline> mergeSkyline(List<Skyline> sky1, List<Skyline> sky2) {
111+
Objects.requireNonNull(sky1, "sky1 must not be null");
112+
Objects.requireNonNull(sky2, "sky2 must not be null");
113+
int i = 0, j = 0;
114+
int h1 = 0, h2 = 0;
115+
int prevHeight = 0;
116+
List<Skyline> result = new ArrayList<>();
117+
while (i < sky1.size() && j < sky2.size()) {
118+
Skyline p1 = sky1.get(i);
119+
Skyline p2 = sky2.get(j);
120+
int x;
121+
if (p1.coordinates < p2.coordinates) {
122+
x = p1.coordinates;
123+
h1 = p1.height;
124+
i++;
125+
} else if (p2.coordinates < p1.coordinates) {
126+
x = p2.coordinates;
127+
h2 = p2.height;
128+
j++;
129+
} else { // same x
130+
x = p1.coordinates;
131+
h1 = p1.height;
132+
h2 = p2.height;
133+
i++;
134+
j++;
135+
}
136+
int maxH = Math.max(h1, h2);
137+
if (result.isEmpty() || prevHeight != maxH) {
138+
result.add(new Skyline(x, maxH));
139+
prevHeight = maxH;
95140
}
96141
}
97-
98-
// Add any remaining points from sky1 or sky2
99-
while (!sky1.isEmpty()) {
100-
skyline.add(sky1.get(0));
101-
sky1.remove(0);
142+
// Append remaining points
143+
while (i < sky1.size()) {
144+
Skyline p = sky1.get(i++);
145+
if (result.isEmpty() || result.get(result.size() - 1).height != p.height || result.get(result.size() - 1).coordinates != p.coordinates) {
146+
result.add(new Skyline(p.coordinates, p.height));
147+
}
102148
}
103-
104-
while (!sky2.isEmpty()) {
105-
skyline.add(sky2.get(0));
106-
sky2.remove(0);
149+
while (j < sky2.size()) {
150+
Skyline p = sky2.get(j++);
151+
if (result.isEmpty() || result.get(result.size() - 1).height != p.height || result.get(result.size() - 1).coordinates != p.coordinates) {
152+
result.add(new Skyline(p.coordinates, p.height));
153+
}
107154
}
108-
109-
return skyline;
155+
return result;
110156
}
111157

112158
/**
113-
* A class representing a point in the skyline with its x-coordinate and height.
159+
* Represents a point in the skyline with its x-coordinate and height.
114160
*/
115-
public class Skyline {
116-
public int coordinates;
117-
public int height;
161+
public static class Skyline {
162+
/** The x-coordinate of the skyline point. */
163+
public final int coordinates;
164+
/** The height of the skyline at the given coordinate. */
165+
public final int height;
118166

119167
/**
120168
* Constructor for the {@code Skyline} class.
@@ -126,16 +174,36 @@ public Skyline(int coordinates, int height) {
126174
this.coordinates = coordinates;
127175
this.height = height;
128176
}
177+
178+
@Override
179+
public boolean equals(Object o) {
180+
if (this == o) return true;
181+
if (o == null || getClass() != o.getClass()) return false;
182+
Skyline skyline = (Skyline) o;
183+
return coordinates == skyline.coordinates && height == skyline.height;
184+
}
185+
186+
@Override
187+
public int hashCode() {
188+
return Objects.hash(coordinates, height);
189+
}
190+
191+
@Override
192+
public String toString() {
193+
return "(" + coordinates + ", " + height + ")";
194+
}
129195
}
130196

131197
/**
132-
* A class representing a building with its left, height, and right
133-
* x-coordinates.
198+
* Represents a building with its left, height, and right x-coordinates.
134199
*/
135-
public class Building {
136-
public int left;
137-
public int height;
138-
public int right;
200+
public static class Building {
201+
/** The left x-coordinate of the building. */
202+
public final int left;
203+
/** The height of the building. */
204+
public final int height;
205+
/** The right x-coordinate of the building. */
206+
public final int right;
139207

140208
/**
141209
* Constructor for the {@code Building} class.
@@ -149,5 +217,11 @@ public Building(int left, int height, int right) {
149217
this.height = height;
150218
this.right = right;
151219
}
220+
221+
@Override
222+
public String toString() {
223+
return "Building{"
224+
+ "left=" + left + ", height=" + height + ", right=" + right + '}';
225+
}
152226
}
153227
}

0 commit comments

Comments
 (0)