Transform a Guava Range to a Different Type

Issue

How do I transform a given Guava Range of one type to a range of another type.

I expect a compose method similar to Predicates.compose. Take Integer and Long for example:

Range<Integer> intRange = Range.compose(Range.atLeast(10l),integerFromLongFunction);

I wrote the compose method:

    public static <F extends Comparable<F>, T extends Comparable<T>> Range<T> compose(Range<F> range,
        Function<F, T> function) {
    Range<T> result;
    if (range.hasUpperBound() && range.hasLowerBound()) {
        T upperEndpoint = function.apply(range.upperEndpoint());
        T lowerEndpoint = function.apply(range.lowerEndpoint());
        result = Range.range(lowerEndpoint, range.lowerBoundType(), upperEndpoint, range.upperBoundType());
    } else if (range.hasUpperBound()) {
        result = Range.upTo(function.apply(range.upperEndpoint()), range.upperBoundType());
    } else if (range.hasLowerBound()) {
        result = Range.downTo(function.apply(range.lowerEndpoint()), range.lowerBoundType());
    } else {
        result = Range.all();
    }
    return result;
}

with the Unit Test:

@Test
public void testLongRangeToInteger() {
    Integer inRange = 6;
    Integer outOfRange = 3;
    Range<Long> longRange = Range.atLeast(5l);
    assertTrue(longRange.apply(inRange.longValue()));
    assertFalse(longRange.apply(outOfRange.longValue()));

    Function<Long, Integer> function = integerFromLongFunction();
    Range<Integer> intRange = RangeExtended.compose(longRange, function);
    assertTrue(intRange.apply(inRange));
    assertFalse(intRange.apply(outOfRange));
}

public static Function<Long, Integer> integerFromLongFunction() {
    return new Function<Long, Integer>() {

        @Override
        public Integer apply(Long input) {
            return (input == null) ? null : input.intValue();
        }
    };
}

My current desire is actually to convert a Joda Duration to it’s corresponding millis, but I wrote the example in Long/Integer for simplicity.

It seems that Guava would have this, but I can’t find anywhere. I’m using v14, but looking at the latest v17 javadoc didn’t expose anything.

Solution


package com.stackoverflow.q24889243;

import static org.junit.jupiter.api.Assertions.*;

import java.time.Duration;
import java.util.List;
import java.util.function.Function;

import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import org.joda.time.ReadableDuration;
import org.junit.jupiter.api.Test;

public class Answer {

    /**
     * Transforms one range to another
     *
     * @param <T>         the from type
     * @param <R>         the to type
     * @param fromRange   the from range
     * @param transformer the transformer
     *
     * @return the range
     */
    public static final <T extends Comparable<T>, R extends Comparable<R>> Range<R> transform(Range<T> fromRange,
            Function<? super T, ? extends R> transformer) {

        var hasLowerBound = fromRange.hasLowerBound();
        var hasUpperBound = fromRange.hasUpperBound();

        if (hasLowerBound && hasUpperBound) {

            var fromLowerEndpoint = fromRange.lowerEndpoint();
            var toLowerEndpoint = transformer.apply(fromLowerEndpoint);

            var fromUpperEndpoint = fromRange.upperEndpoint();
            var toUpperEndpoint = transformer.apply(fromUpperEndpoint);

            return Range.range(toLowerEndpoint, fromRange.lowerBoundType(), toUpperEndpoint,
                fromRange.upperBoundType());
        }

        if (hasLowerBound) {

            var fromLowerEndpoint = fromRange.lowerEndpoint();
            var toLowerEndpoint = transformer.apply(fromLowerEndpoint);

            return Range.downTo(toLowerEndpoint, fromRange.lowerBoundType());
        }

        if (hasUpperBound) {

            var fromUpperEndpoint = fromRange.upperEndpoint();
            var toUpperEndpoint = transformer.apply(fromUpperEndpoint);

            return Range.upTo(toUpperEndpoint, fromRange.upperBoundType());
        }

        return Range.all();
    }


