今回は範囲 (groovy.lang.Range) について。 『Groovyイン・アクション』Chapter4 を参考にしています(一覧)。
- 作者: Dierk Konig,Andrew Glover,Paul King,Guillaume Laforge,Jon Skeet,杉浦孝,櫻井正樹,須江信洋,関谷和愛,佐野徹郎,寺沢尚史
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2008/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 5人 クリック: 146回
- この商品を含むブログ (121件) を見る
インスタンス生成
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つの条件を満たすクラス
であれば、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 ';