Skip to content

Commit 2b3d13f

Browse files
committed
docs: add doctests for min_cost_string_conversion
1 parent a71618f commit 2b3d13f

File tree

1 file changed

+97
-119
lines changed

1 file changed

+97
-119
lines changed
Lines changed: 97 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,48 @@
11
"""
22
Algorithm for calculating the most cost-efficient sequence for converting one string
3-
into another.
4-
The only allowed operations are
5-
--- Cost to copy a character is copy_cost
6-
--- Cost to replace a character is replace_cost
7-
--- Cost to delete a character is delete_cost
8-
--- Cost to insert a character is insert_cost
3+
into another (Levenshtein distance).
4+
The allowed operations are insertion, deletion, or substitution of a single character.
95
"""
6+
import doctest
7+
8+
9+
def min_cost_string_conversion(str1: str, str2: str) -> int:
10+
"""
11+
Calculates the minimum cost (standard Levenshtein distance) to convert str1 to str2.
12+
The cost of insertion, deletion, and substitution is 1. The cost of a copy is 0.
13+
14+
This is a user-friendly wrapper around the more detailed compute_transform_tables.
15+
16+
Args:
17+
str1: The source string.
18+
str2: The target string.
19+
20+
Returns:
21+
The minimum number of operations required to convert str1 to str2.
22+
23+
Doctests:
24+
>>> min_cost_string_conversion("apple", "apply")
25+
1
26+
>>> min_cost_string_conversion("sunday", "saturday")
27+
3
28+
>>> min_cost_string_conversion("test", "test")
29+
0
30+
>>> min_cost_string_conversion("", "")
31+
0
32+
>>> min_cost_string_conversion("kitten", "sitting")
33+
3
34+
>>> min_cost_string_conversion("flaw", "lawn")
35+
2
36+
"""
37+
costs, _ = compute_transform_tables(
38+
source_string=str1,
39+
destination_string=str2,
40+
copy_cost=0,
41+
replace_cost=1,
42+
delete_cost=1,
43+
insert_cost=1,
44+
)
45+
return costs[-1][-1]
1046

1147

