RootishArrayStack
ブロック単位で確保をしていくArray.
まずは配列は以下のような構造で構築していく.
ブロックのデータの持ち方はちょっと特殊で、Block0: 1個 Block1: 2個 Block2: 3個といった風になる.
つまり、ブロックの個数に対するデータの個数は等差数列を使うと,
さて、データのi番目がブロック内にあるかどうかを考える.
これはi番目というのは0 ~ i番目までなので、i+1個のデータがブロック0~bのb+1個のブロックの合計を超えないようにしてあげればよい.
これを実際に式にして解くと,
ここで、これが0になる場合を解くと,
となる.
欲しいのは正の値で、bがこれよりも大きい場合なので、
後はこれをコードに落とし込むと、データの位置iからブロックの位置bに変換するコードが求まる.
int IndexToBlockIndex(int i)
{
double db = (-3 + Sqrt(9 + 8 * i)) / 2.0;
int b = static_cast<int>(Ceil(db));
return b;
}
さて、ここまでできればGetとSetは簡単なので、最初にこちらから見ていこう.
まずはGetから.
まずindexを現状のブロックに変換を行う.
こうして変換したBlockのIndexが分かれば、Block内のデータの総数が分かる.
これは(1)の式と同じように和の計算でわかる.
こうして全体の総数が分かれば、Blockのスタート位置も計算可能.
よって以下の計算で現在のブロックの位置が分かる.
// blockの個数分引いた値がStart位置なので,blockの個数分を引く
int j = i - block * (block + 1) / 2;
// Getだとindex=0スタートなので、blockをそのまま渡せばOK
return m_blocks.Get(block)[j];
// 取得
T Get(int i)
{
int block = IndexToBlockIndex(i);
// blockの個数分引いた値がStart位置なので,blockの個数分を引く
int j = i - block * (block + 1) / 2;
// Getだとindex=0スタートなので、blockをそのまま渡せばOK
return m_blocks.Get(block)[j];
}
その後に値を入れてあげるだけなので簡単.
// セット
T Set(int i, T x)
{
int block = IndexToBlockIndex(i);
// blockの個数分引いた値がStart位置なので,blockの個数分を引く
int j = i - block * (block + 1) / 2;
// Getだとindex=0スタートなので、blockをそのまま渡せばOK
T y = m_blocks.Get(block)[j];
m_blocks.Get(block)[j] = x;
return y;
}
最初は配列内にデータを詰められるかを確認.
ブロック内の要素内にまだ空きがあるなら大丈夫.
もし無理な場合は
growという処理を呼ぶことで拡張を行う.resizeみたいなもんだね.そして、データを追加する前にデータをずらす必要がある.
データを突っ込むので、その位置を開ける処理だね.今までもあったね.
さて、ずらすのは右にずらすような感じで進めていく.
ArrayStackのResizeとやってることは同じだね.
// Count Up
m_size++;
for (int j = m_size - 1; i < j; j--)
{
// 配列を右にずらして開けるのと同じイメージ
Set(j, Get(j - 1));
}
これをまとめたのが以下である.
// 追加
void Add(int i, T x)
{
int r = m_blocks.Size();
if (r * (r + 1) / 2 < m_size + 1)
{
// ブロック内が埋まってるなら確保
Grow();
}
// Count Up
m_size++;
for (int j = m_size - 1; i < j; j--)
{
// 配列を右にずらして開けるのと同じイメージ
Set(j, Get(j - 1));
}
// 空いた場所に挿入
Set(i, x);
}
これは次のブロックを確保するだけである.
void Grow()
{
// 次のブロック分確保するだけ
m_blocks.Add(m_blocks.Size(), std::move(std::make_shared<T[]>(m_blocks.Size() + 1)));
}
これは実際に以下のように考えればよい.
1つ目のブロックは1個、2つ目のブロックは2個,3つ目のブロックは3個という風にブロックの中のデータ数は増えていく.
なので、ブロック数+1で次のブロックを確保してあげればよいとなるわけだね.
次はRemoveを見ていく.
削除の場合は左にずらして埋めるだけ、これもArrayStackと同じ感じ.
for (int j = i; j < m_size - 1; j++)
{
// 配列を左にずらして埋めてしまうのと同じイメージ
Set(j, Get(j + 1));
}
// Count Down
m_size--;
つまり、N個のブロック分配列は用意してるけど、N-1個しかデータが埋まってない場合は配列を小さくしてしまう.
これは
Shrink処理で行う.int r = m_blocks.Size();
if ((r - 2) * (r - 1) / 2 >= m_size)
{
// 一つ下のブロックよりも小さいサイズになったら、
// 一番上のブロックは解放してしまう
Shrink();
}
T Remove(int i)
{
T x = Get(i);
for (int j = i; j < m_size - 1; j++)
{
// 配列を左にずらして埋めてしまうのと同じイメージ
Set(j, Get(j + 1));
}
// Count Down
m_size--;
int r = m_blocks.Size();
if ((r - 2) * (r - 1) / 2 >= m_size)
{
// 一つ下のブロックよりも小さいサイズになったら、
// 一番上のブロックは解放してしまう
Shrink();
}
return x;
}
Shrinkを見ていこう.ここでやるのは簡単で、データ数的にブロックが削れる場合はデータを開放する.
そしてblock数を下げて更に同じ判定を繰り返して、開けられるBlockまで全部開放していくだけである.
まあ現状の実装だとRemoveでしかデータが消えず、ShrinkはBlock毎にやるので一回しか起こらないとは思うけども...
void Shrink()
{
int r = m_blocks.Size();
// blockが最下層ではない+まだ空けられるblockがある
// この二つを満たす場合は順に空けていく
while (r > 0 && (r - 2) * (r - 1) / 2 >= m_size)
{
m_blocks.Remove(m_blocks.Size() - 1).reset();
r--;
}
}
これで2章も終了!!次からは3章である.