    @Test
    void testDurations() {
        assertRangeTransformer(Duration::toMillis, Duration.ofMillis(10), Duration.ofMillis(15));

        assertRangeTransformer(ReadableDuration::getMillis, org.joda.time.Duration.millis(10),
            org.joda.time.Duration.millis(15));
    }

    <T extends Comparable<T>, R extends Comparable<R>> void assertRangeTransformer(
            Function<? super T, ? extends R> transformer, T fromLowerBound, T fromUpperBound) {

        assertRangeTransformer(transformer, fromLowerBound, BoundType.CLOSED, fromUpperBound, BoundType.CLOSED);
        assertRangeTransformer(transformer, fromLowerBound, BoundType.CLOSED, fromUpperBound, BoundType.OPEN);
        assertRangeTransformer(transformer, fromLowerBound, BoundType.OPEN, fromUpperBound, BoundType.CLOSED);
        assertRangeTransformer(transformer, fromLowerBound, BoundType.OPEN, fromUpperBound, BoundType.OPEN);

        assertRangeTransformerLowerBoundOnly(transformer, fromLowerBound, BoundType.CLOSED);
        assertRangeTransformerLowerBoundOnly(transformer, fromLowerBound, BoundType.OPEN);

        assertRangeTransformerUpperBoundOnly(transformer, fromUpperBound, BoundType.CLOSED);
        assertRangeTransformerUpperBoundOnly(transformer, fromUpperBound, BoundType.OPEN);

        this.<T, R>assertRangeTransformerAll(transformer);
    }

    <T extends Comparable<T>, R extends Comparable<R>> void assertRangeTransformer(
            Function<? super T, ? extends R> transformer, T fromLowerBound, BoundType lowerBoundType, T fromUpperBound,
            BoundType upperBoundType) {

        var toLowerBound = transformer.apply(fromLowerBound);
        var toUpperBound = transformer.apply(fromUpperBound);

        var fromRange = Range.range(fromLowerBound, lowerBoundType, fromUpperBound, upperBoundType);
        var actualToRange = transform(fromRange, transformer);
        assertEquals(toLowerBound, actualToRange.lowerEndpoint());
        assertEquals(fromRange.lowerBoundType(), actualToRange.lowerBoundType());
        assertEquals(toUpperBound, actualToRange.upperEndpoint());
        assertEquals(fromRange.upperBoundType(), actualToRange.upperBoundType());
    }

    <T extends Comparable<T>, R extends Comparable<R>> void assertRangeTransformerLowerBoundOnly(
            Function<? super T, ? extends R> transformer, T fromLowerBound, BoundType lowerBoundType) {

        var toLowerBound = transformer.apply(fromLowerBound);
        var fromRange = Range.downTo(fromLowerBound, lowerBoundType);

        var actualToRange = transform(fromRange, transformer);
        assertFalse(actualToRange.hasUpperBound());
        assertEquals(toLowerBound, actualToRange.lowerEndpoint());
        assertEquals(fromRange.lowerBoundType(), actualToRange.lowerBoundType());
    }

    <T extends Comparable<T>, R extends Comparable<R>> void assertRangeTransformerUpperBoundOnly(
            Function<? super T, ? extends R> transformer, T fromUpperBound, BoundType upperBoundType) {

        var toLowerBound = transformer.apply(fromUpperBound);
        var fromRange = Range.upTo(fromUpperBound, upperBoundType);

        var actualToRange = transform(fromRange, transformer);
        assertFalse(actualToRange.hasLowerBound());
        assertEquals(toLowerBound, actualToRange.upperEndpoint());
        assertEquals(fromRange.upperBoundType(), actualToRange.upperBoundType());

    }

    <T extends Comparable<T>, R extends Comparable<R>> void assertRangeTransformerAll(
            Function<? super T, ? extends R> transformer) {

        var fromRange = Range.<T>all();

        var actualToRange = transform(fromRange, transformer);
        assertFalse(actualToRange.hasLowerBound());
        assertFalse(actualToRange.hasUpperBound());

    }
}

Answered By – Jeff

Answer Checked By – Mary Flores (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.