patchdiff 0.3.4__py3-none-any.whl → 0.3.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
patchdiff/diff.py CHANGED
@@ -1,4 +1,5 @@
1
- from functools import partial, reduce
1
+ from __future__ import annotations
2
+
2
3
  from typing import Dict, List, Set, Tuple
3
4
 
4
5
  from .pointer import Pointer
@@ -6,86 +7,129 @@ from .types import Diffable
6
7
 
7
8
 
8
9
  def diff_lists(input: List, output: List, ptr: Pointer) -> Tuple[List, List]:
9
- memory = {(0, 0): {"ops": [], "rops": [], "cost": 0}}
10
+ m, n = len(input), len(output)
11
+
12
+ # Build DP table bottom-up (iterative approach)
13
+ # dp[i][j] = cost of transforming input[0:i] to output[0:j]
14
+ dp = [[0] * (n + 1) for _ in range(m + 1)]
10
15
 
11
- def dist(i, j):
12
- if (i, j) not in memory:
13
- if i > 0 and j > 0 and input[i - 1] == output[j - 1]:
14
- step = dist(i - 1, j - 1)
16
+ # Initialize base cases
17
+ for i in range(1, m + 1):
18
+ dp[i][0] = i # Cost of deleting all elements
19
+ for j in range(1, n + 1):
20
+ dp[0][j] = j # Cost of adding all elements
21
+
22
+ # Fill DP table
23
+ for i in range(1, m + 1):
24
+ for j in range(1, n + 1):
25
+ if input[i - 1] == output[j - 1]:
26
+ # Elements match, no operation needed
27
+ dp[i][j] = dp[i - 1][j - 1]
15
28
  else:
16
- paths = []
17
- if i > 0:
18
- base = dist(i - 1, j)
19
- op = {"op": "remove", "idx": i - 1}
20
- rop = {"op": "add", "idx": j - 1, "value": input[i - 1]}
21
- paths.append(
22
- {
23
- "ops": base["ops"] + [op],
24
- "rops": base["rops"] + [rop],
25
- "cost": base["cost"] + 1,
26
- }
27
- )
28
- if j > 0:
29
- base = dist(i, j - 1)
30
- op = {"op": "add", "idx": i - 1, "value": output[j - 1]}
31
- rop = {"op": "remove", "idx": j - 1}
32
- paths.append(
33
- {
34
- "ops": base["ops"] + [op],
35
- "rops": base["rops"] + [rop],
36
- "cost": base["cost"] + 1,
37
- }
38
- )
39
- if i > 0 and j > 0:
40
- base = dist(i - 1, j - 1)
41
- op = {
42
- "op": "replace",
43
- "idx": i - 1,
44
- "original": input[i - 1],
45
- "value": output[j - 1],
46
- }
47
- rop = {
48
- "op": "replace",
49
- "idx": j - 1,
50
- "original": output[j - 1],
51
- "value": input[i - 1],
52
- }
53
- paths.append(
54
- {
55
- "ops": base["ops"] + [op],
56
- "rops": base["rops"] + [rop],
57
- "cost": base["cost"] + 1,
58
- }
59
- )
60
- step = min(paths, key=lambda a: a["cost"])
61
- memory[(i, j)] = step
62
- return memory[(i, j)]
63
-
64
- def pad(state, op, target=None):
65
- ops, padding = state
29
+ # Take minimum of three operations
30
+ dp[i][j] = min(
31
+ dp[i - 1][j] + 1, # Remove from input
32
+ dp[i][j - 1] + 1, # Add from output
33
+ dp[i - 1][j - 1] + 1, # Replace
34
+ )
35
+
36
+ # Traceback to extract operations
37
+ ops = []
38
+ rops = []
39
+ i, j = m, n
40
+
41
+ while i > 0 or j > 0:
42
+ if i > 0 and j > 0 and input[i - 1] == output[j - 1]:
43
+ # Elements match, no operation
44
+ i -= 1
45
+ j -= 1
46
+ elif i > 0 and (j == 0 or dp[i][j] == dp[i - 1][j] + 1):
47
+ # Remove from input
48
+ ops.append({"op": "remove", "idx": i - 1})
49
+ rops.append({"op": "add", "idx": j - 1, "value": input[i - 1]})
50
+ i -= 1
51
+ elif j > 0 and (i == 0 or dp[i][j] == dp[i][j - 1] + 1):
52
+ # Add from output
53
+ ops.append({"op": "add", "idx": i - 1, "value": output[j - 1]})
54
+ rops.append({"op": "remove", "idx": j - 1})
55
+ j -= 1
56
+ else:
57
+ # Replace
58
+ ops.append(
59
+ {
60
+ "op": "replace",
61
+ "idx": i - 1,
62
+ "original": input[i - 1],
63
+ "value": output[j - 1],
64
+ }
65
+ )
66
+ rops.append(
67
+ {
68
+ "op": "replace",
69
+ "idx": j - 1,
70
+ "original": output[j - 1],
71
+ "value": input[i - 1],
72
+ }
73
+ )
74
+ i -= 1
75
+ j -= 1
76
+
77
+ # Apply padding to operations (using explicit loops instead of reduce)
78
+ padded_ops = []
79
+ padding = 0
80
+ # Iterate in reverse to get correct order (traceback extracts operations backwards)
81
+ for op in reversed(ops):
66
82
  if op["op"] == "add":
