title | actions | requireLogin | material | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
For ループ |
|
true |
|
前のチャプターでは、関数内で配列を構築する際に、単にstorageに保存するかわりにfor
ループを使用して作りたい場合もあると言ったな。
その理由を説明しよう。
getZombiesByOwner
関数を素直に実装しようとするなら、オーナーからゾンビ軍団へのmapping
をZombieFactory
コントラクトに持たせればいいだろう:
mapping (address => uint[]) public ownerToZombies
新しいゾンビを作る度に、ownerToZombies[owner].push(zombieId)
を使ってオーナーのゾンビ配列に追加していくだけだ。するとgetZombiesByOwner
は非常にシンプルな関数になる:
function getZombiesByOwner(address _owner) external view returns (uint[]) {
return ownerToZombies[_owner];
}
この方法は簡単だから、つい使いたくなってしまう。だがゾンビを誰かに譲る関数をあとで作成した時に問題が起こる(後のレッスンで教えるから、しばし待つのだ!)。
その関数には次の動作が必要になる:
- ゾンビを新しいオーナーの
ownerToZombies
配列に追加する - 元のオーナーの
ownerToZombies
配列からゾンビを削除する - 穴を埋めるために、元のオーナーの各ゾンビの配列の番号を変更する
- 配列のlengthを1 減らす。
ステップ 3は、ゾンビの位置を全てずらすことになるから、ガスコストは非常に高額になってしまう。もしオーナーがゾンビを20体持っていて、最初のゾンビを誰かにあげたとする。すると残りの19体の配列番号を書き直さなくてはならなくなるのだ。
storageに書き込むことはSolidityの操作で一番ガスコストがかかるものだから、このゾンビを誰かに譲る関数を実行するたびに、莫大なガスコストが発生してしまうのだ。さらに悪いことに、ユーザーがどれくらいのゾンビを所有していて、何番目のゾンビを譲るのかによってガスのコストが変わってしまうのだ。だからユーザーはどのくらいガスが必要なのか、実行するまでわからないのだ。
注:もちろん、譲ったゾンビの場所に、配列の最後のゾンビを移動して、穴埋めに使う方法はあります。しかしその場合、取引の度にゾンビ軍団の順番を変えなければならなくなります。
この場合、view
関数は外部から呼び出した時にガスコストがかからないから、getZombiesByOwner
内でforループを使ってそのオーナーのゾンビ軍団の配列を作ってしまえばいい。そうすればtransfer
関数はstorage内の配列を並び替える必要がないため安く抑えられるし、直感的ではないにしろ全体のコストも抑えられる。
Solidityのfor
ループはJavaScriptと同じようなものだと考えていい。
偶数の数字を格納する配列の例を出すぞ:
function getEvens() pure external returns(uint[]) {
uint[] memory evens = new uint[](5);
// 新しい配列のインデックスをトラックする:
uint counter = 0;
// 1から10までループさせる:
for (uint i = 1; i <= 10; i++) {
// もし `i` が偶数なら...
if (i % 2 == 0) {
// 配列に格納する
evens[counter] = i;
// カウンタを増やして `evens`の空のインデックスにする:
counter++;
}
}
return evens;
}
この関数は[2, 4, 6, 8, 10]
の配列を返す。
getZombiesByOwner
を完成させよ。for
ループでDApp内の全てのゾンビをループさせ、オーナーが一致するかどうかを判定し、result
配列に格納して返却せよ。
-
counter
というuint
を宣言し、0
に設定せよ。この変数はresult
配列のインデックスとして使用する。 -
uint i = 0
から始めて、i < zombies.length
までループするfor
ループを宣言せよ。このループは配列内の全てのゾンビをイテレートする。 -
for
ループ内にif
ステートメントを作成し、zombieToOwner[i]
が_owner
と一致するか判定せよ。2つのアドレスを比較することでチェックしているのだ。 -
if
ステートメント内部には以下を設定せよ:result
配列内にゾンビのIDを追加せよ。result[counter]
をi
と同等になるよう設定するだけでよい。counter
を 1 増やせ。(for
ループの例を参考にするのだ)。
これでいい。_owner
が所有する全てのゾンビが返るはずだ。しかもガスは一切不要だ。