Google Guava與區間操作相關的類

Ranges:Guava強大的API,用於處理連續和離散的可Comparable比較類型的區間。

1.示例

List<Double> scores;
Iterable<Double> belowMedianScores = Iterables.filter(scores, Range.lessThan(median));
...
Range<Integer> validGrades = Range.closed(1, 12);
for(int grade : ContiguousSet.create(validGrades, DiscreteDomain.integers())) {
  ...
}

2.介紹

區間(有時稱爲間隔)是特定域的凸(非正式地稱爲"連續的"或"不間斷的")部分。從形式上說,凸度表示對於任何a <= b <= crange.contains(a) && range.contains(c)意味着range.contains(b)

區間可以“擴展到無窮大”——例如,區間x > 3包含任意大的值——或可以受到有限約束,例如2 <= x < 5。我們將使用更精簡的表示法,這是具有數學背景的程序員所熟悉的:

  • (a..b) = {x | a < x < b}
  • [a..b] = {x | a <= x <= b}
  • [a..b) = {x | a <= x < b}
  • (a..b] = {x | a < x <= b}
  • (a..+∞) = {x | x > a}
  • [a..+∞) = {x | x >= a}
  • (-∞..b) = {x | x < b}
  • (-∞..b] = {x | x <= b}
  • (-∞..+∞) = all values

上面使用的值a和b稱爲端點。爲了提高一致性,Guava的Range概念要求上端點不能小於下端點。僅當至少一個邊界閉合時,上下端點纔可能相等:

  • [a..a]: 單重區間
  • [a..a); (a..a]: 空,但有效
  • (a..a): 無效

Guava中區間的類型爲Range<C>。所有區間都是不可變的

3.構建區間

可以從Range上的靜態方法獲得區間:

區間類型 方法
(a..b) open(C, C)
[a..b] closed(C, C)
[a..b) closedOpen(C, C)
(a..b] openClosed(C, C)
(a..+∞) greaterThan(C)
[a..+∞) atLeast(C)
(-∞..b) lessThan(C)
(-∞..b] atMost(C)
(-∞..+∞) all()
Range.closed("left", "right"); // all strings lexographically between "left" and "right" inclusive
Range.lessThan(4.0); // double values strictly less than 4

此外,可以通過顯式傳遞邊界類型來構造Range實例:

區間類型 方法
兩端有界 range(C, BoundType, C, BoundType)
上限無界((a..+∞)[a..+∞)) downTo(C, BoundType)
下限無界((-∞..b)(-∞..b]) upTo(C, BoundType)

在這裏,BoundType是一個包含值CLOSEDOPEN的枚舉。

Range.downTo(4, boundType); // allows you to decide whether or not you want to include 4
Range.range(1, CLOSED, 4, OPEN); // another way of writing Range.closedOpen(1, 4)

4.操作

Range的基本操作是其contains(C)方法,其行爲與你預期的完全一樣。此外,Range可以用作Predicate謂語,並用於函數式語法。任何Range也支持containsAll(Iterable<? extends C>)

Range.closed(1, 3).contains(2); // returns true
Range.closed(1, 3).contains(4); // returns false
Range.lessThan(5).contains(5); // returns false
Range.closed(1, 4).containsAll(Ints.asList(1, 2, 3)); // returns true

4.1查詢操作

爲了查看區間的端點,Range公開了以下方法:

Range.closedOpen(4, 4).isEmpty(); // returns true
Range.openClosed(4, 4).isEmpty(); // returns true
Range.closed(4, 4).isEmpty(); // returns false
Range.open(4, 4).isEmpty(); // Range.open throws IllegalArgumentException

Range.closed(3, 10).lowerEndpoint(); // returns 3
Range.open(3, 10).lowerEndpoint(); // returns 3
Range.closed(3, 10).lowerBoundType(); // returns CLOSED
Range.open(3, 10).upperBoundType(); // returns OPEN

4.2運算操作

4.2.1encloses

