列表

详情


2691. 不可变辅助工具

创建带有微小修改的不可变对象的克隆副本是一个繁琐的过程。请你编写一个名为 ImmutableHelper 的类,作为满足这一要求的工具。构造函数接受一个不可变对象 obj ,该对象将是一个 JSON 对象或数组。

该类有一个名为 produce 的方法,它接受一个名为 mutator 的函数。该函数返回一个新的对象,它与原始对象相似,但应用了这些变化。

mutator 函数接受 obj代理 版本。函数的使用者可以(看起来)对该对象进行修改,但原始对象 obj 实际上没有被改变。

例如,用户可以编写如下代码:

const originalObj = {"x": 5};
const helper = new ImmutableHelper(originalObj);
const newObj = helper.produce((proxy) => {
  proxy.x = proxy.x + 1;
});
console.log(originalObj); // {"x": 5}
console.log(newObj); // {"x": 6}

mutator 函数的属性:

关于如何测试解决方案的说明:解决方案验证器仅分析返回结果与原始 obj 之间的差异。进行完全比较的计算开销太大。此外,对原始对象进行的任何变更都将导致答案错误。

 

示例 1:

输入:
obj = {"val": 10}, 
mutators = [
  proxy => { proxy.val += 1; },
  proxy => { proxy.val -= 1; }
]
输出:
[
  {"val": 11},
  {"val": 9}
]
解释:
const helper = new ImmutableHelper({val: 10});
helper.produce(proxy => { proxy.val += 1; }); // { "val": 11 }
helper.produce(proxy => { proxy.val -= 1; }); // { "val": 9 }

示例 2:

输入:
obj = {"arr": [1, 2, 3]} 
mutators = [
 proxy => { 
   proxy.arr[0] = 5; 
   proxy.newVal = proxy.arr[0] + proxy.arr[1];
 }
]
输出:
[
  {"arr": [5, 2, 3], "newVal": 7 } 
]
解释:对原始数组进行了两次编辑。首先将数组的第一个元素设置为 5。然后添加了一个值为 7 的新键。

示例 3:

输入:
obj = {"obj": {"val": {"x": 10, "y": 20}}}
mutators = [
  proxy => { 
    let data = proxy.obj.val; 
    let temp = data.x; 
    data.x = data.y; 
    data.y = temp; 
  }
]
输出:
[
  {"obj": {"val": {"x": 20, "y": 10}}}
]
解释:交换了 "x" 和 "y" 的值。

 

提示:

原站题解

去查看

上次编辑到这里,代码来自缓存 点击恢复默认模板
var ImmutableHelper = function(obj) { }; /** * @param {Function} mutator * @return {JSON} clone of obj */ ImmutableHelper.prototype.produce = function(mutator) { }; /** * const originalObj = {"x": 5}; * const mutator = new ImmutableHelper(originalObj); * const newObj = mutator.produce((proxy) => { * proxy.x = proxy.x + 1; * }); * console.log(originalObj); // {"x: 5"} * console.log(newObj); // {"x": 6} */

typescript 解法, 执行用时: 932 ms, 内存消耗: 119.3 MB, 提交时间: 2023-10-15 14:59:48

type Obj = Record<PropertyKey, unknown> | unknown[]

/**
 * 实现immer库的produce函数.
 */
class ImmutableHelper {
  private readonly _base: Obj

  constructor(draft: Obj) {
    this._base = draft
  }

  produce(recipe: (draft: Obj) => void): Obj {
    const handler = new HandlerNode(this._base)
    recipe(new Proxy(this._base, handler))
    return handler.query()[1]
  }
}

/**
 * const originalObj = {"x": 5};
 * const mutator = new ImmutableHelper(originalObj);
 * const newObj = mutator.produce((proxy) => {
 *   proxy.x = proxy.x + 1;
 * });
 * console.log(originalObj); // {"x: 5"}
 * console.log(newObj); // {"x": 6}
 */

/**
 * 类似于TrieNode的结构.
 * @property _origin 原始对象.
 * @property _children 子节点, 保存每个儿子的handler和proxy.
 * @property _action 每个结点处的修改.
 */
class HandlerNode {
  private readonly _origin: Obj
  private readonly _children: Map<PropertyKey, [handler: HandlerNode, proxy: Obj]> = new Map()
  private readonly _action: Map<PropertyKey, Obj> = new Map()

  constructor(obj: Obj) {
    this._origin = obj
  }

  has(_: Obj, prop: PropertyKey): boolean {
    return prop in this._origin || this._action.has(prop)
  }

  get(_: Obj, prop: PropertyKey): Obj {
    if (this._action.has(prop)) return this._action.get(prop)!
    if (this._children.has(prop)) return this._children.get(prop)![1]
    const res = this._origin[prop]
    if (!isObj(res)) return res
    const handler = new HandlerNode(res)
    const proxy = new Proxy(res, handler)
    this._children.set(prop, [handler, proxy])
    return proxy
  }

  set(_: Obj, prop: PropertyKey, value: Obj): boolean {
    this._children.delete(prop)
    this._action.set(prop, value)
    return true
  }

  query(): [dirty: boolean, res: Obj] {
    const patch: Record<PropertyKey, Obj> = Object.create(null)
    let dirty = this.dirty
    this._children.forEach(([childHandler], key) => {
      const [childDirty, childRes] = childHandler.query()
      if (childDirty) {
        dirty = true
        patch[key] = childRes
      }
    })

    if (!dirty) return [false, this._origin]
    const res = { ...this._origin, ...patch }
    this._action.forEach((value, key) => {
      res[key] = value
    })
    return [true, res]
  }

  get dirty(): boolean {
    return !!this._action.size
  }
}

function isObj(obj: unknown): obj is Obj {
  return typeof obj === 'object' && obj !== null
}

上一题