Approach
Depth-first search
For Immutability Helper, the implementation follows one branch at a time, making it suitable for components, trees, backtracking, or dependency exploration.
- Define the state carried into one recursive or stack frame.
- Mark or choose the current state before exploring children.
- Combine child results or undo the choice when the branch finishes.
Code notes
- 115 lines of TypeScript from the credited upstream file 2691.ts.
- The implementation visibly relies on sequence storage, ordered lookup.
- 2 loop blocks detected.
Complexity
Count unique states for graph traversal; for backtracking, count the branching factor and maximum depth.
Check the problem constraints before deciding whether this complexity will pass.
Use this to learn the idea, then write your own version.
1type JSONValue =2 | null3 | boolean4 | number5 | string6 | JSONValue[]7 | { [key: string]: JSONValue };8type InputObj = Record<string, JSONValue> | Array<JSONValue>;9 10type RecursiveHandler = {11 set: (target: any, prop: string, value: any) => boolean;12 get: (target: any, prop: string) => unknown;13};14 15const isObject = (o: any) => o !== null && typeof o === 'object';16 1718class AccessHistory {19 value: JSONValue | null = null;20 props: Map<string, AccessHistory> = new Map();21}22 23class ImmutableHelper {24 private obj: InputObj;25 26 constructor(obj: InputObj) {27 this.obj = obj;28 }29 30 produce(mutator: (obj: InputObj) => void): InputObj {31 32 function createProxiedObj(33 obj: InputObj,34 accessHistory: AccessHistory35 ): InputObj {36 const handler: RecursiveHandler = {37 38 set(_, prop, value) {39 if (!accessHistory.props.has(prop)) {40 accessHistory.props.set(prop, new AccessHistory());41 }42 accessHistory.props.get(prop)!.value = value;43 return true;44 },45 46 get(_, prop) {47 if (accessHistory.value !== null) {48 return accessHistory.value;49 }50 if (!accessHistory.props.has(prop)) {51 accessHistory.props.set(prop, new AccessHistory());52 }53 if (accessHistory.props.get(prop)!.value !== null) {54 return accessHistory.props.get(prop)!.value;55 }56 if (isObject(obj[prop])) {57 58 return createProxiedObj(59 obj[prop] as InputObj,60 accessHistory.props.get(prop)! as AccessHistory61 );62 }63 return obj[prop];64 },65 };66 return new Proxy(obj, handler);67 }68 69 70 71 function deleteUnmutatedProps(accessHistory: AccessHistory): boolean {72 if (accessHistory.value !== null) {73 return true;74 }75 let hasMutation = false;76 for (const [prop, childAccessHistory] of [...accessHistory.props]) {77 if (deleteUnmutatedProps(childAccessHistory)) {78 hasMutation = true;79 } else {80 accessHistory.props.delete(prop);81 }82 }83 return hasMutation;84 }85 86 87 function transform(obj: InputObj, accessHistory: AccessHistory): InputObj {88 if (accessHistory.value !== null) {89 return accessHistory.value as InputObj;90 }91 if (accessHistory.props.size === 0) {92 return obj;93 }94 if (!isObject(obj)) {95 return obj;96 }97 let clone = Array.isArray(obj) ? [...obj] : { ...obj };98 for (const [prop, childAccessHistory] of [...accessHistory.props]) {99 clone[prop] = transform(obj[prop] as InputObj, childAccessHistory);100 }101 return clone;102 }103 104 const accessHistory = new AccessHistory();105 const proxiedObj = createProxiedObj(this.obj, accessHistory);106 107 108 mutator(proxiedObj);109 110 deleteUnmutatedProps(accessHistory);111 112 return transform(this.obj, accessHistory);113 }114}115