反復処理とスパース配列
ダイナミック・エンティティは、標準の反復メソッド %GetNext() を使用します。これは、オブジェクトと配列の両方で機能します。各要素を順番に指定して配列を反復処理することもできます (for ループまたは同様の構造体を使用) が、これには、値を含まない要素を持つスパース配列の知識が必要になる場合があります。%GetNext() は、これらの要素をスキップすることにより問題を回避するため、可能な限り、優先される反復メソッドになります。
この章では、各反復メソッドを使用するタイミングと方法ついて説明します。以下の項目について説明します。
%GetNext() を使用したダイナミック・エンティティの反復処理
すべてのダイナミック・エンティティで提供されている %GetIterator() メソッドは、そのダイナミック・オブジェクトまたは動的配列のメンバを指すポインタが含まれた %Iterator (%Iterator.Object または %Iterator.Array) のインスタンスを返します。%Iterator オブジェクトは、各メンバのキーと値を取得するための %GetNext() メソッドを提供します。
%GetNext() メソッドを呼び出すたびに、反復子カーソルが進められて、カーソルの位置が有効なメンバである場合は 1 (true) が返され、カーソルが最後のメンバを越えている場合は 0 (false) が返されます。メンバの名前またはインデックス番号が 1 つ目の出力引数内に返されて、値が 2 つ目の出力引数内に返されます。以下に例を示します。
set test = ["a","b","c"] // dynamic arrays are zero-based
set iter = test.%GetIterator()
while iter.%GetNext(.key, .value) { write "element:"_key_"=/"_value_"/ "}
element:0=/a/ element:1=/b/ element:2=/c/
反復子カーソルは一方向のみに移動するため、前のメンバに戻ったり、配列を逆順に反復処理したりすることはできません。
スパース配列を反復処理する際は、反復子は値が割り当てられていない要素をスキップします。オブジェクトを反復処理する際は、プロパティは必ずしも予測可能な順序で返されません。以下の例では、配列の反復処理とオブジェクトの反復処理の間に存在するこれらの相違点を示しています。
この例では、スパース配列を作成します。配列は 0 から始まり、6 つの要素が指定されていますが、要素 0、1、および 5 のみに値が割り当てられています。JSON 文字列に表示されている null 要素は、未割り当て値の単なるプレースホルダです。
set dynArray=["abc",999]
set dynArray."5" = "final"
write dynArray.%Size()_" elements: "_dynArray.%ToJSON()
6 elements: ["abc",999,null,null,null,"final"]
%GetNext() は、値が割り当てられている 3 つの要素のみを返して、すべての未割り当て要素をスキップします。
set iterator=dynArray.%GetIterator()
while iterator.%GetNext(.key,.val) { write !, "Element index: "_key_", value: "_val }
Element index: 0, value: abc
Element index: 1, value: 999
Element index: 5, value: final
スパース配列の詳細は、次のセクション (“スパース配列と未割り当て値の理解”) を参照してください。
オブジェクト・プロパティには固定された順序はありません。つまり、未割り当て値を作成しなくても、プロパティを任意の順序で作成したり破棄したりすることはできますが、オブジェクトを変更すると、%GetNext() によってプロパティが返される順序も変更される可能性があります。以下の例では、3 つのプロパティを持つオブジェクトを作成し、%Remove() を呼び出して 1 つのプロパティを破棄し、別のプロパティを追加します。
set dynObject={"propA":"abc","PropB":"byebye","propC":999}
do dynObject.%Remove("PropB")
set dynObject.propD = "final"
write dynObject.%Size()_" properties: "_dynObject.%ToJSON()
3 properties: {"propA":"abc","propD":"final","propC":999}
オブジェクトを反復処理する際は、%GetNext() はアイテムをそれらが作成された順序で返しません。
set iterator=dynObject.%GetIterator()
while iterator.%GetNext(.key,.val) { write !, "Property name: """_key_""", value: "_val }
Property name: "propA", value: abc
Property name: "propD", value: final
Property name: "propC", value: 999
スパース配列と未割り当て値の理解
動的配列は、スパース配列であってもかまいません (すなわち、すべての配列要素に値が保持されていなくてもかまいません)。例えば、動的配列に 0 ~ 99 の要素がまだ含まれていない場合でも、この配列の要素 100 に値を割り当てることができます。メモリ領域は、要素 100 の値のみに割り当てられます。要素 0 ~ 99 は未割り当てです。つまり、0 ~ 99 は有効な要素識別子ですが、メモリ内のどの値も指していません。%Size() メソッドは 101 という配列サイズを返しますが、%GetNext() メソッドは未割り当て要素をスキップして、要素 100 の値のみを返します。
次の例では、要素 8 と 11 に新しい値を割り当てることで、スパース配列を作成します。
set array = ["val_0",true,1,"",null,"val_5"] // values 0 through 5
do array.%Set(8,"val_8") // undefined values 6 and 7 will be null
set array."11" = "val_11" // undefined values 9 and 10 will be null
write array.%ToJSON()
["val_0",true,1,"",null,"val_5",null,null,"val_8",null,null,"val_11"]
要素 6、7、9、および 10 には値が割り当てられていません。また、これらの要素はメモリ領域を使用しませんが、JSON 文字列には null 値で表されます。これは JSON が未定義の値をサポートしていないためです。
%Remove() メソッドは、未割り当ての要素を他の要素と同様に扱います。未割り当ての値のみで構成される配列を持つことは可能です。次の例では、スパース配列を作成して未割り当ての要素 0 を削除します。次に、要素 7 を削除します。これは現在、値を含む唯一の要素です。
set array = []
do array.%Set(8,"val_8")
do array.%Remove(0)
do array.%Remove(7)
write "Array size = "_array.%Size()_":",!,array.%ToJSON()
Array size = 7:
[null,null,null,null,null,null,null]
%Remove() の使用例については、“%Set()、%Get()、および %Remove() の使用” を参照してください。
ダイナミック・エンティティには、null 値と unassigned 値を区別することを可能にするメタデータが含まれています。JSON では、独立した undefined データ型が指定されていないため、ダイナミック・エンティティが JSON 文字列にシリアル化される際に、この区別を保持するためのキャノニック形式の方法はありません。シリアル化されたデータに追加の null 値が不要な場合は、シリアル化する前に未割り当ての要素を削除するか (“%IsDefined() を使用した有効値の有無の確認” を参照)、アプリケーションに依存する何らかの手段を使用して、この区別をメタデータとして記録する必要があります。
%Size() を使用したスパース配列の反復処理
%Size() メソッドは、ダイナミック・エンティティ内のプロパティまたは要素の数を返します。以下に例を示します。
set dynObject={"prop1":123,"prop2":[7,8,9],"prop3":{"a":1,"b":2}}
write "Number of properties: "_dynObject.%Size()
Number of properties: 3
スパース配列では、この数には、値が割り当てられていない要素が含まれます (次の例を参照)。この例で作成される配列には 6 つの要素がありますが、要素 0、1、および 5 のみに値が割り当てられています。JSON 文字列に表示されている null 要素は、未割り当て値の単なるプレースホルダです。
set test=["abc",999]
set test."5" = "final"
write test.%Size()_" elements: "_test.%ToJSON()
6 elements: ["abc",999,null,null,null,"final"]
要素 2、3、および 4 には値が割り当てられていませんが、有効な配列要素として扱われます。動的配列は 0 から始まるため、最終要素のインデックス番号は常に %Size()-1 となります。次の例では、配列 test の 6 つの要素すべてを逆順に反復処理して、%Get() を使用してこれらの要素の値を返します。
for i=(test.%Size()-1):-1:0 {write "element "_i_" = /"_test.%Get(i)_"/",!}
element 5 = /final/
element 4 = //
element 3 = //
element 2 = //
element 1 = /999/
element 0 = /abc/
%Get() メソッドは、%Size()-1 より大きい値の場合は "" (空の文字列) を返し、負の数の場合は例外をスローします。未割り当て値、空の文字列、および null 値を区別する方法については、“データ型を使用した作業” を参照してください。
ここで紹介した反復手法は、特別な目的に対してのみ有用です (配列内の未割り当て値の検出や、配列の逆順の反復処理など)。ほとんどの場合は、未割り当て要素をスキップする %GetNext() メソッドを使用してください。このメソッドは、ダイナミック・オブジェクトや動的配列に対して使用できます。詳細は、前のセクション (“%GetNext() を使用したダイナミック・エンティティの反復処理”) を参照してください。
%IsDefined() を使用した有効値の有無の確認
%IsDefined() メソッドは、指定されたプロパティ名や配列インデックス番号に値が存在するかどうかを判定します。このメソッドは、指定されたメンバが値を持つ場合は 1 (true) を返して、そのメンバが存在しない場合は 0 (false) を返します。このメソッドは、スパース配列内の値が割り当てられていない要素についても false を返します。
for ループを使用してスパース配列を反復処理する場合は、未割り当て値が検出されます。次の例では、最初の 3 つの要素が JSON の null、空の文字列、および未割り当て値である配列を作成します。for ループは、配列の末尾を越えて、配列インデックス 4 の要素をテストするように、意図的に設定されています。
set dynarray = [null,""]
set dynarray."3" = "final"
write dynarray.%ToJSON()
[null,"",null,"final"]
for index = 0:1:4 {write !,"Element "_index_": "_(dynarray.%IsDefined(index))}
Element 0: 1
Element 1: 1
Element 2: 0
Element 3: 1
Element 4: 0
%IsDefined() によって 0 が返される 2 つのケースは、要素 2 に値が割り当てられていない場合と、要素 4 が存在しない場合です。
ObjectScript は、JSON の null 値 (この例の要素 0 など) については "" (空の文字列) を返します。""、null、および未割り当て値の有無を判定する必要がある場合は、%IsDefined() ではなく %GetTypeOf() を使用してください (“NULL、空の文字列、および未割り当て値の解決” を参照してください)。
前のセクションで述べたとおり、いくつかの特殊な場合を除いて、反復処理のために for ループを使用しないでください。ほとんどの場合は、未割り当て値をスキップする %GetNext() メソッドを使用してください (“%GetNext() を使用したダイナミック・エンティティの反復処理” を参照してください)。
%IsDefined() メソッドは、オブジェクト・プロパティが存在するかどうかを判定するためにも使用できます。以下のコードは、3 つの文字列値を持つ動的配列 names を作成してから、最初の 2 つの文字列を使用して、prop1 および prop2 というプロパティを持つオブジェクト dynobj を作成します。
set names = ["prop1","prop2","noprop"]
set dynobj={}.%Set(names."0",123).%Set(names."1",456)
write dynobj.%ToJSON()
{"prop1":123,"prop2":456}
以下のコードは、%IsDefined() を使用して dynobj 内でプロパティ名として使用された文字列を特定します。
for name = 0:1:2 {write !,"Property "_names.%Get(name)_": "_(dynobj.%IsDefined(names.%Get(name)))}
Property prop1: 1
Property prop2: 1
Property noprop: 0
動的配列での %Push と %Pop の使用
%Push() メソッドと %Pop() メソッドは、動的配列に対してのみ使用できます。これらは %Set() および %Remove() とまったく同じように機能しますが、配列の最後の要素を常に追加または削除する点が異なります。例えば、以下のコードは、どちらのメソッドのセットでも同じ結果が得られます (同じ文で %Set() または %Push() を複数回呼び出すことについて、詳細は、“メソッドの連鎖” を参照してください)。
set array = []
do array.%Set(array.%Size(), 123).%Set(array.%Size(), 456)
write "removed "_array.%Remove(array.%Size()-1)_", leaving "_array.%ToJSON()
removed 456, leaving [123]
set array = []
do array.%Push(123).%Push(456)
write "removed "_array.%Pop()_", leaving "_array.%ToJSON()
removed 456, leaving [123]
%Push() と %Pop() はスタック操作用ですが、%Pop() の代わりに %Remove(0) を使用すると、キューを実装できます。
以下の例では、%Push() を使用して配列を作成し、%Pop() を使用して各要素を逆の順序で削除します。
入れ子になった配列を含む配列を構築します。%Push() の最後の呼び出しは、オプションの type 引数を指定して、ブーリアン値を ObjectScript の 0 ではなく JSON の false として格納します (“%Set() または %Push() を使用したデフォルトデータ型のオーバーライド” を参照)。
set array=[]
do array.%Push(42).%Push("abc").%Push([])
do array."2".%Push("X").%Push(0,"boolean")
write array.%ToJSON()
[42,"abc",["X",false]]
入れ子になった配列のすべての要素を削除します。すべてのダイナミック・エンティティ・メソッドと同様に、%Pop() は JSON の false ではなく ObjectScript の 0 を返します。
for i=0:1:1 {write "/"_array."2".%Pop()_"/ "}
/0/ /X/
write array.%ToJSON()
[42,"abc",[]]
空の入れ子になった配列を含む、メイン配列のすべての要素を削除します。
for i=0:1:2 {write "/"_array.%Pop()_"/ "}
/2@%Library.DynamicArray/ /abc/ /42/
write array.%ToJSON()
[]
簡単にするために、これらの例ではハード・コード化された for ループを使用しています。配列の反復処理の実例は、“%Size() を使用したスパース配列の反復処理” を参照してください。