11"""
22Algorithm 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
1248def 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
76114def 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
119146if __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 \n Minimum cost: " + str (cost ))
147+ # Run the doctests in this file
148+ doctest .testmod ()
0 commit comments