1248
def compute_transform_tables(
@@ -18,153 +54,95 @@ def compute_transform_tables(
1854
insert_cost: int,
1955
) -> tuple[list[list[int]], list[list[str]]]:
2056
"""
21-
Finds the most cost efficient sequence
22-
for converting one string into another.
23-
24-
>>> costs, operations = compute_transform_tables("cat", "cut", 1, 2, 3, 3)
25-
>>> costs[0][:4]
26-
[0, 3, 6, 9]
27-
>>> costs[2][:4]
28-
[6, 4, 3, 6]
29-
>>> operations[0][:4]
30-
['0', 'Ic', 'Iu', 'It']
31-
>>> operations[3][:4]
32-
['Dt', 'Dt', 'Rtu', 'Ct']
33-
34-
>>> compute_transform_tables("", "", 1, 2, 3, 3)
35-
([[0]], [['0']])
57+
Finds the most cost efficient sequence for converting one string into another
58+
using dynamic programming with specified costs.
59+
60+
Doctests:
61+
>>> costs, operations = compute_transform_tables("cat", "cut", 1, 2, 3, 3)
62+
>>> costs[0][:4]
63+
[0, 3, 6, 9]
64+
>>> costs[2][:4]
65+
[6, 4, 3, 6]
66+
>>> operations[0][:4]
67+
['0', 'Ic', 'Iu', 'It']
68+
>>> operations[3][:4]
69+
['Dt', 'Dt', 'Rtu', 'Ct']
70+
>>> compute_transform_tables("", "", 1, 2, 3, 3)
71+
([[0]], [['0']])
3672
"""
37-
source_seq = list(source_string)
38-
destination_seq = list(destination_string)
39-
len_source_seq = len(source_seq)
40-
len_destination_seq = len(destination_seq)
73+
len_source_seq = len(source_string)
74+
len_destination_seq = len(destination_string)
4175
costs = [
4276
[0 for _ in range(len_destination_seq + 1)] for _ in range(len_source_seq + 1)
4377
]
4478
ops = [
45-
["0" for _ in range(len_destination_seq + 1)] for _ in range(len_source_seq + 1)
79+
["0" for _ in range(len_destination_seq + 1)]
80+
for _ in range(len_source_seq + 1)
4681
]
4782

4883
for i in range(1, len_source_seq + 1):
4984
costs[i][0] = i * delete_cost
50-
ops[i][0] = f"D{source_seq[i - 1]}"
85+
ops[i][0] = f"D{source_string[i - 1]}"
5186

52-
for i in range(1, len_destination_seq + 1):
53-
costs[0][i] = i * insert_cost
54-
ops[0][i] = f"I{destination_seq[i - 1]}"
87+
for j in range(1, len_destination_seq + 1):
88+
costs[0][j] = j * insert_cost
89+
ops[0][j] = f"I{destination_string[j - 1]}"
5590

5691
for i in range(1, len_source_seq + 1):
5792
for j in range(1, len_destination_seq + 1):
58-
if source_seq[i - 1] == destination_seq[j - 1]:
93+
# Cost of copying or replacing the current character
94+
if source_string[i - 1] == destination_string[j - 1]:
5995
costs[i][j] = costs[i - 1][j - 1] + copy_cost
60-
ops[i][j] = f"C{source_seq[i - 1]}"
96+
ops[i][j] = f"C{source_string[i - 1]}"
6197
else:
6298
costs[i][j] = costs[i - 1][j - 1] + replace_cost
63-
ops[i][j] = f"R{source_seq[i - 1]}" + str(destination_seq[j - 1])
99+
ops[i][j] = f"R{source_string[i - 1]}{destination_string[j - 1]}"
64100

101+
# Check if deleting from source is cheaper
65102
if costs[i - 1][j] + delete_cost < costs[i][j]:
66103
costs[i][j] = costs[i - 1][j] + delete_cost
67-
ops[i][j] = f"D{source_seq[i - 1]}"
104+
ops[i][j] = f"D{source_string[i - 1]}"
68105

106+
# Check if inserting into destination is cheaper
69107
if costs[i][j - 1] + insert_cost < costs[i][j]:
70108
costs[i][j] = costs[i][j - 1] + insert_cost
71-
ops[i][j] = f"I{destination_seq[j - 1]}"
109+
ops[i][j] = f"I{destination_string[j - 1]}"
72110

73111
return costs, ops
74112

75113

76114
def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]:
77115
"""
78-
Assembles the transformations based on the ops table.
79-
80-
>>> ops = [['0', 'Ic', 'Iu', 'It'],
81-
... ['Dc', 'Cc', 'Iu', 'It'],
82-
... ['Da', 'Da', 'Rau', 'Rat'],
83-
... ['Dt', 'Dt', 'Rtu', 'Ct']]
84-
>>> x = len(ops) - 1
85-
>>> y = len(ops[0]) - 1
86-
>>> assemble_transformation(ops, x, y)
87-
['Cc', 'Rau', 'Ct']
88-
89-
>>> ops1 = [['0']]
90-
>>> x1 = len(ops1) - 1
91-
>>> y1 = len(ops1[0]) - 1
92-
>>> assemble_transformation(ops1, x1, y1)
93-
[]
94-
95-
>>> ops2 = [['0', 'I1', 'I2', 'I3'],
96-
... ['D1', 'C1', 'I2', 'I3'],
97-
... ['D2', 'D2', 'R23', 'R23']]
98-
>>> x2 = len(ops2) - 1
99-
>>> y2 = len(ops2[0]) - 1
100-
>>> assemble_transformation(ops2, x2, y2)
101-
['C1', 'I2', 'R23']
116+
Assembles the list of transformations based on the ops table.
117+
118+
Doctests:
119+
>>> ops = [['0', 'Ic', 'Iu', 'It'],
120+
... ['Dc', 'Cc', 'Iu', 'It'],
121+
... ['Da', 'Da', 'Rau', 'Rat'],
122+
... ['Dt', 'Dt', 'Rtu', 'Ct']]
123+
>>> x = len(ops) - 1
124+
>>> y = len(ops[0]) - 1
125+
>>> assemble_transformation(ops, x, y)
126+
['Cc', 'Rau', 'Ct']
127+
>>> ops1 = [['0']]
128+
>>> x1 = len(ops1) - 1
129+
>>> y1 = len(ops1[0]) - 1
130+
>>> assemble_transformation(ops1, x1, y1)
131+
[]
102132
"""
103133
if i == 0 and j == 0:
104134
return []
105-
elif ops[i][j][0] in {"C", "R"}:
135+
op_code = ops[i][j][0]
136+
if op_code in {"C", "R"}:
106137
seq = assemble_transformation(ops, i - 1, j - 1)
107-
seq.append(ops[i][j])
108-
return seq
109-
elif ops[i][j][0] == "D":
138+
elif op_code == "D":
110139
seq = assemble_transformation(ops, i - 1, j)
111-
seq.append(ops[i][j])
112-
return seq
113-
else:
140+
else: # op_code == "I"
114141
seq = assemble_transformation(ops, i, j - 1)
115-
seq.append(ops[i][j])
116-
return seq
142+
seq.append(ops[i][j])
143+
return seq
117144

118145

119146
if __name__ == "__main__":
120-
_, operations = compute_transform_tables("Python", "Algorithms", -1, 1, 2, 2)
121-
122-
m = len(operations)
123-
n = len(operations[0])
124-
sequence = assemble_transformation(operations, m - 1, n - 1)
125-
126-
string = list("Python")
127-
i = 0
128-
cost = 0
129-
130-
with open("min_cost.txt", "w") as file:
131-
for op in sequence:
132-
print("".join(string))
133-
134-
if op[0] == "C":
135-
file.write("%-16s" % "Copy %c" % op[1]) # noqa: UP031
136-
file.write("\t\t\t" + "".join(string))
137-
file.write("\r\n")
138-
139-
cost -= 1
140-
elif op[0] == "R":
141-
string[i] = op[2]
142-
143-
file.write("%-16s" % ("Replace %c" % op[1] + " with " + str(op[2]))) # noqa: UP031
144-
file.write("\t\t" + "".join(string))
145-
file.write("\r\n")
146-
147-
cost += 1
148-
elif op[0] == "D":
149-
string.pop(i)
150-
151-
file.write("%-16s" % "Delete %c" % op[1]) # noqa: UP031
152-
file.write("\t\t\t" + "".join(string))
153-
file.write("\r\n")
154-
155-
cost += 2
156-
else:
157-
string.insert(i, op[1])
158-
159-
file.write("%-16s" % "Insert %c" % op[1]) # noqa: UP031
160-
file.write("\t\t\t" + "".join(string))
161-
file.write("\r\n")
162-
163-
cost += 2
164-
165-
i += 1
166-
167-
print("".join(string))
168-
print("Cost: ", cost)
169-
170-
file.write("\r\nMinimum cost: " + str(cost))
147+
# Run the doctests in this file
148+
doctest.testmod()

0 commit comments

Comments
 (0)