67
83
  padded_idx = op["idx"] + 1 + padding
68
- idx_token = padded_idx if padded_idx < len(target) + padding else "-"
69
- full_op = {
70
- "op": "add",
71
- "path": ptr.append(idx_token),
72
- "value": op["value"],
73
- }
74
- return [ops + [full_op], padding + 1]
84
+ idx_token = padded_idx if padded_idx < len(input) + padding else "-"
85
+ padded_ops.append(
86
+ {
87
+ "op": "add",
88
+ "path": ptr.append(idx_token),
89
+ "value": op["value"],
90
+ }
91
+ )
92
+ padding += 1
75
93
  elif op["op"] == "remove":
76
- full_op = {
77
- "op": "remove",
78
- "path": ptr.append(op["idx"] + padding),
79
- }
80
- return [ops + [full_op], padding - 1]
81
- else:
94
+ padded_ops.append(
95
+ {
96
+ "op": "remove",
97
+ "path": ptr.append(op["idx"] + padding),
98
+ }
99
+ )
100
+ padding -= 1
101
+ else: # replace
82
102
  replace_ptr = ptr.append(op["idx"] + padding)
83
103
  replace_ops, _ = diff(op["original"], op["value"], replace_ptr)
84
- return [ops + replace_ops, padding]
104
+ padded_ops.extend(replace_ops)
85
105
 
86
- solution = dist(len(input), len(output))
87
- padded_ops, _ = reduce(partial(pad, target=input), solution["ops"], [[], 0])
88
- padded_rops, _ = reduce(partial(pad, target=output), solution["rops"], [[], 0])
106
+ padded_rops = []
107
+ padding = 0
108
+ # Iterate in reverse to get correct order (traceback extracts operations backwards)
109
+ for op in reversed(rops):
110
+ if op["op"] == "add":
111
+ padded_idx = op["idx"] + 1 + padding
112
+ idx_token = padded_idx if padded_idx < len(output) + padding else "-"
113
+ padded_rops.append(
114
+ {
115
+ "op": "add",
116
+ "path": ptr.append(idx_token),
117
+ "value": op["value"],
118
+ }
119
+ )
120
+ padding += 1
121
+ elif op["op"] == "remove":
122
+ padded_rops.append(
123
+ {
124
+ "op": "remove",
125
+ "path": ptr.append(op["idx"] + padding),
126
+ }
127
+ )
128
+ padding -= 1
129
+ else: # replace
130
+ replace_ptr = ptr.append(op["idx"] + padding)
131
+ replace_ops, _ = diff(op["original"], op["value"], replace_ptr)
132
+ padded_rops.extend(replace_ops)
89
133
 
90
134
  return padded_ops, padded_rops
91
135
 
@@ -125,7 +169,9 @@ def diff_sets(input: Set, output: Set, ptr: Pointer) -> Tuple[List, List]:
125
169
  return ops, rops
126
170
 
127
171
 
128
- def diff(input: Diffable, output: Diffable, ptr: Pointer = None) -> Tuple[List, List]:
172
+ def diff(
173
+ input: Diffable, output: Diffable, ptr: Pointer | None = None
174
+ ) -> Tuple[List, List]:
129
175
  if input == output:
130
176
  return [], []
131
177
  if ptr is None:
patchdiff/pointer.py CHANGED
@@ -1,9 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
- from typing import Any, Hashable, List, Tuple
4
+ from typing import Any, Hashable, Iterable, Tuple
3
5
 
4
6
  from .types import Diffable
5
7
 
6
-
7
8
  tilde0_re = re.compile("~0")
8
9
  tilde1_re = re.compile("~1")
9
10
  tilde_re = re.compile("~")
@@ -19,7 +20,7 @@ def escape(token: str) -> str:
19
20
 
20
21
 
21
22
  class Pointer:
