Explanation
You can simply iterate from left to right and maintain ranges while doing so.
For that, just remember the start of a range and the last value in each iteration. Then you can easily figure out if a range just stopped or is continuing. If it stopped, conclude the range and start the next.
For the special case of an empty array, just add a simple if
to the start of the method, to handle it separately.
Build ranges
public static String buildRanges(int[] values) {
if (values.length == 0) {
return "";
}
StringJoiner result = new StringJoiner(", ");
int rangeStart = values[0];
int lastValue = values[0];
// Skip first value to simplify 'lastValue' logic
for (int i = 1; i < values.length; i++) {
int value = values[i];
if (value != lastValue + 1) {
// Range ended, wrap it up
int rangeEnd = lastValue;
result.add(rangeStart == rangeEnd
? Integer.toString(rangeStart)
: rangeStart + "~" + rangeEnd);
rangeStart = value;
}
lastValue = value;
}
// Conclude last range
int rangeEnd = lastValue;
result.add(rangeStart == rangeEnd
? Integer.toString(rangeStart)
: rangeStart + "~" + rangeEnd);
return result.toString();
}
Simplify
To tackle the code duplication and to improve readability, I would suggest to also introduce a helper method rangeToString
:
public static String rangeToString(int rangeStart, int rangeEnd) {
return rangeStart == rangeEnd
? Integer.toString(rangeStart)
: rangeStart + "~" + rangeEnd);
}
The code then simplifies to:
public static String buildRanges(int[] values) {
if (values.length == 0) {
return "";
}
StringJoiner result = new StringJoiner(", ");
int rangeStart = values[0];
int lastValue = values[0];
// Skip first value to simplify 'lastValue' logic
for (int i = 1; i < values.length; i++) {
int value = values[i];
if (value != lastValue + 1) {
// Range ended, wrap it up
result.add(rangeToString(rangeStart, lastValue);
rangeStart = value;
}
lastValue = value;
}
// Conclude last range
result.add(rangeToString(rangeStart, lastValue);
return result.toString();
}
OOP solution
If you feel like, you can also introduce dedicated Range
classes to solve this. Might be a bit overkill in this particular situation but still.
Let us first create a Range
class that knows its start and end. Furthermore, it can convert itself to a String
properly and you can attempt to increase the range.
public final class Range {
private final int start;
private int end;
public Range(int value) {
start = value;
end = value;
}
public boolean add(int value) {
if (value != end + 1) {
return false;
}
end = value;
return true;
}
@Override
public String toString() {
return start == end
? Integer.toString(start)
: start + "~" + end;
}
}
And now you can easily use that in a simple loop:
public static String buildRanges(int[] values) {
if (values.length == 0) {
return "";
}
StringJoiner result = new StringJoiner(", ");
Range range = new Range(values[0]);
// Skip first value to simplify logic
for (int i = 1; i < values.length; i++) {
int value = values[i];
if (!range.add(value)) {
// Range ended, wrap it up
result.add(range.toString());
range = new Range(value);
}
}
// Conclude last range
result.add(range.toString());
return result.toString();
}
Collecting to List<Range>
This approach has the advantage that you can also collect to something like a List<Range> ranges
and then continue working with the data, instead of only going to a String
. Example:
public static List<Range> buildRanges(int[] values) {
if (values.length == 0) {
return List.of();
}
List<Range> ranges = new ArrayList<>();
Range range = new Range(values[0]);
// Skip first value to simplify logic
for (int i = 1; i < values.length; i++) {
int value = values[i];
if (!range.add(value)) {
// Range ended, wrap it up
ranges.add(range);
range = new Range(value);
}
}
// Conclude last range
ranges.add(range);
return ranges;
}
In particular useful if you also add some getStart()
and getEnd()
method to the Range
class.
Notes
Note that the method will likely behave funky if the array contains duplicates. You did not specify what to do in that case, so I simply assumed duplicates will not exist for your use case.