iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
佛心分享-SideProject30

Mongory:打造跨語言、高效能的萬用查詢引擎系列 第 13

Day 12:動態 Array 設計:Ruby 風格 API 與介面設計

  • 分享至 

  • xImage
  •  

這一篇承接 Day 11 的 mongory_value,筆者帶讀者走一遍 C core 裡的動態陣列 mongory_array:為什麼長這樣、怎麼用、有哪些邊界與測試重點
整體 API 以 Ruby 習慣為靈感:push/get/set/each/sort_by/includes,同時確保擴容策略簡潔、可觀測、易於與 Matcher 串接。

設計目標

  • 可預期的性能:倍增擴容(amortized O(1) push)、需要時才擴。
  • 易用介面:函式指標掛在結構體上,像 Ruby 物件方法一樣呼叫。
  • 可觀測:錯誤集中寫入 pool->error,便於測試/回報。
  • mongory_value 深度整合:比對/字串化皆走一致入口。

介面一覽(對照 Ruby 風格)

  • mongory_array_new(pool):建立新陣列(初始容量 4)。
  • array->push(array, value):尾端加入元素;自動擴容(倍增)。
  • array->get(array, index):取得元素;越界回傳 NULL
  • array->set(array, index, value):設定元素;若跨越目前長度,會用 NULL 補齊中間空洞並更新 count
  • array->each(array, acc, callback):逐一走訪;callback(item, acc) 回傳 false 可提前停止。
  • mongory_array_sort_by(array, temp_pool, ctx, callback):依 key 排序,回傳新陣列;callback(value, ctx) -> size_t key
  • mongory_array_includes(array, value):是否包含;比較走 mongory_value->comp

對照 Ruby:

  • Ruby arr.push(x) → C array->push(array, x)
  • Ruby arr[i] → C array->get(array, i);Ruby arr[i] = x → C array->set(array, i, x)
  • Ruby arr.each { |x| } → C array->each(array, acc, cb)
  • Ruby arr.sort_by { |x| key } → C mongory_array_sort_by(array, tmp, ctx, cb)

最小可執行範例(C)

以下展示 push/get/each/sort_by 的基本用法(以整數為例):

#include <mongory-core/foundations/memory_pool.h>
#include <mongory-core/foundations/array.h>
#include <mongory-core/foundations/value.h>
#include <stdio.h>

static bool print_item(mongory_value *item, void *acc) {
  mongory_memory_pool *pool = (mongory_memory_pool *)acc;
  char *s = item->to_str(item, pool);
  printf("%s\n", s);
  return true;
}

static size_t key_of(mongory_value *item, void *ctx) {
  // 以整數值作為排序 key(小到大)
  (void)ctx;
  return (size_t)item->data.i64;
}

int main() {
  mongory_init();
  mongory_memory_pool *pool = mongory_memory_pool_new();
  mongory_array *array = mongory_array_new(pool);

  array->push(array, mongory_value_wrap_int(pool, 3));
  array->push(array, mongory_value_wrap_int(pool, 1));
  array->push(array, mongory_value_wrap_int(pool, 2));

  // get / set
  mongory_value *v1 = array->get(array, 1); // 1
  printf("get[1]=%lld\n", v1->data.i64);
  array->set(array, 5, mongory_value_wrap_int(pool, 9)); // 自動補 NULL,count 變為 6

  // each(列印)
  array->each(array, pool, print_item);

  // sort_by(回傳新陣列,不就地排序)
  mongory_array *sorted = mongory_array_sort_by(array, pool, NULL, key_of);
  printf("-- sorted --\n");
  sorted->each(sorted, pool, print_item);

  mongory_memory_pool_free(pool);
  mongory_cleanup();
  return 0;
}

擴容策略與複雜度

  • 初始容量 4,容量不足時倍增(4→8→16→…),直到可容納目標索引或 push 需求。
  • push 攤銷 O(1),擴容發生時計 O(n) 搬移指標。
  • set 若直接跳到遠端索引,會補 NULL 形成「洞洞」;count 會被推進到 index+1

邊界與例外(務必注意)

  • get 越界回 NULL;呼叫端需判斷。
  • set 造成的 NULL 空洞會影響部分操作:
    • includes 內部直接取出元素後呼叫 item->comp;若該位置為 NULL 將不安全。建議在有空洞情境先避免 includes,或先以實值補齊。
    • each 會把 NULL 交給回呼;回呼需自保(可檢查 item == NULL)。
  • sort_by 使用合併排序實作,會在 temp_pool 產生中介陣列;是否穩定排序取決於 callback 與實作,不保證穩定。
  • 任何配置失敗或違規呼叫都會把錯誤寫到 pool->error(集中回報)。

與 Matchers 的關係(為何要 Ruby 風)

  • $every$elemMatch 等操作會頻繁走 array->each;以「方法掛在物件」的風格,可讀性更接近 Ruby。
  • Query 的解構常見「先 get/再遞迴 match」,因此 get/set 的語意需簡單直覺、邊界明確。
  • sort_byincludes 可支援測試/診斷用例(例如 explain/trace 的輸出排序一致性)。

測試清單(Unity)

  • 建立/初值:new 後容量/計數與函式指標皆正確。
  • push/擴容:超過 4/8/16 觸發擴容,元素順序不變。
  • get/set 邊界:越界 getNULL;跨距 set 形成空洞,count 正確。
  • each:可走訪全部;回呼回 false 可提前停止;能處理 NULL
  • sort_by:小集合/大集合排序正確;不同 key(含相等)表現;temp_pool 釋放後不影響原陣列。
  • includes:命中/未命中;有空洞時行為(建議避免或先補值)。
  • 錯誤處理:刻意製造配置失敗,pool->error 被寫入。

小結與預告

  • mongory_array 提供 Ruby 風 API 與可預期的擴容行為,讓上層 Matchers(特別是 $every/$elemMatch)易於組合與觀測。
  • 下一篇 Day 13 會延伸到 Table(雜湊、碰撞與 rehash 取捨),完成基礎容器三件組(Value/Array/Table)。

專案(C Core)


上一篇
Day 11:Value wrapper 與 function pointer dispatch
下一篇
Day 13:Hash Table(Table):bucket/雜湊/衝突策略取捨
系列文
Mongory:打造跨語言、高效能的萬用查詢引擎26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言