22
- def __init__(self, tokens: List[Hashable] = None) -> None:
23
+ def __init__(self, tokens: Iterable[Hashable] | None = None) -> None:
23
24
  if tokens is None:
24
25
  tokens = []
25
26
  self.tokens = tuple(tokens)
@@ -33,7 +34,7 @@ class Pointer:
33
34
  return "/" + "/".join(escape(str(t)) for t in self.tokens)
34
35
 
35
36
  def __repr__(self) -> str:
36
- return f"Pointer({repr(list(self.tokens))})"
37
+ return f"Pointer({list(self.tokens)!r})"
37
38
 
38
39
  def __hash__(self) -> int:
39
40
  return hash(self.tokens)
@@ -62,4 +63,4 @@ class Pointer:
62
63
 
63
64
  def append(self, token: Hashable) -> "Pointer":
64
65
  """append, creating new Pointer"""
65
- return Pointer(self.tokens + (token,))
66
+ return Pointer((*self.tokens, token))
@@ -1,16 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: patchdiff
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: MIT
5
- Home-page: https://github.com/fork-tongue/patchdiff
6
- Author: Korijn van Golen
7
- Author-email: korijn@gmail.com
8
- Requires-Python: >=3.7
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.10
11
- Classifier: Programming Language :: Python :: 3.7
12
- Classifier: Programming Language :: Python :: 3.8
13
- Classifier: Programming Language :: Python :: 3.9
5
+ Project-URL: Homepage, https://github.com/fork-tongue/patchdiff
6
+ Author-email: Korijn van Golen <korijn@gmail.com>, Berend Klein Haneveld <berendkleinhaneveld@gmail.com>
7
+ Requires-Python: >=3.8
14
8
  Description-Content-Type: text/markdown
15
9
 
16
10
  [![PyPI version](https://badge.fury.io/py/patchdiff.svg)](https://badge.fury.io/py/patchdiff)
@@ -58,4 +52,3 @@ print(to_json(ops, indent=4))
58
52
  # }
59
53
  # ]
60
54
  ```
61
-
@@ -0,0 +1,9 @@
1
+ patchdiff/__init__.py,sha256=uQAwiWM9rTop0zZC6Spn0y_v74cfAzuhCxcEFOrAaXY,110
2
+ patchdiff/apply.py,sha256=25TfOTZTYFIjy70mBus5yCzmbJRawbguDffv_4yHjqE,1366
3
+ patchdiff/diff.py,sha256=JPERnHGR0oyV_E-Q4GDnjrg2dfofCrGcfrrJbNfd50c,6489
4
+ patchdiff/pointer.py,sha256=5bcOYUGPJ-bo1dEWBcQpXVsjhV46EJtDyG3RPw0VwTI,1812
5
+ patchdiff/serialize.py,sha256=N0S9e0P49TBMb7ghM--h13MsF59ybiscjZ_auAErTq8,295
6
+ patchdiff/types.py,sha256=BVKXOl3tnQOOml3VI_epTrLn79agi6sD5vNr2acC-yE,77
7
+ patchdiff-0.3.6.dist-info/METADATA,sha256=Xw0FpdVK-G5SZTtfQv_J3cj4AnySJt5A_OF7thAT0tU,1634
8
+ patchdiff-0.3.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
+ patchdiff-0.3.6.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry 1.0.8
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,9 +0,0 @@
1
- patchdiff/__init__.py,sha256=uQAwiWM9rTop0zZC6Spn0y_v74cfAzuhCxcEFOrAaXY,110
2
- patchdiff/apply.py,sha256=25TfOTZTYFIjy70mBus5yCzmbJRawbguDffv_4yHjqE,1366
3
- patchdiff/diff.py,sha256=JHEut2x4uLztJ72FuHFH_qwNbBr-U5aWhvJWsLAFB7U,5424
4
- patchdiff/pointer.py,sha256=8woatYj2inseiyb027HwwsTFTV4yNQomynLZF6TuXZ4,1767
5
- patchdiff/serialize.py,sha256=N0S9e0P49TBMb7ghM--h13MsF59ybiscjZ_auAErTq8,295
6
- patchdiff/types.py,sha256=BVKXOl3tnQOOml3VI_epTrLn79agi6sD5vNr2acC-yE,77
7
- patchdiff-0.3.4.dist-info/WHEEL,sha256=DA86_h4QwwzGeRoz62o1svYt5kGEXpoUTuTtwzoTb30,83
8
- patchdiff-0.3.4.dist-info/METADATA,sha256=7Idsz1U3AFalNosjnBX7EG3y-nm7PBVlNv-IFqdpEyU,1823
9
- patchdiff-0.3.4.dist-info/RECORD,,