はじめに
今回は、Curl言語のコレクションについて説明します。コレクションは、同じ型のデータを集めたものです。各要素データの持ち方により、「配列」「ハッシュテーブル」「セット」の3種類がCurl言語により用意されています。これらの使い方について見ていきましょう。
配列
配列は、同じ型の変数を複数保持するリストです。各要素にアクセスするには、0から始まる保持順番(インデックス)を使用します。配列には機能によっていくつか種類があります。最も高機能なのは、Array-of クラスです。
let 変数:{Array-of 型} = {{Array-of 型} 要素0, 要素1, ...}
Array-of は、パラメータ化クラスになっています。そのために、型の指定は、{ } で囲む必要があります。新しいインスタンスを生成する場合は、内側の{ } が型を指定し、外側の { } がインスタンスの生成を表します。new を使った構文の方が意味は分かりやすいですね。
let 変数:{Array-of 型} = {new {Array-of 型}, 要素0, 要素1, ...}
型を指定することにより、その型だけを含むことができる配列が生成できます。すべての型を要素に含めるようにしたい場合は、{Array-of any} を使います。null値をとるような型指定が必要な場合は、#演算子を使って型指定をします。要素の型をnull値または文字列型に指定したい場合は、以下のようになります。
let string-array:{Array-of #String} = {{Array-of #String}}
具体的なサンプルコードを見てみましょう。配列に初期値として文字列 a、b、c を設定します。そして、各要素の文字列を連結した文字列を表示します。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value let array:{Array-of String} = {{Array-of String} "a", "b", "c"} {array.get 0} & {array.get 1} & {array.get 2} }
実行結果は文字列 abc が表示されます。
指定した配列の要素にアクセスするために、[ ]を使った表記が利用できます。同じプログラムをgetメソッドの代わりに [ ] で書いてみます。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value let array:{Array-of String} = {{Array-of String} "a", "b", "c"} array[0] & array[1] & array[2] }
Array-of のデフォルトコンストラクタで引数を指定しない場合は、要素がない状態(要素数が0)で生成されます。インスタンス生成後、要素を追加したい場合は、最後に追加する場合は append メソッド、追加する位置を index値で指定して挿入する場合は insert メソッドを使います。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value let array:{Array-of String} = {{Array-of String} "b"} {array.insert "a", 0} {array.append "c"} array[0] & array[1] & array[2] }
実行結果は先ほどと同じ、文字列 abc が表示されます。
要素数を変えずに、値を変更したい場合、set メソッドもしくは setコマンドと [ ] で記述できます。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value let array:{Array-of String} = {{Array-of String}} {array.append "a"} {array.append "b"} {array.append "c"} {array.set 1, "x"} set array[2] = "y" array[0] & array[1] & array[2] }
実行結果は、文字列 axy です。
要素を削除したい場合は、remove メソッドを使います。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value let array:{Array-of String} = {{Array-of String} "a", "b", "c"} {array.remove 1} array[0] & array[1] }
実行結果は、文字列 ac です。このようにインデックス値が1の要素を削除した場合は、要素数が2つになりますから、array[0] と array[1] のみが存在します。array[2] のように存在しない要素を指定すると、以下のエラーが発生します。
<エラー> - このアプレットをロード中にエラーが発生しました。 ArrayBoundsException: 配列インデックスが境界外です。
Array-of の他に、機能を絞って高速に動作する FastArray-of があります。FastArary-of は、最大要素数を予め指定して使う必要があります。また、一度設定したら要素数や値を変更できない ReadOnlyArray-of があります。
プロパティ | 内容 |
---|---|
element-type | 要素の型 |
empty? | 要素が空か否か |
size | 要素の数 |
メソッド | 内容 |
---|---|
append | 配列の最後に要素を追加する |
clear | 配列の要素を全て削除する |
find | 配列の最初に一致した要素のインデックスを返す |
get | 指定したインデックスの要素を返す |
get-if-exists | 指定したインデックスの要素があるか否かと値を返す |
in-bounds? | 指定したインデックス値が有効な範囲か否かを返す |
insert | 指定したインデックス位置に要素を挿入する |
pop | 配列の最後から要素を削除して返す |
push | 配列の最後に要素を追加する |
remove | 指定インデックス位置の1つまたは複数の要素を削除する |
set | 指定インデックス位置の要素を設定する |
sort | 要素を並び替える |
splice | 指定インデックスの位置に、複数要素の挿入を行う |
ハッシュテーブル
配列では、要素にインデックス番号が付いていてその値で要素を特定してアクセスできました。このインデックス番号の代わりに、任意のキーデータを使って値を保持する仕組みがハッシュテーブルです。
let 変数:{HashTable-of キーの型, 値の型} = {{HashTable-of キーの型, 値の型} キー0, 値0, キー1, 値1, ...}
HashTable-of も、パラメータ化クラスになっています。キーの型と値の型の両方を指定する必要があります。Array-of と同じようにキーと値の型を指定しますので型宣言自身を { } で囲みます。新しいインスタンスを生成する場合は、内側の{ } が型を指定し、外側の { } がインスタンスの生成を表すのも Array-of と同じです。HashTable-of は、値を登録した順番は持つ事ができません。キーと値の組み合わせでデータを保持します。キーはユニークである必要があります。
次のサンプルでは、キーを文字列、値を整数としたハッシュテーブルを生成し、値の設定、値の取り出しを行っています。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value let map:{HashTable-of String, int} = {{HashTable-of String, int}} {map.set "zero", 0} {map.set "first", 1} {map.get "zero"} }
実行結果は 0 が表示されます。
値を設定する場合は set メソッドを、値を取得する場合は get メソッドを使います。Array-of の時と同じように [ ] を使った表記も可能です。同じ内容で書き直してみます。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value let map:{HashTable-of String, int} = {{HashTable-of String, int}} set map["zero"] = 0 set map["first"] = 1 map["zero"] }
ハッシュテーブルでは、キーの有無を確認し、あれば値を取り出すような処理を書くことがよくあります。そのような処理を get-if-exists メソッドを使って効率的に書くことができます。ここでは、Curl言語の複数の値が返せる機能が利用されています。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value let map:{HashTable-of String, int} = {{HashTable-of String, int}} {map.set "zero", 0} {map.set "first", 1} let (value:int, exists?:bool) = {map.get-if-exists "zero"} {if exists? then value else "nothing" } }
実行結果は、0 が表示されます。
プロパティ | 内容 |
---|---|
element-type | 要素の型 |
empty? | 要素が空か否か |
key-type | キーの型 |
size | 要素の数 |
メソッド | 内容 |
---|---|
clear | 全ての要素を削除する |
get | 指定したキーの要素を返す |
get-if-exists | 指定したキーの要素があるか否かの判定と値を返す |
key-exists? | 指定したキーがあるか否か |
remove | 指定したキーの要素を削除する |
set | 指定したキーの要素を設定する |
セット
セットはユニークな値を保持する入れものです。順番は保持されません。前章の HashTable-of のキーの部分はユニークな値で構成されるのでセットとなります。以下のように利用します。
let 変数:{Set-of 値の型} = {{Set-of 値の型} 引数, 引数, ...}
Set-of もまたパラメータ化クラスですので、値の型を指定する必要があります。セットに値を追加するには、insert メソッドを利用します。setの中に値があるかどうかを判定するのは、member? メソッドを使います。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value let string-set:{Set-of String} = {{Set-of String}} {string-set.insert "a"} {string-set.insert "b"} {string-set.insert "c"} {string-set.member? "c"} }
実行結果は、true が表示されます。
いろいろな値を持つデータから、ユニークな値を取り出したい場合、Set-of でデータを追加していけばそれで完成です。
次のサンプルコードでは、Randomクラスの next-in-range メソッドを使って、0から10の間の整数の乱数を生成しています。乱数の生成を10回繰り返した場合、int-set にはいくつの要素が格納されているかが表示されます。実行するまでいくつになるか分かりません。9以下の数であった場合、同じ値が生成されたことが分かります。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value let int-set:{Set-of int} = {{Set-of int}} let random:Random = {Random} {for i:int = 0 below 10 do {int-set.insert {random.next-in-range 0, 10}} } int-set.size }
setの中の要素を削除したい場合は、remove メソッドを使います。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value let string-set:{Set-of String} = {{Set-of String} "a", "b"} {string-set.remove "b"} {string-set.member? "b"} }
実行結果は、false です。
Set-of で代表的なメソッドは以下のようになります。
プロパティ | 内容 |
---|---|
element-type | 要素の型 |
empty? | 要素が空か否か |
size | 要素の数 |
メソッド | 内容 |
---|---|
clear | 要素を全て削除する |
insert | 要素を追加する |
member? | 指定した要素がセットに含まれているか否かを返す |
remove | 要素を削除する |
コレクションの反復処理
コレクションを使った反復処理を説明します。コレクションは同じ型の値をもった集合体ですから、反復処理を行うのに適しています。全要素について反復処理を行うには、for文を使います。
最初の構文は、コレクションの要素の値を順番に取り出し、処理を繰り返します。Array-of、HashTable-of、Set-of すべてで利用できます。
{for 要素変数:型 in コレクション変数 do 処理 }
整数配列に初期値を設定して、全要素の合計値を求めるサンプルです。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value def array = {{Array-of int} 11, 13, 15, 17, 19, 21} let sum:int = 0 {for element:int in array do {inc sum, element} } sum }
実行結果は、96 です。
次の構文は、コレクションの要素のキーを取り出し、処理を繰り返します。Array-of の場合は、キーはインデックス番号になります。HashTable-of の場合は、キーの値です。Set-of ではキーがないので利用できません。
{for key キー変数:型 in コレクション変数 do 処理 }
キーを文字列、値を整数のハッシュテーブルで、要素のキーの平均文字列長を出すサンプルです。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value def map = {{HashTable-of String, int}} {map.set "zero", 0} {map.set "one", 1} {map.set "two", 2} {map.set "tree", 3} let sum:int = 0 {for key element:String in map do {inc sum, element.size} } sum / map.size }
実行結果は、3.5 です。
最後の構文は、コレクションの要素のキーと値の両方を取り出し、処理を繰り返します。Array-of の場合は、キーはインデックス番号、値は要素になります。HashTable-of の場合は、キーの値と要素の値です。Set-of ではキーがないので利用できません。
{for 要素変数:型 key キー変数:型 in コレクション変数 do 処理 }
キー、値がそれぞれ整数のハッシュテーブルで、キーと値の両方の合計値を出すサンプルです。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {value def map = {{HashTable-of int, int}} {map.set 11, 40} {map.set 12, 17} {map.set 15, 22} {map.set 32, 34} let sum-key:int = 0 let sum-val:int = 0 {for value:int key key-value:int in map do {inc sum-key, key-value} {inc sum-val, value} } {format "key:%d / value:%d", sum-key, sum-val} }
実行結果は、key:70 / value:113 です。
独自クラスをキーに利用する
セットやハッシュテーブルはキー項目にどのような型でも指定できます。しかし、キーがユニークであるか否かの判定は、== 演算子、つまりインスタンスの一致がデフォルトの動作になります。
==演算子の処理では具合が悪い場合には、コレクションにインスタンスの同一を判定するプロシージャおよびハッシュ値の計算を行うプロシージャを指定する事で任意の一致条件を利用できます。セットでもハッシュテーブルでも指定方法は同じです。
少々長いのですが、以下にサンプルコードを示します。まず、Pointクラスを定義しています。Pointクラスは、x、y の2つのdoubleの値を持っているので、2つのインスタンスの一致条件をx、yの両方の値が等しいことにしています。また、ハッシュ値の生成は、2つの値を単純に合計してCurlのデフォルトのハッシュ計算用プロシージャ value-hash に渡しています。
{curl 7.0 applet} {curl-file-attributes character-encoding = "shift-jis"} || 独自クラスを定義 {define-class public Point || デフォルトコンストラクタ {constructor public {default x:double, y:double} set self.x = x set self.y = y } || x座標 field public x:double || y座標 field public y:double || 原点からの距離 {method public {get-distance}:double {return {sqrt {pow self.x, 2} + {pow self.y ,2}}} } } {value def set0 = {{Set-of Point}} def v-hash = {proc {point:Point}:int {return {value-hash point.x + point.y}} } def equals? = {proc {p0:Point, p1:Point}:bool {return p0.x == p1.x and p0.y == p1.y} } def set1 = {{Set-of Point, key-hash-proc = v-hash, key-equality-proc = equals?} } {set0.insert {Point 10.0, 20.0}} {set1.insert {Point 10.0, 20.0}} def result0 = {set0.member? {Point 10.0, 20.0}} def result1 = {set1.member? {Point 10.0, 20.0}} {format "==判定では %s、proc判定では %s", result0, result1} }
実行結果は、==判定では false、proc判定では true となります。
最後に
Curl言語のコレクションについて見てきました。パラメータ化クラスによって実装されており、キーの一致条件なども任意に設定できるなど十分な汎用性を持っています。Java等では、クラスごとのequalsメソッドで一致条件を定義するのに対し、Curl言語ではオブジェクトを格納するコレクション側で一致条件を指定できるため、より柔軟な処理が可能になります。
Curl言語に標準で実装されているコレクションの他に、順番を保持したHashTableやSetなどが、Curl拡張ライブラリとしてオープンソースで提供されています。カールデベロッパーセンターにアクセスしてみてください。