跳至內容
Astera Technology

合併欄位改動鏈,仍保存逐步紀錄

先核前後銜接,才取最初及最後值。

下載完整個案資料及參考答案 ↓

來源格式與預期記錄

這是原創假設轉換例子。輸入限下列欄位的普通、無循環 JSON 形記錄,統一記錄格式及拒絕代碼由本例訂明。若換來源,先修改格式約定。

每次改動要有非空 field,from、to 只用文字、布林、有限數字或 null。同一欄下一次 from 須與上次 to 同類型且相等,數字正零、負零視為相等。按欄合成 before、after 及 trace,逐步仍記原位置。不能銜接便整筆拒絕,值未變的事件也保留。

可修改的假設轉換個案
個案輸入記錄(JSON)預期輸出(JSON)
整理連續欄位歷史{"events":[{"field":"title","from":"A","to":"B"},{"field":"title","from":"B","to":"C"}]}{"ok":true,"value":{"changes":[{"field":"title","before":"A","after":"C","trace":[{"position":1,"from":"A","to":"B"},{"position":2,"from":"B","to":"C"}]}]}}
拒絕歷史接不上{"events":[{"field":"title","from":"A","to":"B"},{"field":"title","from":"X","to":"C"}]}{"ok":false,"error":"AUDIT_CONTINUITY"}
保留沒有改值的事件{"events":[{"field":"title","from":"A","to":"A"}]}{"ok":true,"value":{"changes":[{"field":"title","before":"A","after":"A","trace":[{"position":1,"from":"A","to":"A"}]}]}}
下載可編輯 CSV ↓

在本機執行轉換

把完整例子以 JavaScript 模組在 Node.js 執行,內含純轉換函數、三個輸入及寫明的預期輸出,對不上便由斷言停止。拒絕個案回傳 ok=false 及代碼,不回傳半份統一記錄。

完整函數及可執行個案
import assert from 'node:assert/strict';

function auditChain(i){
  const fail=error=>({ok:false,error});if(!i||!Array.isArray(i.events))return fail('AUDIT_EVENTS');
  const scalar=v=>v===null||typeof v==='string'||typeof v==='boolean'||(typeof v==='number'&&Number.isFinite(v));
  const fields=new Map();
  for(let n=0;n<i.events.length;n++){
    const e=i.events[n];if(!e||typeof e.field!=='string'||!e.field||!scalar(e.from)||!scalar(e.to))return fail('AUDIT_VALUE');
    let f=fields.get(e.field);
    if(f&&f.after!==e.from)return fail('AUDIT_CONTINUITY');
    if(!f){f={field:e.field,before:e.from,after:e.from,trace:[]};fields.set(e.field,f);}
    f.after=e.to;f.trace.push({position:n+1,from:e.from,to:e.to});
  }
  return {ok:true,value:{changes:[...fields.values()]}};
}

// Original synthetic cases; expected outputs are literal records.
const cases = [
  {
    "label": "Fold a continuous field history",
    "input": {
      "events": [
        {
          "field": "title",
          "from": "A",
          "to": "B"
        },
        {
          "field": "title",
          "from": "B",
          "to": "C"
        }
      ]
    },
    "expected": {
      "ok": true,
      "value": {
        "changes": [
          {
            "field": "title",
            "before": "A",
            "after": "C",
            "trace": [
              {
                "position": 1,
                "from": "A",
                "to": "B"
              },
              {
                "position": 2,
                "from": "B",
                "to": "C"
              }
            ]
          }
        ]
      }
    }
  },
  {
    "label": "Reject broken continuity",
    "input": {
      "events": [
        {
          "field": "title",
          "from": "A",
          "to": "B"
        },
        {
          "field": "title",
          "from": "X",
          "to": "C"
        }
      ]
    },
    "expected": {
      "ok": false,
      "error": "AUDIT_CONTINUITY"
    }
  },
  {
    "label": "Preserve unchanged event",
    "input": {
      "events": [
        {
          "field": "title",
          "from": "A",
          "to": "A"
        }
      ]
    },
    "expected": {
      "ok": true,
      "value": {
        "changes": [
          {
            "field": "title",
            "before": "A",
            "after": "A",
            "trace": [
              {
                "position": 1,
                "from": "A",
                "to": "A"
              }
            ]
          }
        ]
      }
    }
  }
];
for (const sample of cases) {
  const actual = auditChain(sample.input);
  assert.deepEqual(actual, sample.expected);
  console.log(sample.label, JSON.stringify(actual));
}
下載可執行例子 ↓

判清邊界個案

title 由 A→B→C,除了最後 C 還保留甚麼?

查看參考答案

before=A、after=C,位置 1、2 的兩次事件都保存。若 A→B 後是 X→C,則 AUDIT_CONTINUITY 拒絕。輸出描述改動,未執行更新。

相關資源