倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

Groovy in Addiction (5) : 範囲 Range

今回は範囲 (groovy.lang.Range) について。 『Groovyイン・アクション』Chapter4 を参考にしています(一覧)。

Groovyイン・アクション

Groovyイン・アクション

インスタンス生成


int の範囲

範囲で一番使用するのは「int の範囲」でしょう。

//***** int の範囲 *****
def range1 = 1..5;
assert range1 == [1, 2, 3, 4, 5];

def range2 = 1..<5;
assert range2 == [1, 2, 3, 4];

def egnar1 = 5..1;
assert egnar1 == [5, 4, 3, 2, 1];

def egnar2 = 5..<1;
assert egnar2 == [5, 4, 3, 2];

def range3 = new IntRange(1, 5);
assert range3 == [1, 2, 3, 4, 5];
  • 範囲は全て groovy.lang.Range オブジェクトですが、Range インターフェースは java.util.List を拡張しているので、Range はすべて List です。 上記サンプルでは、それを利用して等値評価をしています。
  • x..<y」は「x..(y.previous())」みたいなものでしょうか。
  • 「<..」や「<..<」、「..>」はありません。
  • 範囲指定の際に大小関係を逆順に定義することが出来ます(5..1 とか)。 

その他の型の Range

2つの条件を満たすクラス

  • next(), previous() メソッド(つまり ++, -- 演算子)が実装されている
  • java.lang.Comparable を実装している

であれば、int 以外でも範囲を作ることが出来ます。

//***** BigDecimal の範囲 *****
def decimalRange = 1.0..5.0;
assert decimalRange == [1.0, 2.0, 3.0, 4.0, 5.0];

//***** 文字列の範囲 *****
def stringRange1 = 'a'..'c';
assert stringRange1 == ['a', 'b', 'c'];

def stringRange2 = 'aa'..'ac';
assert stringRange2 == ['aa', 'ab', 'ac'];

//***** 日付の範囲 *****
def today = new Date();
def yesterday = today - 1;
def tomorrow = today + 1;
def dateRange = yesterday..tomorrow;
assert dateRange == [yesterday, today, tomorrow];

List として見たときにどういう要素が含まれるかは、 next(), previous() の実装が決めます。

基本メソッド


Range に定義されている基本メソッドの振る舞いを見ていきます:

  • isReverse()
  • getFrom()
  • getTo()
  • size()
  • contains() -- List としての包含関係
  • containsWithBounds() -- Range としての包含関係
//***** int の範囲 *****
def range1 = 1..5;
assert range1.from == 1;
assert range1.to == 5;
assert range1.size() == 5;
assert range1.contains(1);    // contains(from)
assert range1.contains(5);    // contains(to)

assert range1.contains(3.5) == false;     // IntRange は BigDecimal を含まない
assert range1.containsWithinBounds(3.5) == false;     // IntRange は BigDecimal を含まない

def range2 = 1..<5;
assert range2.from == 1;
assert range2.to == 4;
assert range2.size() == 4;
assert range2.contains(1);    // contains(from)
assert range2.contains(5) == false;    // contains(to)

def egnar1 = 5..1;
assert egnar1.isReverse();
assert egnar1.from == 1;
assert egnar1.to == 5;
assert egnar1.size() == 5;
assert egnar1.contains(1);    // contains(from)
assert egnar1.contains(5);    // contains(to)

def egnar2 = 5..<1;
assert egnar2.isReverse();
assert egnar2.from == 2;
assert egnar2.to == 5;
assert egnar2.size() == 4;
assert egnar2.contains(1) == false;    // contains(from)
assert egnar2.contains(5);    // contains(to)

//***** BigDecimal の範囲 *****
def d_range = 1.0..5.0;
assert d_range.from == 1.0;
assert d_range.to == 5.0;
assert d_range.size() == 5;
assert d_range.contains(1.0);    // contains(from)
assert d_range.contains(5.0);    // contains(to)

assert d_range.contains(3.5) == false;    // List としての包含関係
assert d_range.containsWithinBounds(3.5);    // Range としての包含関係
  • 範囲指定の際に大小関係を逆順に定義した場合、プロパティ from, to の値に気を付けましょう。 from, to はそれぞれ最小値、最大値の要素となっています。 間違いやすいモノは使わない、ってのがいいかな。

範囲と制御構造


範囲は制御構造とともに用いると強力。

範囲内要素の列挙

  • for-in 文
  • each × Closure
//***** for-in 文 *****
def s1 = '';
for(i in 0..9){
    s1 += i;
}
assert s1 == '0123456789';

def s2 = '';
for(i in 9..0){
    s2 += i;
}
assert s2 == '9876543210';

//***** each × Closure *****
def s3 = '';
('a'..'z').each{
    s3 += it;
}
assert s3 == 'abcdefghijklmnopqrstuvwxyz';

  • switch-case 文
  • grep()
//***** switch-case *****
age = 36;
switch(age){
    case 0..<20 : assert false; break;
    case 20..<40 : assert true; break;
    case 40..<60 : assert false; break;
    case 60..<80 : assert false; break;
    case 80..<100 : assert false; break;
    default: assert false;
}

//***** grep() *****
ages = [11, 12, 13, 15, 18, 20, 22];
teenager = 13..19;
assert ages.grep(teenager) == [13, 15, 18];

独自の型の範囲を作る


独自の型を範囲として扱うためには、以下の2つの条件を満たしていればいいようです:

  • next(), previous() メソッド(++, -- 演算子)を実装している
  • java.lang.Comparable を実装しており、compareTo() メソッド(<=> 演算子)を実装している

compareTo() メソッドを定義していても、Comparable を実装してないとダメなようです。

class Weekday implements Comparable{

    static final DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
    
    private int index = 0;
    
    Weekday(String day){
        this.index = DAYS.indexOf(day);
    }
    
    Weekday next(){
        return new Weekday(DAYS[(this.index + 1) % DAYS.size()]);
    }
    
    Weekday previous(){
        return new Weekday(DAYS[this.index - 1]);    // これでうまくいく!
    }
    
    @Override
    int compareTo(Object other){
        return this.index <=> other.index;
    }
    
    @Override
    String toString(){
        return DAYS[this.index];
    }
}

def mon = new Weekday('Mon');
def fri = new Weekday('Fri');

def s = '';
for(day in mon..fri){
    s += day.toString() + ' ';
}
assert s == 'Mon Tue Wed Thu Fri ';