區間上最基本的關係是encloses(Range),如果內部區間的邊界沒有超出外部區間的邊界之外,則爲true。這完全取決於端點之間的比較!

  • [3..6]包圍[4..5]
  • (3..6)包圍(3..6)
  • [3..6]包圍[4..4)(即使後者爲空)
  • (3..6]不包圍[3..6]
  • [4..5]不包圍(3..6)儘管它包含後一個區間包含的每個值,但使用離散域可以解決此問題(參見下文)
  • [3..6]不包圍(1..1]儘管它包含後一個區間包含的每個值

encloses部分排序

鑑於此,Range提供以下操作:

4.2.2isConnected

Range.isConnected(Range),測試這些區間是否連接。具體來說,isConnected測試是否有某個區間包圍這兩個區間,但這等同於數學定義,即區間的並集必須形成一個連接集(空區間的特殊情況除外)。

isConnected自反的對稱的 關係

Range.closed(3, 5).isConnected(Range.open(5, 10)); // returns true
Range.closed(0, 9).isConnected(Range.closed(3, 4)); // returns true
Range.closed(0, 5).isConnected(Range.closed(3, 9)); // returns true
Range.open(3, 5).isConnected(Range.open(5, 10)); // returns false
Range.closed(1, 5).isConnected(Range.closed(6, 10)); // returns false

4.2.3intersection

Range.intersection(Range)返回此區間和其他區間所包圍的最大區間(如果這些區間是連接的,則存在),或者如果不存在這樣的區間,則拋出IllegalArgumentException

交集是可交換的關聯的[運算] [二進制運算]。

Range.closed(3, 5).intersection(Range.open(5, 10)); // returns (5, 5]
Range.closed(0, 9).intersection(Range.closed(3, 4)); // returns [3, 4]
Range.closed(0, 5).intersection(Range.closed(3, 9)); // returns [3, 5]
Range.open(3, 5).intersection(Range.open(5, 10)); // throws IAE
Range.closed(1, 5).intersection(Range.closed(6, 10)); // throws IAE

4.2.4span

Range.span(Range)返回此區間和其他區間所包圍的最小區間。如果兩個區間都連接,則這是它們的並集。

span可交換的關聯的閉合的[運算] [二進制運算]。

Range.closed(3, 5).span(Range.open(5, 10)); // returns [3, 10)
Range.closed(0, 9).span(Range.closed(3, 4)); // returns [0, 9]
Range.closed(0, 5).span(Range.closed(3, 9)); // returns [0, 9]
Range.open(3, 5).span(Range.open(5, 10)); // returns (3, 10)
Range.closed(1, 5).span(Range.closed(6, 10)); // returns [1, 10]

5.離散域

有些類型(但不是全部可比較類型)是離散的,這意味着可以枚舉兩邊有界的區間。

在Guava中,DiscreteDomain<C>C類型實現了離散操作。離散域始終代表其類型的整個值集;它不能表示部分域,例如“整型素數”,“長度爲5的字符串”或“午夜的時間戳”。

DiscreteDomain類提供DiscreteDomain實例:

類型 離散域
Integer integers()
Long longs()

