Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing Interval Notation to Guava Range

I'm needing to parse a string containing standard interval notation (i.e. (8,100), [6,10), and so forth) into a Guava Range object. How would I go about doing that in Java? Is there a utility package that would parse the string into the components I would need to construct a Guava Range object?

like image 914
Dave221 Avatar asked May 28 '26 08:05

Dave221


1 Answers

If we look at the pattern, the interval either starts with a '[' or a '(', then it is followed by at least one digit, followed by a comma, again one or more digit and finished by either ']' or ')'.

So the regular expression will look like this :

^[\\(|\\[](\\d+),(\\d+)[\\)|\\]]$

Here it is decomposed :

^
 [\\(|\\[] -> start either with `'['` or `'('` (we need to escape the special characters with `\\`)
 (\\d+) -> followed by one or more digit that we capture in a group
 , -> followed by a comma
 (\\d+) -> followed again by one or more digit that we capture in another group
 [\\)|\\]] -> and that finishes either with `']'` or `')'`
$

^ and $ assert that the all string matched the expression and not only a part of it.

So we have the regex, yay!

Now we need to create a Pattern instance from it, so that will be able to fetch a matcher from. Finally we check if the string matches the pattern and we grab the corresponding groups

Pattern p = Pattern.compile("^[\\(|\\[](\\d+),(\\d+)[\\)|\\]]$");
Matcher m = p.matcher("(0,100)");

if(matcher.matches()) {
    int lowerBound = Integer.parseInt(matcher.group(1));
    int upperBound = Integer.parseInt(matcher.group(2));
    System.out.println(lowerBound + "_" + upperBound);
}

The following outputs 0_100.

Now the final step, get the first and last character and create the appropriate range from it; putting it all together:

class RangeFactory {

    private static final Pattern p = Pattern.compile("^[\\(|\\[](\\d+),(\\d+)[\\)|\\]]$");

    public static Range from(String range) {
        Matcher m = p.matcher(range);
        if(m.matches()) {
            int length = range.length();

            int lowerBound = Integer.parseInt(m.group(1));
            int upperBound = Integer.parseInt(m.group(2));

            if(range.charAt(0) == '(') {
                if(range.charAt(length - 1) == ')') {
                    return Range.open(lowerBound, upperBound);
                }
                return Range.openClosed(lowerBound, upperBound);
            } else {
                if(range.charAt(length - 1) == ')') {
                    return Range.closedOpen(lowerBound, upperBound);
                }
                return Range.closed(lowerBound, upperBound);
            }
        }
        throw new IllegalArgumentException("Range " + range + " is not valid.");
    }
}

Here's some test cases :

List<String> ranges =
    Arrays.asList("(0,100)", "[0,100]", "[0,100)", "(0,100]", "", "()", "(0,100", "[,100]", "[100]");

for(String range : ranges) {
    try {
        System.out.println(RangeFactory.from(range));
    } catch (IllegalArgumentException ex) {
        System.out.println(ex);
    }
}

which outputs:

(0‥100)
[0‥100]
[0‥100)
(0‥100]
java.lang.IllegalArgumentException: Range  is not valid.
java.lang.IllegalArgumentException: Range () is not valid.
java.lang.IllegalArgumentException: Range (0,100 is not valid.
java.lang.IllegalArgumentException: Range [,100] is not valid.
java.lang.IllegalArgumentException: Range [100] is not valid.

You can ameliorate the regex (to accept ranges with infinite bounds, etc.), but it should give you a good starting point.

Hope it helps! :)

like image 93
Alexis C. Avatar answered May 31 '26 06:05

Alexis C.