0
# Edit Operations
1
2
Functions for analyzing and manipulating edit operation sequences that transform one string into another. These functions provide detailed analysis of the specific changes needed and support conversion between different operation formats.
3
4
## Capabilities
5
6
### Edit Operations (Triples)
7
8
Find sequence of edit operations transforming one string to another, returning operations as triples (operation, source_pos, dest_pos).
9
10
```python { .api }
11
def editops(*args):
12
"""
13
Find sequence of edit operations transforming one string to another.
14
15
Two calling patterns:
16
- editops(source_string, destination_string): Find operations
17
- editops(opcodes, source_length, destination_length): Convert from opcodes
18
19
Parameters:
20
- source_string: Source string or opcodes list for conversion
21
- destination_string: Destination string or source length for conversion
22
- (conversion mode): destination_length for conversion
23
24
Returns:
25
list: List of tuples (operation, source_pos, dest_pos) where:
26
- operation: 'delete', 'insert', or 'replace'
27
- source_pos: Position in source string
28
- dest_pos: Position in destination string
29
"""
30
```
31
32
**Usage Examples:**
33
34
```python
35
import Levenshtein
36
37
# Find edit operations between strings
38
ops = Levenshtein.editops("spam", "park")
39
print(ops) # [('delete', 0, 0), ('insert', 3, 2), ('replace', 3, 3)]
40
41
# Convert from opcodes to editops
42
opcodes_list = [('delete', 0, 1, 0, 0), ('equal', 1, 3, 0, 2),
43
('insert', 3, 3, 2, 3), ('replace', 3, 4, 3, 4)]
44
editops_list = Levenshtein.editops(opcodes_list, len("spam"), len("park"))
45
print(editops_list) # Converted edit operations
46
```
47
48
### Opcodes (5-tuples)
49
50
Find sequence of edit operations in SequenceMatcher-compatible format, returning 5-tuples that include range information.
51
52
```python { .api }
53
def opcodes(*args):
54
"""
55
Find sequence of edit operations in SequenceMatcher format.
56
57
Two calling patterns:
58
- opcodes(source_string, destination_string): Find operations
59
- opcodes(editops, source_length, destination_length): Convert from editops
60
61
Parameters:
62
- source_string: Source string or editops list for conversion
63
- destination_string: Destination string or source length for conversion
64
- (conversion mode): destination_length for conversion
65
66
Returns:
67
list: List of 5-tuples (operation, start1, end1, start2, end2) where:
68
- operation: 'delete', 'insert', 'replace', or 'equal'
69
- start1, end1: Range in source string
70
- start2, end2: Range in destination string
71
"""
72
```
73
74
**Usage Examples:**
75
76
```python
77
import Levenshtein
78
79
# Find opcodes between strings
80
ops = Levenshtein.opcodes("spam", "park")
81
for op in ops:
82
print(op)
83
# Output:
84
# ('delete', 0, 1, 0, 0)
85
# ('equal', 1, 3, 0, 2)
86
# ('insert', 3, 3, 2, 3)
87
# ('replace', 3, 4, 3, 4)
88
89
# Convert from editops to opcodes
90
editops_list = [('delete', 0, 0), ('insert', 3, 2), ('replace', 3, 3)]
91
opcodes_list = Levenshtein.opcodes(editops_list, len("spam"), len("park"))
92
print(opcodes_list) # Converted opcodes
93
```
94
95
### Matching Blocks
96
97
Find identical blocks in two strings from edit operations, compatible with SequenceMatcher's get_matching_blocks() output.
98
99
```python { .api }
100
def matching_blocks(edit_operations, source_string, destination_string):
101
"""
102
Find identical blocks in two strings from edit operations.
103
104
Parameters:
105
- edit_operations: List of editops or opcodes
106
- source_string: Source string or its length as int
107
- destination_string: Destination string or its length as int
108
109
Returns:
110
list: List of triples (source_pos, dest_pos, length) representing matching blocks.
111
Always ends with a zero-length block for compatibility.
112
"""
113
```
114
115
**Usage Examples:**
116
117
```python
118
import Levenshtein
119
120
# Get matching blocks from edit operations
121
a, b = "spam", "park"
122
ops = Levenshtein.editops(a, b)
123
blocks = Levenshtein.matching_blocks(ops, a, b)
124
print(blocks) # [(1, 0, 2), (4, 4, 0)]
125
126
# Works with string lengths too
127
blocks = Levenshtein.matching_blocks(ops, len(a), len(b))
128
print(blocks) # Same result
129
130
# Extract matching substrings
131
a, b = "dog kennels", "mattresses"
132
ops = Levenshtein.editops(a, b)
133
blocks = Levenshtein.matching_blocks(ops, a, b)
134
matches_a = ''.join([a[block[0]:block[0]+block[2]] for block in blocks])
135
matches_b = ''.join([b[block[1]:block[1]+block[2]] for block in blocks])
136
print(f"Matching parts: '{matches_a}' == '{matches_b}'") # 'ees' == 'ees'
137
```
138
139
### Apply Edit Operations
140
141
Apply a sequence of edit operations to transform a string, supporting both complete and partial operation sequences.
142
143
```python { .api }
144
def apply_edit(edit_operations, source_string, destination_string):
145
"""
146
Apply sequence of edit operations to transform a string.
147
148
Parameters:
149
- edit_operations: List of editops or opcodes to apply
150
- source_string: Source string to transform
151
- destination_string: Destination string (for reference/validation)
152
153
Returns:
154
str: Transformed string after applying operations
155
"""
156
```
157
158
**Usage Examples:**
159
160
```python
161
import Levenshtein
162
163
# Apply complete edit sequence
164
source = "man"
165
dest = "scotsman"
166
ops = Levenshtein.editops(source, dest)
167
result = Levenshtein.apply_edit(ops, source, dest)
168
print(f"'{source}' -> '{result}'") # 'man' -> 'scotsman'
169
170
# Apply partial edit sequence
171
partial_ops = ops[:3] # First 3 operations only
172
partial_result = Levenshtein.apply_edit(partial_ops, source, dest)
173
print(f"Partial: '{source}' -> '{partial_result}'") # 'man' -> 'scoman'
174
175
# Works with opcodes too
176
opcodes_list = Levenshtein.opcodes(source, dest)
177
result = Levenshtein.apply_edit(opcodes_list, source, dest)
178
print(f"With opcodes: '{source}' -> '{result}'") # 'man' -> 'scotsman'
179
```
180
181
### Subtract Edit Operations
182
183
Subtract an edit subsequence from a sequence, creating operations that complete the transformation after a partial application.
184
185
```python { .api }
186
def subtract_edit(edit_operations, subsequence):
187
"""
188
Subtract an edit subsequence from an operation sequence.
189
190
Parameters:
191
- edit_operations: Complete list of edit operations
192
- subsequence: Ordered subset of operations to subtract
193
194
Returns:
195
list: Remaining operations after subtracting the subsequence
196
197
Note: Only works with editops (triples), not opcodes
198
"""
199
```
200
201
**Usage Examples:**
202
203
```python
204
import Levenshtein
205
206
# Get complete edit sequence
207
source = "man"
208
dest = "scotsman"
209
complete_ops = Levenshtein.editops(source, dest)
210
print("Complete ops:", complete_ops)
211
212
# Apply partial operations
213
partial_ops = complete_ops[:3]
214
intermediate = Levenshtein.apply_edit(partial_ops, source, dest)
215
print(f"After partial: '{intermediate}'")
216
217
# Calculate remaining operations
218
remaining_ops = Levenshtein.subtract_edit(complete_ops, partial_ops)
219
print("Remaining ops:", remaining_ops)
220
221
# Apply remaining operations
222
final = Levenshtein.apply_edit(remaining_ops, intermediate, dest)
223
print(f"Final result: '{final}'") # Should equal dest
224
```
225
226
### Inverse Edit Operations
227
228
Invert the sense of edit operations, swapping source and destination to reverse the transformation direction.
229
230
```python { .api }
231
def inverse(edit_operations):
232
"""
233
Invert the sense of edit operations.
234
235
Returns operations that transform the destination string to the source string.
236
Works with both editops and opcodes.
237
238
Parameters:
239
- edit_operations: List of edit operations to invert
240
241
Returns:
242
list: Inverted edit operations
243
"""
244
```
245
246
**Usage Examples:**
247
248
```python
249
import Levenshtein
250
251
# Get edit operations
252
forward_ops = Levenshtein.editops("spam", "park")
253
print("Forward:", forward_ops)
254
# [('delete', 0, 0), ('insert', 3, 2), ('replace', 3, 3)]
255
256
# Invert operations
257
reverse_ops = Levenshtein.inverse(forward_ops)
258
print("Reverse:", reverse_ops)
259
# [('insert', 0, 0), ('delete', 2, 3), ('replace', 3, 3)]
260
261
# Verify inversion works
262
result = Levenshtein.apply_edit(reverse_ops, "park", "spam")
263
print(f"Reverse transform: 'park' -> '{result}'") # 'park' -> 'spam'
264
265
# Works with opcodes too
266
forward_opcodes = Levenshtein.opcodes("spam", "park")
267
reverse_opcodes = Levenshtein.inverse(forward_opcodes)
268
print("Reversed opcodes:", reverse_opcodes)
269
```
270
271
## Advanced Usage Patterns
272
273
### Analyzing String Transformations
274
275
```python
276
import Levenshtein
277
278
def analyze_transformation(source, dest):
279
"""Detailed analysis of string transformation."""
280
# Get different operation formats
281
editops_list = Levenshtein.editops(source, dest)
282
opcodes_list = Levenshtein.opcodes(source, dest)
283
blocks = Levenshtein.matching_blocks(editops_list, source, dest)
284
285
print(f"Transform '{source}' -> '{dest}'")
286
print(f"Edit operations: {editops_list}")
287
print(f"Opcodes: {opcodes_list}")
288
print(f"Matching blocks: {blocks}")
289
290
# Count operation types
291
op_counts = {}
292
for op, _, _ in editops_list:
293
op_counts[op] = op_counts.get(op, 0) + 1
294
print(f"Operation counts: {op_counts}")
295
296
return editops_list, opcodes_list, blocks
297
298
# Example
299
analyze_transformation("kitten", "sitting")
300
```
301
302
### Step-by-Step Transformation
303
304
```python
305
import Levenshtein
306
307
def step_by_step_transform(source, dest):
308
"""Show each step of the transformation."""
309
ops = Levenshtein.editops(source, dest)
310
current = source
311
312
print(f"Start: '{current}'")
313
314
for i, (op, src_pos, dest_pos) in enumerate(ops, 1):
315
# Apply just this operation
316
single_op = [ops[i-1]] # Current operation
317
# For step-by-step, we need to build operations cumulatively
318
cumulative_ops = ops[:i]
319
current = Levenshtein.apply_edit(cumulative_ops, source, dest)
320
print(f"Step {i} ({op}): '{current}'")
321
322
print(f"Final: '{current}'")
323
324
# Example
325
step_by_step_transform("cat", "dog")
326
```
327
328
### Operation Format Conversion
329
330
```python
331
import Levenshtein
332
333
def convert_operations(source, dest):
334
"""Convert between different operation formats."""
335
# Start with editops
336
editops_list = Levenshtein.editops(source, dest)
337
print("Editops:", editops_list)
338
339
# Convert to opcodes
340
opcodes_list = Levenshtein.opcodes(editops_list, len(source), len(dest))
341
print("Opcodes:", opcodes_list)
342
343
# Convert back to editops
344
editops_converted = Levenshtein.editops(opcodes_list, len(source), len(dest))
345
print("Converted back:", editops_converted)
346
347
# Verify they're equivalent
348
print("Equivalent:", editops_list == editops_converted)
349
350
# Example
351
convert_operations("hello", "world")
352
```
353
354
### Partial Transformations
355
356
```python
357
import Levenshtein
358
359
def partial_transformation_demo(source, dest, steps=None):
360
"""Demonstrate partial application of transformations."""
361
ops = Levenshtein.editops(source, dest)
362
363
if steps is None:
364
steps = len(ops) // 2 # Apply half the operations
365
366
# Apply partial operations
367
partial_ops = ops[:steps]
368
intermediate = Levenshtein.apply_edit(partial_ops, source, dest)
369
370
# Get remaining operations
371
remaining_ops = Levenshtein.subtract_edit(ops, partial_ops)
372
373
# Apply remaining operations
374
final = Levenshtein.apply_edit(remaining_ops, intermediate, dest)
375
376
print(f"Source: '{source}'")
377
print(f"After {steps} operations: '{intermediate}'")
378
print(f"Final: '{final}'")
379
print(f"Matches destination: {final == dest}")
380
381
# Example
382
partial_transformation_demo("programming", "algorithm", 3)
383
```