一旦有DiscreteDomain後,可以使用以下Range操作:

  • ContiguousSet.create(range, domain):以ImmutableSortedSet的形式查看Range<C>,並加入一些額外的操作。(除非類型本身是有界的,否則不適用於無界區間)
  • canonical(domain):以“規範形式”放置區間。如果ContiguousSet.create(a, domain).equals(ContiguousSet.create(b, domain))!a.isEmpty(),則a.canonical(domain).equals(b.canonical(domain))。(但是,這並意味着a.equals(b)
ImmutableSortedSet<Integer> set = ContiguousSet.create(Range.open(1, 5), DiscreteDomain.integers());
// set contains [2, 3, 4]

ContiguousSet.create(Range.greaterThan(0), DiscreteDomain.integers());
// set contains [1, 2, ..., Integer.MAX_VALUE]

注意,ContiguousSet.create實際上並不構造整個區間,而是將區間視圖作爲集合返回。

5.1你自己的離散域

你可以創建自己的DiscreteDomain對象,但是必須記住DiscreteDomain契約的幾個重要方面。

  • 離散域始終代表其類型的整個值集;它不能表示部分域,例如“整型素數”或“長度爲5的字符串”。例如,你不能構造一個DiscreteDomain來查看一個區間內的日期集,其JODA DateTime包含的時間不超過秒:因爲它不包含該類型的所有元素。
  • DiscreteDomain可以是無限的——例如,BigInteger DiscreteDomain。在這種情況下,應使用minValue()maxValue()的默認實現,它們會拋出NoSuchElementException。但是,這禁止你在無限區間內使用ContiguousSet.create方法!

6.如果需要Comparator怎麼辦?

我們希望在Range的能力與API複雜度之間取得非常明確的平衡,其中一部分涉及不提供基於Comparator的接口:我們不必擔心基於不同比較器的區間如何相互作用; API簽名都顯著地簡化;事情變的更好。

另一方面,如果你想使用任意Comparator,則可以執行以下操作之一:

  • 使用通用Predicate而不是Range。(由於Range實現了Predicate接口,因此可以使用Predicates.compose(range, function)來獲取Predicate。)
  • 在對象周圍使用包裝器類來定義所需的順序。

7.示例代碼

import com.google.common.base.Predicate;
import com.google.common.collect.*;
import com.google.common.testing.EqualsTester;
import junit.framework.TestCase;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;

import static com.google.common.collect.BoundType.CLOSED;
import static com.google.common.collect.BoundType.OPEN;
import static com.google.common.collect.DiscreteDomain.integers;
import static com.google.common.testing.SerializableTester.reserializeAndAssert;
import static java.util.Arrays.asList;

public class RangeTest extends TestCase {

    public void testOpen() {
        Range<Integer> range = Range.open(4, 8);
        checkContains(range);
        assertTrue(range.hasLowerBound());
        assertEquals(4, (int) range.lowerEndpoint());
        assertEquals(OPEN, range.lowerBoundType());
        assertTrue(range.hasUpperBound());
        assertEquals(8, (int) range.upperEndpoint());
        assertEquals(OPEN, range.upperBoundType());
        assertFalse(range.isEmpty());
        assertEquals("(4..8)", range.toString());
        reserializeAndAssert(range);
    }

    public void testOpen_invalid() {
        try {
            Range.open(4, 3);
            fail();
        } catch (IllegalArgumentException expected) {
        }
        try {
            Range.open(3, 3);
            fail();
        } catch (IllegalArgumentException expected) {
        }
    }

    public void testClosed() {
        Range<Integer> range = Range.closed(5, 7);
        checkContains(range);
        assertTrue(range.hasLowerBound());
        assertEquals(5, (int) range.lowerEndpoint());
        assertEquals(CLOSED, range.lowerBoundType());
        assertTrue(range.hasUpperBound());
        assertEquals(7, (int) range.upperEndpoint());
        assertEquals(CLOSED, range.upperBoundType());
        assertFalse(range.isEmpty());
        assertEquals("[5..7]", range.toString());
        reserializeAndAssert(range);
    }

    public void testClosed_invalid() {
        try {
            Range.closed(4, 3);
            fail();
        } catch (IllegalArgumentException expected) {
        }
    }

    public void testOpenClosed() {
        Range<Integer> range = Range.openClosed(4, 7);
        checkContains(range);
        assertTrue(range.hasLowerBound());
        assertEquals(4, (int) range.lowerEndpoint());
        assertEquals(OPEN, range.lowerBoundType());
        assertTrue(range.hasUpperBound());
        assertEquals(7, (int) range.upperEndpoint());
        assertEquals(CLOSED, range.upperBoundType());
        assertFalse(range.isEmpty());
        assertEquals("(4..7]", range.toString());
        reserializeAndAssert(range);
    }

    public void testClosedOpen() {
        Range<Integer> range = Range.closedOpen(5, 8);
        checkContains(range);
        assertTrue(range.hasLowerBound());
        assertEquals(5, (int) range.lowerEndpoint());
        assertEquals(CLOSED, range.lowerBoundType());
        assertTrue(range.hasUpperBound());
        assertEquals(8, (int) range.upperEndpoint());
        assertEquals(OPEN, range.upperBoundType());
        assertFalse(range.isEmpty());
        assertEquals("[5..8)", range.toString());
        reserializeAndAssert(range);
    }

    public void testIsConnected() {
        assertTrue(Range.closed(3, 5).isConnected(Range.open(5, 6)));
        assertTrue(Range.closed(3, 5).isConnected(Range.openClosed(5, 5)));
        assertTrue(Range.open(3, 5).isConnected(Range.closed(5, 6)));
        assertTrue(Range.closed(3, 7).isConnected(Range.open(6, 8)));
        assertTrue(Range.open(3, 7).isConnected(Range.closed(5, 6)));
        assertFalse(Range.closed(3, 5).isConnected(Range.closed(7, 8)));
        assertFalse(Range.closed(3, 5).isConnected(Range.closedOpen(7, 7)));
    }

    private static void checkContains(Range<Integer> range) {
        assertFalse(range.contains(4));
        assertTrue(range.contains(5));
        assertTrue(range.contains(7));
        assertFalse(range.contains(8));
    }

    public void testSingleton() {
        Range<Integer> range = Range.closed(4, 4);
        assertFalse(range.contains(3));
        assertTrue(range.contains(4));
        assertFalse(range.contains(5));
        assertTrue(range.hasLowerBound());
        assertEquals(4, (int) range.lowerEndpoint());
        assertEquals(CLOSED, range.lowerBoundType());
        assertTrue(range.hasUpperBound());
        assertEquals(4, (int) range.upperEndpoint());
        assertEquals(CLOSED, range.upperBoundType());
        assertFalse(range.isEmpty());
        assertEquals("[4..4]", range.toString());
        reserializeAndAssert(range);
    }

    public void testEmpty1() {
        Range<Integer> range = Range.closedOpen(4, 4);
        assertFalse(range.contains(3));
        assertFalse(range.contains(4));
        assertFalse(range.contains(5));
        assertTrue(range.hasLowerBound());
        assertEquals(4, (int) range.lowerEndpoint());
        assertEquals(CLOSED, range.lowerBoundType());
        assertTrue(range.hasUpperBound());
        assertEquals(4, (int) range.upperEndpoint());
        assertEquals(OPEN, range.upperBoundType());
        assertTrue(range.isEmpty());
        assertEquals("[4..4)", range.toString());
        reserializeAndAssert(range);
    }

    public void testEmpty2() {
        Range<Integer> range = Range.openClosed(4, 4);
        assertFalse(range.contains(3));
        assertFalse(range.contains(4));
        assertFalse(range.contains(5));
        assertTrue(range.hasLowerBound());
        assertEquals(4, (int) range.lowerEndpoint());
        assertEquals(OPEN, range.lowerBoundType());
        assertTrue(range.hasUpperBound());
        assertEquals(4, (int) range.upperEndpoint());
        assertEquals(CLOSED, range.upperBoundType());
        assertTrue(range.isEmpty());
        assertEquals("(4..4]", range.toString());
        reserializeAndAssert(range);
    }

    public void testLessThan() {
        Range<Integer> range = Range.lessThan(5);
        assertTrue(range.contains(Integer.MIN_VALUE));
        assertTrue(range.contains(4));
        assertFalse(range.contains(5));
        assertUnboundedBelow(range);
        assertTrue(range.hasUpperBound());
        assertEquals(5, (int) range.upperEndpoint());
        assertEquals(OPEN, range.upperBoundType());
        assertFalse(range.isEmpty());
        assertEquals("(-\u221e..5)", range.toString());
        reserializeAndAssert(range);
    }

    public void testGreaterThan() {
        Range<Integer> range = Range.greaterThan(5);
        assertFalse(range.contains(5));
        assertTrue(range.contains(6));
        assertTrue(range.contains(Integer.MAX_VALUE));
        assertTrue(range.hasLowerBound());
        assertEquals(5, (int) range.lowerEndpoint());
        assertEquals(OPEN, range.lowerBoundType());
        assertUnboundedAbove(range);
        assertFalse(range.isEmpty());
        assertEquals("(5..+\u221e)", range.toString());
        reserializeAndAssert(range);
    }

    public void testAtLeast() {
        Range<Integer> range = Range.atLeast(6);
        assertFalse(range.contains(5));
        assertTrue(range.contains(6));
        assertTrue(range.contains(Integer.MAX_VALUE));
        assertTrue(range.hasLowerBound());
        assertEquals(6, (int) range.lowerEndpoint());
        assertEquals(CLOSED, range.lowerBoundType());
        assertUnboundedAbove(range);
        assertFalse(range.isEmpty());
        assertEquals("[6..+\u221e)", range.toString());
        reserializeAndAssert(range);
    }

    public void testAtMost() {
        Range<Integer> range = Range.atMost(4);
        assertTrue(range.contains(Integer.MIN_VALUE));
        assertTrue(range.contains(4));
        assertFalse(range.contains(5));
        assertUnboundedBelow(range);
        assertTrue(range.hasUpperBound());
        assertEquals(4, (int) range.upperEndpoint());
        assertEquals(CLOSED, range.upperBoundType());
        assertFalse(range.isEmpty());
        assertEquals("(-\u221e..4]", range.toString());
        reserializeAndAssert(range);
    }

    public void testAll() {
        Range<Integer> range = Range.all();
        assertTrue(range.contains(Integer.MIN_VALUE));
        assertTrue(range.contains(Integer.MAX_VALUE));
        assertUnboundedBelow(range);
        assertUnboundedAbove(range);
        assertFalse(range.isEmpty());
        assertEquals("(-\u221e..+\u221e)", range.toString());
        assertSame(range, reserializeAndAssert(range));
        assertSame(range, Range.all());
    }

    private static void assertUnboundedBelow(Range<Integer> range) {
        assertFalse(range.hasLowerBound());
        try {
            range.lowerEndpoint();
            fail();
        } catch (IllegalStateException expected) {
        }
        try {
            range.lowerBoundType();
            fail();
        } catch (IllegalStateException expected) {
        }
    }

    private static void assertUnboundedAbove(Range<Integer> range) {
        assertFalse(range.hasUpperBound());
        try {
            range.upperEndpoint();
            fail();
        } catch (IllegalStateException expected) {
        }
        try {
            range.upperBoundType();
            fail();
        } catch (IllegalStateException expected) {
        }
    }

    public void testContainsAll() {
        Range<Integer> range = Range.closed(3, 5);
        assertTrue(range.containsAll(asList(3, 3, 4, 5)));
        assertFalse(range.containsAll(asList(3, 3, 4, 5, 6)));

        // We happen to know that natural-order sorted sets use a different code
        // path, so we test that separately
        assertTrue(range.containsAll(ImmutableSortedSet.of(3, 3, 4, 5)));
        assertTrue(range.containsAll(ImmutableSortedSet.of(3)));
        assertTrue(range.containsAll(ImmutableSortedSet.<Integer>of()));
        assertFalse(range.containsAll(ImmutableSortedSet.of(3, 3, 4, 5, 6)));

        assertTrue(Range.openClosed(3, 3).containsAll(Collections.<Integer>emptySet()));
    }

    public void testEncloses_open() {
        Range<Integer> range = Range.open(2, 5);
        assertTrue(range.encloses(range));
        assertTrue(range.encloses(Range.open(2, 4)));
        assertTrue(range.encloses(Range.open(3, 5)));
        assertTrue(range.encloses(Range.closed(3, 4)));

        assertFalse(range.encloses(Range.openClosed(2, 5)));
        assertFalse(range.encloses(Range.closedOpen(2, 5)));
        assertFalse(range.encloses(Range.closed(1, 4)));
        assertFalse(range.encloses(Range.closed(3, 6)));
        assertFalse(range.encloses(Range.greaterThan(3)));
        assertFalse(range.encloses(Range.lessThan(3)));
        assertFalse(range.encloses(Range.atLeast(3)));
        assertFalse(range.encloses(Range.atMost(3)));
        assertFalse(range.encloses(Range.<Integer>all()));
    }

    public void testEncloses_closed() {
        Range<Integer> range = Range.closed(2, 5);
        assertTrue(range.encloses(range));
        assertTrue(range.encloses(Range.open(2, 5)));
        assertTrue(range.encloses(Range.openClosed(2, 5)));
        assertTrue(range.encloses(Range.closedOpen(2, 5)));
        assertTrue(range.encloses(Range.closed(3, 5)));
        assertTrue(range.encloses(Range.closed(2, 4)));

        assertFalse(range.encloses(Range.open(1, 6)));
        assertFalse(range.encloses(Range.greaterThan(3)));
        assertFalse(range.encloses(Range.lessThan(3)));
        assertFalse(range.encloses(Range.atLeast(3)));
        assertFalse(range.encloses(Range.atMost(3)));
        assertFalse(range.encloses(Range.<Integer>all()));
    }

    public void testIntersection_empty() {
        Range<Integer> range = Range.closedOpen(3, 3);
        assertEquals(range, range.intersection(range));

        try {
            range.intersection(Range.open(3, 5));
            fail();
        } catch (IllegalArgumentException expected) {
        }
        try {
            range.intersection(Range.closed(0, 2));
            fail();
        } catch (IllegalArgumentException expected) {
        }
    }

    public void testIntersection_deFactoEmpty() {
        Range<Integer> range = Range.open(3, 4);
        assertEquals(range, range.intersection(range));

        assertEquals(Range.openClosed(3, 3), range.intersection(Range.atMost(3)));
        assertEquals(Range.closedOpen(4, 4), range.intersection(Range.atLeast(4)));

        try {
            range.intersection(Range.lessThan(3));
            fail();
        } catch (IllegalArgumentException expected) {
        }
        try {
            range.intersection(Range.greaterThan(4));
            fail();
        } catch (IllegalArgumentException expected) {
        }

        range = Range.closed(3, 4);
        assertEquals(Range.openClosed(4, 4), range.intersection(Range.greaterThan(4)));
    }

    public void testIntersection_singleton() {
        Range<Integer> range = Range.closed(3, 3);
        assertEquals(range, range.intersection(range));

        assertEquals(range, range.intersection(Range.atMost(4)));
        assertEquals(range, range.intersection(Range.atMost(3)));
        assertEquals(range, range.intersection(Range.atLeast(3)));
        assertEquals(range, range.intersection(Range.atLeast(2)));

        assertEquals(Range.closedOpen(3, 3), range.intersection(Range.lessThan(3)));
        assertEquals(Range.openClosed(3, 3), range.intersection(Range.greaterThan(3)));

        try {
            range.intersection(Range.atLeast(4));
            fail();
        } catch (IllegalArgumentException expected) {
        }
        try {
            range.intersection(Range.atMost(2));
            fail();
        } catch (IllegalArgumentException expected) {
        }
    }

    public void testIntersection_general() {
        Range<Integer> range = Range.closed(4, 8);

        // separate below
        try {
            range.intersection(Range.closed(0, 2));
            fail();
        } catch (IllegalArgumentException expected) {
        }

        // adjacent below
        assertEquals(Range.closedOpen(4, 4), range.intersection(Range.closedOpen(2, 4)));

        // overlap below
        assertEquals(Range.closed(4, 6), range.intersection(Range.closed(2, 6)));

        // enclosed with same start
        assertEquals(Range.closed(4, 6), range.intersection(Range.closed(4, 6)));

        // enclosed, interior
        assertEquals(Range.closed(5, 7), range.intersection(Range.closed(5, 7)));

        // enclosed with same end
        assertEquals(Range.closed(6, 8), range.intersection(Range.closed(6, 8)));

        // equal
        assertEquals(range, range.intersection(range));

        // enclosing with same start
        assertEquals(range, range.intersection(Range.closed(4, 10)));

        // enclosing with same end
        assertEquals(range, range.intersection(Range.closed(2, 8)));

        // enclosing, exterior
        assertEquals(range, range.intersection(Range.closed(2, 10)));

        // overlap above
        assertEquals(Range.closed(6, 8), range.intersection(Range.closed(6, 10)));

        // adjacent above
        assertEquals(Range.openClosed(8, 8), range.intersection(Range.openClosed(8, 10)));

        // separate above
        try {
            range.intersection(Range.closed(10, 12));
            fail();
        } catch (IllegalArgumentException expected) {
        }
    }

    public void testGap_overlapping() {
        Range<Integer> range = Range.closedOpen(3, 5);

        try {
            range.gap(Range.closed(4, 6));
            fail();
        } catch (IllegalArgumentException expected) {
        }
        try {
            range.gap(Range.closed(2, 4));
            fail();
        } catch (IllegalArgumentException expected) {
        }
        try {
            range.gap(Range.closed(2, 3));
            fail();
        } catch (IllegalArgumentException expected) {
        }
    }

    public void testGap_connectedAdjacentYieldsEmpty() {
        Range<Integer> range = Range.open(3, 4);

        assertEquals(Range.closedOpen(4, 4), range.gap(Range.atLeast(4)));
        assertEquals(Range.openClosed(3, 3), range.gap(Range.atMost(3)));
    }

    public void testGap_general() {
        Range<Integer> openRange = Range.open(4, 8);
        Range<Integer> closedRange = Range.closed(4, 8);

        // first range open end, second range open start
        assertEquals(Range.closed(2, 4), Range.lessThan(2).gap(openRange));
        assertEquals(Range.closed(2, 4), openRange.gap(Range.lessThan(2)));

        // first range closed end, second range open start
        assertEquals(Range.openClosed(2, 4), Range.atMost(2).gap(openRange));
        assertEquals(Range.openClosed(2, 4), openRange.gap(Range.atMost(2)));

        // first range open end, second range closed start
        assertEquals(Range.closedOpen(2, 4), Range.lessThan(2).gap(closedRange));
        assertEquals(Range.closedOpen(2, 4), closedRange.gap(Range.lessThan(2)));

        // first range closed end, second range closed start
        assertEquals(Range.open(2, 4), Range.atMost(2).gap(closedRange));
        assertEquals(Range.open(2, 4), closedRange.gap(Range.atMost(2)));
    }

    public void testSpan_general() {
        Range<Integer> range = Range.closed(4, 8);

        // separate below
        assertEquals(Range.closed(0, 8), range.span(Range.closed(0, 2)));
        assertEquals(Range.atMost(8), range.span(Range.atMost(2)));

        // adjacent below
        assertEquals(Range.closed(2, 8), range.span(Range.closedOpen(2, 4)));
        assertEquals(Range.atMost(8), range.span(Range.lessThan(4)));

        // overlap below
        assertEquals(Range.closed(2, 8), range.span(Range.closed(2, 6)));
        assertEquals(Range.atMost(8), range.span(Range.atMost(6)));

        // enclosed with same start
        assertEquals(range, range.span(Range.closed(4, 6)));

        // enclosed, interior
        assertEquals(range, range.span(Range.closed(5, 7)));

        // enclosed with same end
        assertEquals(range, range.span(Range.closed(6, 8)));

        // equal
        assertEquals(range, range.span(range));

        // enclosing with same start
        assertEquals(Range.closed(4, 10), range.span(Range.closed(4, 10)));
        assertEquals(Range.atLeast(4), range.span(Range.atLeast(4)));

        // enclosing with same end
        assertEquals(Range.closed(2, 8), range.span(Range.closed(2, 8)));
        assertEquals(Range.atMost(8), range.span(Range.atMost(8)));

        // enclosing, exterior
        assertEquals(Range.closed(2, 10), range.span(Range.closed(2, 10)));
        assertEquals(Range.<Integer>all(), range.span(Range.<Integer>all()));

        // overlap above
        assertEquals(Range.closed(4, 10), range.span(Range.closed(6, 10)));
        assertEquals(Range.atLeast(4), range.span(Range.atLeast(6)));

        // adjacent above
        assertEquals(Range.closed(4, 10), range.span(Range.openClosed(8, 10)));
        assertEquals(Range.atLeast(4), range.span(Range.greaterThan(8)));

        // separate above
        assertEquals(Range.closed(4, 12), range.span(Range.closed(10, 12)));
        assertEquals(Range.atLeast(4), range.span(Range.atLeast(10)));
    }

    public void testApply() {
        Predicate<Integer> predicate = Range.closed(2, 3);
        assertFalse(predicate.apply(1));
        assertTrue(predicate.apply(2));
        assertTrue(predicate.apply(3));
        assertFalse(predicate.apply(4));
    }

    public void testEquals() {
        new EqualsTester()
                .addEqualityGroup(Range.open(1, 5), Range.range(1, OPEN, 5, OPEN))
                .addEqualityGroup(Range.greaterThan(2), Range.greaterThan(2))
                .addEqualityGroup(Range.all(), Range.all())
                .addEqualityGroup("Phil")
                .testEquals();
    }

    static final DiscreteDomain<Integer> UNBOUNDED_DOMAIN =
            new DiscreteDomain<Integer>() {
                @Override
                public Integer next(Integer value) {
                    return integers().next(value);
                }

                @Override
                public Integer previous(Integer value) {
                    return integers().previous(value);
                }

                @Override
                public long distance(Integer start, Integer end) {
                    return integers().distance(start, end);
                }
            };

    public void testCanonical() {
        assertEquals(Range.closedOpen(1, 5), Range.closed(1, 4).canonical(integers()));
        assertEquals(Range.closedOpen(1, 5), Range.open(0, 5).canonical(integers()));
        assertEquals(Range.closedOpen(1, 5), Range.closedOpen(1, 5).canonical(integers()));
        assertEquals(Range.closedOpen(1, 5), Range.openClosed(0, 4).canonical(integers()));

        assertEquals(
                Range.closedOpen(Integer.MIN_VALUE, 0),
                Range.closedOpen(Integer.MIN_VALUE, 0).canonical(integers()));

        assertEquals(Range.closedOpen(Integer.MIN_VALUE, 0), Range.lessThan(0).canonical(integers()));
        assertEquals(Range.closedOpen(Integer.MIN_VALUE, 1), Range.atMost(0).canonical(integers()));
        assertEquals(Range.atLeast(0), Range.atLeast(0).canonical(integers()));
        assertEquals(Range.atLeast(1), Range.greaterThan(0).canonical(integers()));

        assertEquals(Range.atLeast(Integer.MIN_VALUE), Range.<Integer>all().canonical(integers()));
    }

    public void testCanonical_unboundedDomain() {
        assertEquals(Range.lessThan(0), Range.lessThan(0).canonical(UNBOUNDED_DOMAIN));
        assertEquals(Range.lessThan(1), Range.atMost(0).canonical(UNBOUNDED_DOMAIN));
        assertEquals(Range.atLeast(0), Range.atLeast(0).canonical(UNBOUNDED_DOMAIN));
        assertEquals(Range.atLeast(1), Range.greaterThan(0).canonical(UNBOUNDED_DOMAIN));

        assertEquals(Range.all(), Range.<Integer>all().canonical(UNBOUNDED_DOMAIN));
    }

    public void testEncloseAll() {
        assertEquals(Range.closed(0, 0), Range.encloseAll(Arrays.asList(0)));
        assertEquals(Range.closed(-3, 5), Range.encloseAll(Arrays.asList(5, -3)));
        assertEquals(Range.closed(-3, 5), Range.encloseAll(Arrays.asList(1, 2, 2, 2, 5, -3, 0, -1)));
    }

    public void testEncloseAll_empty() {
        try {
            Range.encloseAll(ImmutableSet.<Integer>of());
            fail();
        } catch (NoSuchElementException expected) {
        }
    }

    public void testEncloseAll_nullValue() {
        List<Integer> nullFirst = Lists.newArrayList(null, 0);
        try {
            Range.encloseAll(nullFirst);
            fail();
        } catch (NullPointerException expected) {
        }
        List<Integer> nullNotFirst = Lists.newArrayList(0, null);
        try {
            Range.encloseAll(nullNotFirst);
            fail();
        } catch (NullPointerException expected) {
        }
    }

    public void testEquivalentFactories() {
        new EqualsTester()
                .addEqualityGroup(Range.all())
                .addEqualityGroup(Range.atLeast(1), Range.downTo(1, CLOSED))
                .addEqualityGroup(Range.greaterThan(1), Range.downTo(1, OPEN))
                .addEqualityGroup(Range.atMost(7), Range.upTo(7, CLOSED))
                .addEqualityGroup(Range.lessThan(7), Range.upTo(7, OPEN))
                .addEqualityGroup(Range.open(1, 7), Range.range(1, OPEN, 7, OPEN))
                .addEqualityGroup(Range.openClosed(1, 7), Range.range(1, OPEN, 7, CLOSED))
                .addEqualityGroup(Range.closed(1, 7), Range.range(1, CLOSED, 7, CLOSED))
                .addEqualityGroup(Range.closedOpen(1, 7), Range.range(1, CLOSED, 7, OPEN))
                .testEquals();
    }
}

本文參考:
RangesExplained
RangeTest

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章