1. 程式人生 > >[Google Guava] 8-區間

[Google Guava] 8-區間

原文連結 譯文連結 譯文:沈義揚

範例

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

簡介

區間,有時也稱為範圍,是特定域中的凸性(非正式說法為連續的或不中斷的)部分。在形式上,凸性表示對a<=b<=c, range.contains(a)且range.contains(c)意味著range.contains(b)。

區間可以延伸至無限——例如,範圍”x>3″包括任意大於3的值——也可以被限制為有限,如” 2<=x<5″。Guava用更緊湊的方法表示範圍,有數學背景的程式設計師對此是耳熟能詳的:

  • (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}
  • (-∞..+∞) = 所有值

上面的a、b稱為端點 。為了提高一致性,Guava中的Range要求上端點不能小於下端點。上下端點有可能是相等的,但要求區間是閉區間或半開半閉區間(至少有一個端點是包含在區間中的):

  • [a..a]:單元素區間
  • [a..a); (a..a]:空區間,但它們是有效的
  • (a..a):無效區間

Guava用型別Range<C>表示區間。所有區間實現都是不可變型別。

構建區間

區間例項可以由Range類的靜態方法獲取:

Range.closed("left", "right"); //字典序在"left"和"right"之間的字串,閉區間
Range.lessThan(4.0); //嚴格小於4.0的double值

此外,也可以明確地指定邊界型別來構造區間:

這裡的BoundType是一個列舉型別,包含CLOSED和OPEN兩個值。

Range.downTo(4, boundType);// (a..+∞)或[a..+∞),取決於boundType
Range.range(1, CLOSED, 4, OPEN);// [1..4),等同於Range.closedOpen(1, 4)

區間運算

Range的基本運算是它的contains(C) 方法,和你期望的一樣,它用來區間判斷是否包含某個值。此外,Range例項也可以當作Predicate,並且在函數語言程式設計中使用(譯者注:見第4章)。任何Range例項也都支援containsAll(Iterable<? extends C>)方法:

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

查詢運算

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

關係運算

包含[enclose]

區間之間的最基本關係就是包含[encloses(Range)]:如果內區間的邊界沒有超出外區間的邊界,則外區間包含內區間。包含判斷的結果完全取決於區間端點的比較!

  • [3..6] 包含[4..5] ;
  • (3..6) 包含(3..6) ;
  • [3..6] 包含[4..4),雖然後者是空區間;
  • (3..6]不 包含[3..6] ;
  • [4..5]不 包含(3..6),雖然前者包含了後者的所有值,離散域[discrete domains]可以解決這個問題(見8.5節);
  • [3..6]不 包含(1..1],雖然前者包含了後者的所有值。

包含是一種偏序關係[partial ordering]。基於包含關係的概念,Range還提供了以下運算方法。

相連[isConnected]

Range.isConnected(Range)判斷區間是否是相連的。具體來說,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

交集[intersection]

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

跨區間[span]

Range.span(Range)返回”同時包括兩個區間的最小區間”,如果兩個區間相連,那就是它們的並集。

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]

離散域

部分(但不是全部)可比較型別是離散的,即區間的上下邊界都是可列舉的。

在Guava中,用DiscreteDomain<C>實現型別C的離散形式操作。一個離散域總是代表某種型別值的全集;它不能代表類似”素數”、”長度為5的字串”或”午夜的時間戳”這樣的區域性域。

一旦獲取了DiscreteDomain例項,你就可以使用下面的Range運算方法:

  • ContiguousSet.create(range, domain):用ImmutableSortedSet<C>形式表示Range<C>中符合離散域定義的元素,並增加一些額外操作——譯者注:實際返回ImmutableSortedSet的子類ContiguousSet。(對無限區間不起作用,除非型別C本身是有限的,比如int就是可列舉的)
  • canonical(domain):把離散域轉為區間的”規範形式”。如果ContiguousSet.create(a, domain).equals(ContiguousSet.create(b, domain))並且!a.isEmpty(),則有a.canonical(domain).equals(b.canonical(domain))。(這並不意味著a.equals(b))
ImmutableSortedSet set = ContigousSet.create(Range.open(1, 5), iscreteDomain.integers());
//set包含[2, 3, 4]
ContiguousSet.create(Range.greaterThan(0), DiscreteDomain.integers());
//set包含[1, 2, ..., Integer.MAX_VALUE]

注意,ContiguousSet.create並沒有真的構造了整個集合,而是返回了set形式的區間檢視。

你自己的離散域

你可以建立自己的離散域,但必須記住DiscreteDomain契約的幾個重要方面。

  • 一個離散域總是代表某種型別值的全集;它不能代表類似”素數”或”長度為5的字串”這樣的區域性域。所以舉例來說,你無法構造一個DiscreteDomain以表示精確到秒的JODA DateTime日期集合:因為那將無法包含JODA DateTime的所有值。
  • DiscreteDomain可能是無限的——比如BigInteger DiscreteDomain。這種情況下,你應當用minValue()和maxValue()的預設實現,它們會丟擲NoSuchElementException。但Guava禁止把無限區間傳入ContiguousSet.create——譯者注:那明顯得不到一個可列舉的集合。

如果我需要一個Comparator呢?

我們想要在Range的可用性與API複雜性之間找到特定的平衡,這部分導致了我們沒有提供基於Comparator的介面:我們不需要操心區間是怎樣基於不同Comparator互動的;所有API簽名都是簡單明確的;這樣更好。

另一方面,如果你需要任意Comparator,可以按下列其中一項來做:

  • 使用通用的Predicate介面,而不是Range類。(Range實現了Predicate介面,因此可以用Predicates.compose(range, function)獲取Predicate例項)
  • 使用包裝類以定義期望的排序。

譯者注:實際上Range規定元素型別必須是Comparable,這已經滿足了大多數需求。如果需要自定義特殊的比較邏輯,可以用Predicates.compose(range, function)組合比較的function。