Skip to content

Commit 16599a0

Browse files
committed
add basic diffing
1 parent 8d598ee commit 16599a0

File tree

3 files changed

+89
-7
lines changed

3 files changed

+89
-7
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
*.pyc
2+
.vscode

patchdiff/__init__.py

+62-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,67 @@
1-
from typing import Dict, List, Set, Tuple, Union
1+
from typing import Dict, List
22

33

44
__version__ = "0.1.0"
55

66

7-
def diff(a: Union[Dict, List, Set, Tuple], b: Union[Dict, List, Set, Tuple]):
8-
pass
7+
def diff_lists(input: List, output: List):
8+
memory = {(0, 0): {"ops": [], "cost": 0}}
9+
10+
def dist(i, j):
11+
if (i, j) not in memory:
12+
if i > 0 and j > 0 and not diff(input[i - 1], output[j - 1]):
13+
step = dist(i - 1, j - 1)
14+
else:
15+
paths = []
16+
if i > 0:
17+
base = dist(i - 1, j)
18+
op = {"op": "remove", "idx": i - 1}
19+
paths.append({
20+
"ops": base["ops"] + [op],
21+
"cost": base["cost"] + 1,
22+
})
23+
if j > 0:
24+
base = dist(i, j - 1)
25+
op = {"op": "add", "idx": j - 1, "value": output[j - 1]}
26+
paths.append({
27+
"ops": base["ops"] + [op],
28+
"cost": base["cost"] + 1,
29+
})
30+
if i > 0 and j > 0:
31+
base = dist(i - 1, j - 1)
32+
op = {"op": "replace", "idx": i - 1, "original": input[i - 1], "value": output[j - 1]}
33+
paths.append({
34+
"ops": base["ops"] + [op],
35+
"cost": base["cost"] + 1,
36+
})
37+
step = min(paths, key=lambda a: a["cost"])
38+
memory[(i, j)] = step
39+
return memory[(i, j)]
40+
41+
return dist(len(input), len(output))["ops"]
42+
43+
44+
def diff_dicts(input: Dict, output: Dict):
45+
ops = []
46+
input_keys = set(input.keys())
47+
output_keys = set(output.keys())
48+
for key in input_keys - output_keys:
49+
ops.append({"op": "remove", "key": key})
50+
for key in output_keys - input_keys:
51+
ops.append({"op": "add", "key": key, "value": output[key]})
52+
for key in input_keys & output_keys:
53+
ops.extend(diff(input[key], output[key]))
54+
return ops
55+
56+
57+
def diff(input, output):
58+
# TODO: track paths
59+
# TODO: properly check equality
60+
if input == output:
61+
return []
62+
if isinstance(input, list) and isinstance(output, list):
63+
return diff_lists(input, output)
64+
if isinstance(input, dict) and isinstance(output, dict):
65+
return diff_dicts(input, output)
66+
# TODO: sets, tuples
67+
return [{"op": "replace", "value": output}]

tests/test_all.py

+26-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,29 @@
11
from patchdiff import diff
22

33

4-
def test_all():
5-
a = [1, 5, 9, "sdfsdf"]
6-
b = ["sdf", "c"]
7-
assert diff(a, b)
4+
def test_list():
5+
a = [1, 5, 9, "sdfsdf", "fff"]
6+
b = ["sdf", 5, 9, "c"]
7+
ops = diff(a, b)
8+
9+
assert len(ops) == 3
10+
assert (ops[0]["op"], ops[0]["idx"]) == ("replace", 0)
11+
assert (ops[1]["op"], ops[1]["idx"]) == ("replace", 3)
12+
assert (ops[2]["op"], ops[2]["idx"]) == ("remove", 4)
13+
14+
15+
def test_dicts():
16+
a = {
17+
"a": 5,
18+
"b": 6,
19+
}
20+
b = {
21+
"a": 3,
22+
"b": 6,
23+
"c": 7
24+
}
25+
ops = diff(a, b)
26+
27+
assert len(ops) == 2
28+
assert (ops[0]["op"], ops[0]["key"]) == ("add", "c")
29+
assert (ops[1]["op"], ops[1]["value"]) == ("replace", 3)

0 commit comments

Comments
 (0)