Guitar
LineIndexMap.h
Go to the documentation of this file.
1 #ifndef LINEINDEXMAP_H
2 #define LINEINDEXMAP_H
3 
4 #include <cstdint>
5 #include <vector>
6 #include <optional>
7 #include <memory>
8 #include <utility>
9 #include <assert.h>
10 
11 // LineIndexMap
12 //
13 // 折り返し(ワードラップ)機能付きテキストエディタで、論理行番号と表示行番号を
14 // 相互変換するためのデータ構造。
15 //
16 // - キー = 論理行番号(0ベース)。安定したIDではなく「位置」であり、
17 // insert で後続キーは1つ後ろへずれ、erase で1つ前へ詰まる
18 // - 値 = その論理行が折り返しで占める表示行数。表示行1つにつき UserData を
19 // 1要素持ち、その個数(value())が折り返し数を兼ねる。UserData の col_len に
20 // 各折り返し行の桁数を持たせると、論理列まで含めた座標変換ができる
21 // - count(i) = 論理行 0..i-1 の表示行数の合計 = 論理行 i の先頭の表示行番号
22 // - logical_to_visual(行, 列) = 順変換(論理座標 → 表示座標 VisualPosition)
23 // - visual_to_logical(表示行) = 逆変換(表示行番号 → 論理座標 LogicalPosition)
24 //
25 // 内部は固定3階層の B+-tree: root(nodes_)→ Node → Leaf。
26 // Node に「配下の総要素数」と「配下の値の合計」をキャッシュしておき、
27 // キー解決・集計・逆変換のいずれも Node/Leaf 単位のスキップで高速化する。
28 // 各操作の計算量は O(root内Node数 + fanout + 葉容量)。
29 class LineIndexMap {
30 public:
31  // 表示行(折り返し行)1行ぶんに付随するユーザーデータ
32  struct UserData {
33  uint32_t vcol_len = 0; // この折り返し行が保持する桁数(0 = 未設定)
34  };
35  // 論理行1行ぶんの値。表示行ごとの UserData の配列を共有ポインタで保持し、
36  // その要素数が折り返し数(=この行が占める表示行数)を表す。
37  // コピーは shared_ptr の浅い共有なのでコピーコストは小さい。
38  // data_ は private のため外部からは変更できず、折り返し数の変更は
39  // 必ず LineIndexMap::update() を通る(sum_values との整合が保たれる)。
40  class ValueItem {
41  friend class LineIndexMap;
42  private:
43  std::shared_ptr<std::vector<UserData>> data_;
44  public:
45  // 折り返し数だけ指定して構築する(vcol_len はすべて未設定)
46  ValueItem(uint32_t value = 0)
47  {
48  data_ = std::make_shared<std::vector<UserData>>(value);
49  }
50  // 各折り返し行の桁数を指定して構築する(折り返し数 = col_count_list.size())
51  ValueItem(std::vector<uint32_t> const &col_count_list)
52  {
53  data_ = std::make_shared<std::vector<UserData>>(col_count_list.size());
54  for (size_t i = 0; i < col_count_list.size(); i++) {
55  (*data_)[i].vcol_len = col_count_list[i];
56  }
57  }
58  // 折り返し数(この行が占める表示行数)
59  uint32_t value() const
60  {
61  assert(data_);
62  return data_->size();
63  }
64  // 論理列がこの行の何番目の折り返し行に属するかを求める。
65  // 戻り値は (折り返し行インデックス, 折り返し行内の列)。
66  // 列 == 桁数 の境界は次の折り返し行の先頭に進む。
67  // 最終折り返し行では列を切らないため、行末を超えた列は最終行に丸められる。
68  // vcol_len が未設定(0)の行は折り返し境界として扱われない。
69  std::pair<uint32_t, uint32_t> locate_column(uint32_t lcol) const
70  {
71  assert(data_);
72  uint32_t row = 0;
73  for (size_t i = 0; i + 1 < data_->size(); i++) {
74  uint32_t len = (*data_)[i].vcol_len;
75  if (len > 0) {
76  if (lcol < len) break;
77  lcol -= len;
78  row++;
79  }
80  }
81  return {row, lcol};
82  }
83  // locate_column の逆演算。折り返し行 row の先頭の論理列
84  // (先行する折り返し行の vcol_len の合計)を求める。
85  uint32_t column_of_row(uint32_t row) const
86  {
87  assert(data_);
88  assert(row < data_->size());
89  uint32_t lcol = 0;
90  for (size_t i = 0; i < row; i++) {
91  lcol += (*data_)[i].vcol_len;
92  }
93  return lcol;
94  }
95  };
96  typedef uint32_t key_type;
98 private:
99  static constexpr size_t max_leaf_capacity = 256; // Leafが保持できるitem数の上限
100  static constexpr size_t max_node_fanout = 256; // Nodeが保持できるLeaf数の上限
101  // 第3階層。item(論理行)の実体を保持する。
102  // 不変条件: sum_values は items の value() の総和と常に一致し、
103  // サイズは 1..max_leaf_capacity(空のLeafは残さない)。
104  struct Leaf {
105  std::vector<value_type> items;
106  uint64_t sum_values = 0;
107  };
108  // 第2階層。Leafの列と、配下全体の集計値を保持する。
109  // num_items はキー解決の、sum_values は count/逆変換のスキップに使う。
110  // 不変条件: 集計値は配下のLeafの合計と常に一致し、空のNodeは残さない。
111  struct Node {
112  std::vector<Leaf> leaves;
113  size_t num_items = 0; // 配下の総item数
114  uint64_t sum_values = 0; // 配下のvalue()の総和
115  };
116  // 第1階層(root)。キーは先頭のNodeから順に num_items を差し引いて解決する
117  // 通算インデックス。
118  std::vector<Node> nodes_;
119 private:
120  // 末尾に追記可能な(満杯でない)Leafを用意する。
121  // 最後のLeafが満杯なら新しいLeafを、最後のNodeのfanoutも満杯なら
122  // 新しいNodeを追加する。末尾への連続追加はこの経路で分割を起こさずに
123  // 満杯まで詰めて構築されるため、一括構築が速い。
124  void ensure_tail()
125  {
126  if (nodes_.empty()) {
127  nodes_.push_back(Node());
128  }
129  Node *node = &nodes_.back();
130  if (node->leaves.empty() || node->leaves.back().items.size() >= max_leaf_capacity) {
131  if (node->leaves.size() >= max_node_fanout) {
132  nodes_.push_back(Node());
133  node = &nodes_.back();
134  }
135  Leaf leaf;
136  leaf.items.reserve(max_leaf_capacity);
137  node->leaves.push_back(std::move(leaf));
138  }
139  }
140  // Nodeの集計値(num_items / sum_values)を配下のLeafから再計算する
141  void recalc_node(Node *node)
142  {
143  node->num_items = 0;
144  node->sum_values = 0;
145  for (Leaf const &leaf : node->leaves) {
146  node->num_items += leaf.items.size();
147  node->sum_values += leaf.sum_values;
148  }
149  }
150  // nodes_[ni] のLeaf列を半分に分けて、後半を新しいNodeとして直後に挿入する
151  void split_node(size_t ni)
152  {
153  Node *node = &nodes_[ni];
154  size_t half = node->leaves.size() / 2;
155  Node newnode;
156  newnode.leaves.assign(std::make_move_iterator(node->leaves.begin() + half), std::make_move_iterator(node->leaves.end()));
157  node->leaves.resize(half);
158  recalc_node(node);
159  recalc_node(&newnode);
160  nodes_.insert(nodes_.begin() + ni + 1, std::move(newnode));
161  }
162  // nodes_[ni] の li 番目のLeafの offset 位置に item を挿入する。
163  // 挿入・末尾追加の変更はすべてここを通ることで、集計値の更新漏れを防ぐ。
164  // Leafが容量を超えたら半分に分割し、その結果Nodeのfanoutを超えたら
165  // Nodeも分割する(分割の連鎖はここで完結する)。
166  void insert_into_leaf(size_t ni, size_t li, size_t offset, value_type item)
167  {
168  Node *node = &nodes_[ni];
169  Leaf *leaf = &node->leaves[li];
170  leaf->items.insert(leaf->items.begin() + offset, item);
171  leaf->sum_values += item.value();
172  node->num_items++;
173  node->sum_values += item.value();
174  if (leaf->items.size() > max_leaf_capacity) {
175  // 後半を新しいLeafへ移し、集計値も付け替える
176  size_t half = leaf->items.size() / 2;
177  Leaf newleaf;
178  newleaf.items.reserve(max_leaf_capacity);
179  newleaf.items.assign(leaf->items.begin() + half, leaf->items.end());
180  for (value_type const &x : newleaf.items) {
181  newleaf.sum_values += x.value();
182  }
183  leaf->sum_values -= newleaf.sum_values;
184  leaf->items.resize(half);
185  node->leaves.insert(node->leaves.begin() + li + 1, std::move(newleaf));
186  if (node->leaves.size() > max_node_fanout) {
187  split_node(ni);
188  }
189  }
190  }
191  // count() の走査結果。前置和と、key位置のitemを同時に返すことで、
192  // logical_to_visual での二度引きを避ける。
193  struct CountResult {
194  uint64_t sum = 0; // [0, key) の value() の合計
195  ValueItem const *item = nullptr; // lrow位置のitem(範囲外なら nullptr)
196  };
197  // [0, lrow) の値の合計と、lrow位置のitemを求める。
198  // Node/Leaf 全体が範囲に収まる場合は集計値を一括加算してスキップし、
199  // 境界がかかる最後のLeafだけ個別に加算する。
201  {
202  CountResult result;
203  for (Node const &node : nodes_) {
204  // Node全体が範囲に収まるなら集計値を一括加算してスキップ
205  if (lrow >= node.num_items) {
206  result.sum += node.sum_values;
207  lrow -= node.num_items;
208  if (lrow == 0) break;
209  continue;
210  }
211  for (Leaf const &leaf : node.leaves) {
212  size_t nvalues = leaf.items.size();
213  if (lrow < nvalues) {
214  // 境界がかかる最後のLeafだけ個別に加算する
215  for (size_t j = 0; j < lrow; j++) {
216  result.sum += leaf.items[j].value();
217  }
218  result.item = &leaf.items[lrow];
219  break;
220  }
221  // Leaf全体が範囲に収まるなら集計値を一括加算してスキップ
222  result.sum += leaf.sum_values;
223  lrow -= nvalues;
224  if (lrow == 0) break;
225  }
226  break;
227  }
228  return result;
229  }
230 public:
231  // 全不変条件を検査する(テスト用)。
232  // 集計値の一致・容量とfanoutの上限・空のLeaf/Nodeが残っていないことを確認し、
233  // すべて整合していれば true を返す。
234  bool validate() const
235  {
236  for (Node const &node : nodes_) {
237  if (node.leaves.empty()) return false;
238  if (node.leaves.size() > max_node_fanout) return false;
239  size_t num_items = 0;
240  uint64_t node_sum = 0;
241  for (Leaf const &leaf : node.leaves) {
242  if (leaf.items.empty()) return false;
243  if (leaf.items.size() > max_leaf_capacity) return false;
244  uint64_t sum = 0;
245  for (value_type const &x : leaf.items) {
246  sum += x.value();
247  }
248  if (leaf.sum_values != sum) return false;
249  num_items += leaf.items.size();
250  node_sum += sum;
251  }
252  if (node.num_items != num_items) return false;
253  if (node.sum_values != node_sum) return false;
254  }
255  return true;
256  }
257  // すべて消去する(画面幅変更時の全再構築などに使う)
258  void clear()
259  {
260  nodes_.clear();
261  }
262  // lrow 位置の値を返す。範囲外は nullopt。
263  std::optional<value_type> find(key_type lrow) const
264  {
265  for (Node const &node : nodes_) {
266  // このNodeの範囲外ならNodeごとスキップ
267  if (lrow >= node.num_items) {
268  lrow -= node.num_items;
269  continue;
270  }
271  for (Leaf const &leaf : node.leaves) {
272  size_t nvalues = leaf.items.size();
273  if (lrow < nvalues) {
274  return leaf.items[lrow];
275  }
276  lrow -= nvalues;
277  }
278  break; // Nodeに入ったら必ずLeaf内で解決する(ここには到達しない)
279  }
280  return std::nullopt;
281  }
282  // [0, lrow) の範囲の値の合計を返す。
283  // 論理行 lrow の先頭の表示行番号に相当する。lrow が総要素数を超えていたら
284  // 全体の合計(=総表示行数)を返す。
285  uint64_t count(key_type lrow) const
286  {
287  return count_and_find(lrow).sum;
288  }
289  // lrow の位置に item を挿入する。後続のキーは1つ後ろへずれる。
290  // lrow が末尾より先の場合は、隙間をデフォルト値(value 0)で埋めてから配置する。
291  void insert(key_type lrow, value_type item)
292  {
293  for (size_t ni = 0; ni < nodes_.size(); ni++) {
294  Node const &node = nodes_[ni];
295  // 挿入は末尾境界(lrow == num_items)もこのNodeが受け持つ
296  if (lrow > node.num_items) {
297  lrow -= node.num_items;
298  continue;
299  }
300  for (size_t li = 0; li < node.leaves.size(); li++) {
301  size_t nvalues = node.leaves[li].items.size();
302  if (lrow <= nvalues) {
303  insert_into_leaf(ni, li, lrow, item);
304  return;
305  }
306  lrow -= nvalues;
307  }
308  return; // Nodeに入ったら必ずLeaf内で解決する(ここには到達しない)
309  }
310  // 末尾より先: 隙間をデフォルト値(value 0)で埋めてから追加する
311  while (lrow > 0) {
312  ensure_tail();
313  Node *node = &nodes_.back();
314  std::vector<value_type> *items = &node->leaves.back().items;
315  size_t n = std::min<size_t>(lrow, max_leaf_capacity - items->size());
316  items->insert(items->end(), n, {});
317  node->num_items += n; // value 0 なので sum_values は不変
318  lrow -= n;
319  }
320  ensure_tail();
321  insert_into_leaf(nodes_.size() - 1, nodes_.back().leaves.size() - 1, nodes_.back().leaves.back().items.size(), item);
322  }
323  // 既存の lrow なら値を上書きする(後続のキーはずれない)。
324  // lrow が最大キーを超えていたら末尾に追加する(隙間埋めはしない)。
325  // 末尾追加の性質を使って、連続appendによる一括構築にも使える。
326  void update(key_type lrow, value_type value)
327  {
328  for (Node &node : nodes_) {
329  // このNodeの範囲外ならNodeごとスキップ
330  if (lrow >= node.num_items) {
331  lrow -= node.num_items;
332  continue;
333  }
334  for (Leaf &leaf : node.leaves) {
335  size_t nvalues = leaf.items.size();
336  if (lrow < nvalues) {
337  // 古い値を差し引いてから新しい値を加算し、上書きする
338  uint64_t oldvalue = leaf.items[lrow].value();
339  uint64_t newvalue = value.value();
340  leaf.sum_values -= oldvalue;
341  leaf.sum_values += newvalue;
342  node.sum_values -= oldvalue;
343  node.sum_values += newvalue;
344  leaf.items[lrow] = value;
345  return;
346  }
347  lrow -= nvalues;
348  }
349  return; // Nodeに入ったら必ずLeaf内で解決する(ここには到達しない)
350  }
351  // 最大キーを超えていたら末尾に追加する
352  ensure_tail();
353  insert_into_leaf(nodes_.size() - 1, nodes_.back().leaves.size() - 1, nodes_.back().leaves.back().items.size(), value);
354  }
355  // lrow 位置の要素を削除する。後続のキーは1つ前へ詰まる。範囲外なら何もしない。
356  // 空になったLeafはNodeから、空になったNodeはrootから取り除く。
357  void erase(key_type lrow)
358  {
359  for (size_t ni = 0; ni < nodes_.size(); ni++) {
360  Node *node = &nodes_[ni];
361  // このNodeの範囲外ならNodeごとスキップ
362  if (lrow >= node->num_items) {
363  lrow -= node->num_items;
364  continue;
365  }
366  for (size_t li = 0; li < node->leaves.size(); li++) {
367  Leaf *leaf = &node->leaves[li];
368  size_t nvalues = leaf->items.size();
369  if (lrow < nvalues) {
370  uint64_t oldvalue = leaf->items[lrow].value();
371  leaf->sum_values -= oldvalue;
372  node->sum_values -= oldvalue;
373  node->num_items--;
374  leaf->items.erase(leaf->items.begin() + lrow);
375  if (leaf->items.empty()) {
376  node->leaves.erase(node->leaves.begin() + li);
377  if (node->leaves.empty()) {
378  nodes_.erase(nodes_.begin() + ni);
379  }
380  }
381  return;
382  }
383  lrow -= nvalues;
384  }
385  return; // Nodeに入ったら必ずLeaf内で解決する(ここには到達しない)
386  }
387  }
388 
389  // (論理行, 論理列) から表示位置を求める。countの順変換。
390  // 行頭の表示行(count)に、論理列が属する折り返し行のオフセット
391  // (locate_column)を加える。論理行が範囲外なら {総表示行数, 0} を返す。
392  struct VisualPosition {
393  uint64_t vrow = 0; // 表示行番号
394  uint32_t vcol = 0; // 折り返し行内の列番号
395  };
396  VisualPosition logical_to_visual(key_type lrow, uint32_t lcol) const
397  {
398  CountResult r = count_and_find(lrow);
399  VisualPosition ret;
400  ret.vrow = r.sum;
401  if (r.item) {
402  auto [row, col] = r.item->locate_column(lcol);
403  ret.vrow += row;
404  ret.vcol = col;
405  }
406  return ret;
407  }
408  // 表示行番号から論理位置を求める。countの逆変換。
409  // count(i) <= vrow < count(i+1) となる論理行 i と、その行の中で何番目の
410  // 折り返し行か(wrap_index)、その折り返し行の先頭の論理列(lcol)を返す。
411  // value 0 の行は表示行を持たないためスキップされる。
412  // vrow が総表示行数以上なら lrow = total_logical_row_count()(末尾の次)を返す。
414  uint32_t lrow = 0; // 論理行番号
415  uint32_t lcol = 0; // 論理列番号(該当折り返し行の先頭列)
416  uint32_t wrap_index = 0; // 論理行の中で何番目の折り返し行に属するか
417  };
418  LogicalPosition visual_to_logical(uint64_t vrow) const
419  {
420  key_type lrow = 0;
421  for (Node const &node : nodes_) {
422  // このNodeの表示行範囲より先ならNodeごとスキップ
423  if (vrow >= node.sum_values) {
424  vrow -= node.sum_values;
425  lrow += node.num_items;
426  continue;
427  }
428  for (Leaf const &leaf : node.leaves) {
429  // このLeafの表示行範囲より先ならLeafごとスキップ
430  if (vrow >= leaf.sum_values) {
431  vrow -= leaf.sum_values;
432  lrow += leaf.items.size();
433  continue;
434  }
435  // 該当するLeafの中を個別に引いていく
436  for (value_type const &item : leaf.items) {
437  uint32_t v = item.value(); // この論理行が占める表示行数
438  if (vrow < v) {
439  LogicalPosition ret;
440  ret.lrow = lrow;
441  ret.wrap_index = (uint32_t)vrow;
442  ret.lcol = item.column_of_row((uint32_t)vrow);
443  return ret;
444  }
445  vrow -= v;
446  lrow++;
447  }
448  }
449  }
450  // 範囲外: 全Nodeをスキップした結果、lrow は総論理行数(末尾の次)になっている
451  LogicalPosition ret;
452  ret.lrow = lrow;
453  return ret;
454  }
455  // 総論理行数
456  uint64_t total_logical_row_count() const
457  {
458  uint64_t total = 0;
459  for (Node const &node : nodes_) {
460  total += node.num_items;
461  }
462  return total;
463  }
464  // 総表示行数
465  uint64_t total_visual_row_count() const
466  {
467  uint64_t total = 0;
468  for (Node const &node : nodes_) {
469  total += node.sum_values;
470  }
471  return total;
472  }
473 };
474 
475 #endif // LINEINDEXMAP_H
Definition: LineIndexMap.h:40
std::shared_ptr< std::vector< UserData > > data_
Definition: LineIndexMap.h:43
ValueItem(uint32_t value=0)
Definition: LineIndexMap.h:46
uint32_t column_of_row(uint32_t row) const
Definition: LineIndexMap.h:85
ValueItem(std::vector< uint32_t > const &col_count_list)
Definition: LineIndexMap.h:51
std::pair< uint32_t, uint32_t > locate_column(uint32_t lcol) const
Definition: LineIndexMap.h:69
uint32_t value() const
Definition: LineIndexMap.h:59
Definition: LineIndexMap.h:29
void insert(key_type lrow, value_type item)
Definition: LineIndexMap.h:291
void update(key_type lrow, value_type value)
Definition: LineIndexMap.h:326
uint32_t key_type
Definition: LineIndexMap.h:96
void clear()
Definition: LineIndexMap.h:258
CountResult count_and_find(key_type lrow) const
Definition: LineIndexMap.h:200
uint64_t total_logical_row_count() const
Definition: LineIndexMap.h:456
std::vector< Node > nodes_
Definition: LineIndexMap.h:118
void ensure_tail()
Definition: LineIndexMap.h:124
VisualPosition logical_to_visual(key_type lrow, uint32_t lcol) const
Definition: LineIndexMap.h:396
void erase(key_type lrow)
Definition: LineIndexMap.h:357
bool validate() const
Definition: LineIndexMap.h:234
std::optional< value_type > find(key_type lrow) const
Definition: LineIndexMap.h:263
LogicalPosition visual_to_logical(uint64_t vrow) const
Definition: LineIndexMap.h:418
uint64_t count(key_type lrow) const
Definition: LineIndexMap.h:285
static constexpr size_t max_leaf_capacity
Definition: LineIndexMap.h:99
ValueItem value_type
Definition: LineIndexMap.h:97
uint64_t total_visual_row_count() const
Definition: LineIndexMap.h:465
void insert_into_leaf(size_t ni, size_t li, size_t offset, value_type item)
Definition: LineIndexMap.h:166
static constexpr size_t max_node_fanout
Definition: LineIndexMap.h:100
void split_node(size_t ni)
Definition: LineIndexMap.h:151
void recalc_node(Node *node)
Definition: LineIndexMap.h:141
Definition: LineIndexMap.h:193
uint64_t sum
Definition: LineIndexMap.h:194
ValueItem const * item
Definition: LineIndexMap.h:195
Definition: LineIndexMap.h:104
uint64_t sum_values
Definition: LineIndexMap.h:106
std::vector< value_type > items
Definition: LineIndexMap.h:105
Definition: LineIndexMap.h:413
uint32_t lcol
Definition: LineIndexMap.h:415
uint32_t lrow
Definition: LineIndexMap.h:414
uint32_t wrap_index
Definition: LineIndexMap.h:416
Definition: LineIndexMap.h:111
size_t num_items
Definition: LineIndexMap.h:113
uint64_t sum_values
Definition: LineIndexMap.h:114
std::vector< Leaf > leaves
Definition: LineIndexMap.h:112
Definition: LineIndexMap.h:32
uint32_t vcol_len
Definition: LineIndexMap.h:33
Definition: LineIndexMap.h:392
uint64_t vrow
Definition: LineIndexMap.h:393
uint32_t vcol
Definition: LineIndexMap.h:394