@@ -21,21 +21,55 @@ use std::fmt::Debug;
21
21
/// quadratically increasing its worst-case runtime.
22
22
const MAX_DISTANCE : i32 = 25 ;
23
23
24
- /// Compute the edit list of `left` and `right`.
24
+ /// The difference between two inputs as produced by [`edit_list`].
25
+ #[ derive( Debug ) ]
26
+ pub ( crate ) enum Difference < T > {
27
+ /// No differences were detected at all.
28
+ Equal ,
29
+
30
+ /// At most [`MAX_DISTANCE`] edits are required to convert one input to the
31
+ /// other.
32
+ ///
33
+ /// Contains the list of [`Edit`] to perform the transformation.
34
+ Editable ( Vec < Edit < T > > ) ,
35
+
36
+ /// More than [`MAX_DISTANCE`] edits are required to convert one input to
37
+ /// the other.
38
+ ///
39
+ /// The inputs are therefore considered unrelated and no edit list is
40
+ /// provided.
41
+ Unrelated ,
42
+ }
43
+
44
+ /// An edit operation on two sequences of `T`.
45
+ #[ derive( Debug , Clone ) ]
46
+ pub ( crate ) enum Edit < T > {
47
+ /// An extra `T` was added to the left sequence.
48
+ ExtraLeft ( T ) ,
49
+
50
+ /// An extra `T` was added to the right sequence.
51
+ ExtraRight ( T ) ,
52
+
53
+ /// An element was added to each sequence.
54
+ Both ( T ) ,
55
+ }
56
+
57
+ /// Computes the edit list of `left` and `right`.
25
58
///
26
- /// This returns a vec of [`Edit`] which can be applied to `left` to obtain
27
- /// `right`. See <https://en.wikipedia.org/wiki/Edit_distance> for more
28
- /// information.
59
+ /// If `left` and `right` are equal, then this returns [`Difference::Equal`]. If
60
+ /// they are different but have an
61
+ /// [edit distance](https://en.wikipedia.org/wiki/Edit_distance)
62
+ /// of at most [`MAX_DISTANCE`], this returns [`Difference::Editable`] with the
63
+ /// sequence of [`Edit`] which can be applied to `left` to obtain `right`.
64
+ /// Otherwise this returns [`Difference::Unrelated`].
29
65
///
30
- /// It uses [Myers Algorithm](https://neil.fraser.name/writing/diff/myers.pdf)
31
- /// with a maximum edit distance of [`MAX_DISTANCE`]. If more than
32
- /// [`MAX_DISTANCE`] insertions or deletions are required to convert `left` to
33
- /// `right`, it returns a default fallback edit list which deletes all items
34
- /// from `left` and inserts all items in `right`.
66
+ /// This uses [Myers Algorithm](https://neil.fraser.name/writing/diff/myers.pdf)
67
+ /// with a maximum edit distance of [`MAX_DISTANCE`]. Thus the worst-case
68
+ /// runtime is linear in both the input length and [`MAX_DISTANCE`].
35
69
pub ( crate ) fn edit_list < T : PartialEq + Copy > (
36
70
left : impl IntoIterator < Item = T > ,
37
71
right : impl IntoIterator < Item = T > ,
38
- ) -> Vec < Edit < T > > {
72
+ ) -> Difference < T > {
39
73
let left: Vec < _ > = left. into_iter ( ) . collect ( ) ;
40
74
let right: Vec < _ > = right. into_iter ( ) . collect ( ) ;
41
75
@@ -106,7 +140,11 @@ pub(crate) fn edit_list<T: PartialEq + Copy>(
106
140
107
141
// If we have exhausted both inputs, we are done.
108
142
if left_endpoint == left. len ( ) && right_endpoint == right. len ( ) {
109
- return path. edits ;
143
+ return if path. edits . iter ( ) . any ( |v| !matches ! ( v, Edit :: Both ( _) ) ) {
144
+ Difference :: Editable ( path. edits )
145
+ } else {
146
+ Difference :: Equal
147
+ } ;
110
148
}
111
149
112
150
path. left_endpoint = left_endpoint;
@@ -116,11 +154,7 @@ pub(crate) fn edit_list<T: PartialEq + Copy>(
116
154
paths_last = paths_current;
117
155
}
118
156
119
- // Fallback when the distance is too large: assume the two are completely
120
- // different.
121
- let mut result: Vec < _ > = left. iter ( ) . map ( |t| Edit :: ExtraLeft ( * t) ) . collect ( ) ;
122
- result. extend ( right. iter ( ) . map ( |t| Edit :: ExtraRight ( * t) ) ) ;
123
- result
157
+ Difference :: Unrelated
124
158
}
125
159
126
160
fn index_of_k ( k : i32 , k_min : i32 ) -> usize {
@@ -150,62 +184,55 @@ impl<T: Clone> Path<T> {
150
184
}
151
185
}
152
186
153
- /// An edit operation on two sequences of `T`.
154
- #[ derive( Debug , Clone ) ]
155
- pub ( crate ) enum Edit < T > {
156
- /// An extra `T` was added to the left sequence.
157
- ExtraLeft ( T ) ,
158
- /// An extra `T` was added to the right sequence.
159
- ExtraRight ( T ) ,
160
- /// An element was added to each sequence.
161
- Both ( T ) ,
162
- }
163
-
164
187
#[ cfg( test) ]
165
188
mod tests {
166
189
use super :: * ;
167
190
use crate :: prelude:: * ;
168
- use quickcheck:: { quickcheck, Arbitrary } ;
191
+ use quickcheck:: { quickcheck, Arbitrary , TestResult } ;
169
192
170
193
#[ test]
171
- fn returns_single_edit_when_strings_are_equal ( ) -> Result < ( ) > {
194
+ fn returns_equal_when_strings_are_equal ( ) -> Result < ( ) > {
172
195
let result = edit_list ( [ "A string" ] , [ "A string" ] ) ;
173
- verify_that ! ( result, elements_are! [ matches_pattern!( Edit :: Both ( eq ( "A string" ) ) ) ] )
196
+ verify_that ! ( result, matches_pattern!( Difference :: Equal ) )
174
197
}
175
198
176
199
#[ test]
177
200
fn returns_sequence_of_two_common_parts ( ) -> Result < ( ) > {
178
201
let result = edit_list ( [ "A string (1)" , "A string (2)" ] , [ "A string (1)" , "A string (2)" ] ) ;
179
- verify_that ! (
180
- result,
181
- elements_are![
182
- matches_pattern!( Edit :: Both ( eq( "A string (1)" ) ) ) ,
183
- matches_pattern!( Edit :: Both ( eq( "A string (2)" ) ) )
184
- ]
185
- )
202
+ verify_that ! ( result, matches_pattern!( Difference :: Equal ) )
186
203
}
187
204
188
205
#[ test]
189
206
fn returns_extra_left_when_only_left_has_content ( ) -> Result < ( ) > {
190
207
let result = edit_list ( [ "A string" ] , [ ] ) ;
191
- verify_that ! ( result, elements_are![ matches_pattern!( Edit :: ExtraLeft ( eq( "A string" ) ) ) , ] )
208
+ verify_that ! (
209
+ result,
210
+ matches_pattern!( Difference :: Editable ( elements_are![ matches_pattern!(
211
+ Edit :: ExtraLeft ( eq( "A string" ) )
212
+ ) ] ) )
213
+ )
192
214
}
193
215
194
216
#[ test]
195
217
fn returns_extra_right_when_only_right_has_content ( ) -> Result < ( ) > {
196
218
let result = edit_list ( [ ] , [ "A string" ] ) ;
197
- verify_that ! ( result, elements_are![ matches_pattern!( Edit :: ExtraRight ( eq( "A string" ) ) ) , ] )
219
+ verify_that ! (
220
+ result,
221
+ matches_pattern!( Difference :: Editable ( elements_are![ matches_pattern!(
222
+ Edit :: ExtraRight ( eq( "A string" ) )
223
+ ) ] ) )
224
+ )
198
225
}
199
226
200
227
#[ test]
201
228
fn returns_extra_left_followed_by_extra_right_with_two_unequal_strings ( ) -> Result < ( ) > {
202
229
let result = edit_list ( [ "A string" ] , [ "Another string" ] ) ;
203
230
verify_that ! (
204
231
result,
205
- elements_are![
232
+ matches_pattern! ( Difference :: Editable ( elements_are![
206
233
matches_pattern!( Edit :: ExtraLeft ( eq( "A string" ) ) ) ,
207
234
matches_pattern!( Edit :: ExtraRight ( eq( "Another string" ) ) ) ,
208
- ]
235
+ ] ) )
209
236
)
210
237
}
211
238
@@ -214,12 +241,12 @@ mod tests {
214
241
let result = edit_list ( [ "A string" , "A string" ] , [ "Another string" , "Another string" ] ) ;
215
242
verify_that ! (
216
243
result,
217
- elements_are![
244
+ matches_pattern! ( Difference :: Editable ( elements_are![
218
245
matches_pattern!( Edit :: ExtraLeft ( eq( "A string" ) ) ) ,
219
246
matches_pattern!( Edit :: ExtraRight ( eq( "Another string" ) ) ) ,
220
247
matches_pattern!( Edit :: ExtraLeft ( eq( "A string" ) ) ) ,
221
248
matches_pattern!( Edit :: ExtraRight ( eq( "Another string" ) ) ) ,
222
- ]
249
+ ] ) )
223
250
)
224
251
}
225
252
@@ -228,11 +255,11 @@ mod tests {
228
255
let result = edit_list ( [ "Common part" , "Left only" ] , [ "Common part" , "Right only" ] ) ;
229
256
verify_that ! (
230
257
result,
231
- elements_are![
258
+ matches_pattern! ( Difference :: Editable ( elements_are![
232
259
matches_pattern!( Edit :: Both ( eq( "Common part" ) ) ) ,
233
260
matches_pattern!( Edit :: ExtraLeft ( eq( "Left only" ) ) ) ,
234
261
matches_pattern!( Edit :: ExtraRight ( eq( "Right only" ) ) ) ,
235
- ]
262
+ ] ) )
236
263
)
237
264
}
238
265
@@ -241,10 +268,10 @@ mod tests {
241
268
let result = edit_list ( [ "Common part" , "Left only" ] , [ "Common part" ] ) ;
242
269
verify_that ! (
243
270
result,
244
- elements_are![
271
+ matches_pattern! ( Difference :: Editable ( elements_are![
245
272
matches_pattern!( Edit :: Both ( eq( "Common part" ) ) ) ,
246
273
matches_pattern!( Edit :: ExtraLeft ( eq( "Left only" ) ) ) ,
247
- ]
274
+ ] ) )
248
275
)
249
276
}
250
277
@@ -253,10 +280,10 @@ mod tests {
253
280
let result = edit_list ( [ "Common part" ] , [ "Common part" , "Right only" ] ) ;
254
281
verify_that ! (
255
282
result,
256
- elements_are![
283
+ matches_pattern! ( Difference :: Editable ( elements_are![
257
284
matches_pattern!( Edit :: Both ( eq( "Common part" ) ) ) ,
258
285
matches_pattern!( Edit :: ExtraRight ( eq( "Right only" ) ) ) ,
259
- ]
286
+ ] ) )
260
287
)
261
288
}
262
289
@@ -265,11 +292,11 @@ mod tests {
265
292
let result = edit_list ( [ "Left only" , "Common part" ] , [ "Right only" , "Common part" ] ) ;
266
293
verify_that ! (
267
294
result,
268
- elements_are![
295
+ matches_pattern! ( Difference :: Editable ( elements_are![
269
296
matches_pattern!( Edit :: ExtraLeft ( eq( "Left only" ) ) ) ,
270
297
matches_pattern!( Edit :: ExtraRight ( eq( "Right only" ) ) ) ,
271
298
matches_pattern!( Edit :: Both ( eq( "Common part" ) ) ) ,
272
- ]
299
+ ] ) )
273
300
)
274
301
}
275
302
@@ -282,13 +309,13 @@ mod tests {
282
309
) ;
283
310
verify_that ! (
284
311
result,
285
- elements_are![
312
+ matches_pattern! ( Difference :: Editable ( elements_are![
286
313
matches_pattern!( Edit :: ExtraLeft ( eq( "Left only (1)" ) ) ) ,
287
314
matches_pattern!( Edit :: ExtraRight ( eq( "Right only (1)" ) ) ) ,
288
315
matches_pattern!( Edit :: Both ( eq( "Common part" ) ) ) ,
289
316
matches_pattern!( Edit :: ExtraLeft ( eq( "Left only (2)" ) ) ) ,
290
317
matches_pattern!( Edit :: ExtraRight ( eq( "Right only (2)" ) ) ) ,
291
- ]
318
+ ] ) )
292
319
)
293
320
}
294
321
@@ -301,12 +328,12 @@ mod tests {
301
328
) ;
302
329
verify_that ! (
303
330
result,
304
- elements_are![
331
+ matches_pattern! ( Difference :: Editable ( elements_are![
305
332
matches_pattern!( Edit :: Both ( eq( "Common part (1)" ) ) ) ,
306
333
matches_pattern!( Edit :: ExtraLeft ( eq( "Left only" ) ) ) ,
307
334
matches_pattern!( Edit :: ExtraRight ( eq( "Right only" ) ) ) ,
308
335
matches_pattern!( Edit :: Both ( eq( "Common part (2)" ) ) ) ,
309
- ]
336
+ ] ) )
310
337
)
311
338
}
312
339
@@ -319,11 +346,11 @@ mod tests {
319
346
) ;
320
347
verify_that ! (
321
348
result,
322
- elements_are![
349
+ matches_pattern! ( Difference :: Editable ( elements_are![
323
350
matches_pattern!( Edit :: Both ( eq( "Common part (1)" ) ) ) ,
324
351
matches_pattern!( Edit :: ExtraLeft ( eq( "Left only" ) ) ) ,
325
352
matches_pattern!( Edit :: Both ( eq( "Common part (2)" ) ) ) ,
326
- ]
353
+ ] ) )
327
354
)
328
355
}
329
356
@@ -336,37 +363,59 @@ mod tests {
336
363
) ;
337
364
verify_that ! (
338
365
result,
339
- elements_are![
366
+ matches_pattern! ( Difference :: Editable ( elements_are![
340
367
matches_pattern!( Edit :: Both ( eq( "Common part (1)" ) ) ) ,
341
368
matches_pattern!( Edit :: ExtraRight ( eq( "Right only" ) ) ) ,
342
369
matches_pattern!( Edit :: Both ( eq( "Common part (2)" ) ) ) ,
343
- ]
370
+ ] ) )
344
371
)
345
372
}
346
373
347
374
#[ test]
348
- fn returns_rewrite_fallback_when_maximum_distance_exceeded ( ) -> Result < ( ) > {
375
+ fn returns_unrelated_when_maximum_distance_exceeded ( ) -> Result < ( ) > {
349
376
let result = edit_list ( 0 ..=20 , 20 ..40 ) ;
350
- verify_that ! ( result, not ( contains ( matches_pattern!( Edit :: Both ( anything ( ) ) ) ) ) )
377
+ verify_that ! ( result, matches_pattern!( Difference :: Unrelated ) )
351
378
}
352
379
353
380
quickcheck ! {
354
381
fn edit_list_edits_left_to_right(
355
382
left: Vec <Alphabet >,
356
383
right: Vec <Alphabet >
357
- ) -> bool {
358
- let edit_list = edit_list( left. clone( ) , right. clone( ) ) ;
359
- apply_edits_to_left( & edit_list, & left) == right
384
+ ) -> TestResult {
385
+ match edit_list( left. clone( ) , right. clone( ) ) {
386
+ Difference :: Equal => TestResult :: from_bool( left == right) ,
387
+ Difference :: Editable ( edit_list) => {
388
+ TestResult :: from_bool( apply_edits_to_left( & edit_list, & left) == right)
389
+ }
390
+ Difference :: Unrelated => {
391
+ if left == right {
392
+ TestResult :: failed( )
393
+ } else {
394
+ TestResult :: discard( )
395
+ }
396
+ }
397
+ }
360
398
}
361
399
}
362
400
363
401
quickcheck ! {
364
402
fn edit_list_edits_right_to_left(
365
403
left: Vec <Alphabet >,
366
404
right: Vec <Alphabet >
367
- ) -> bool {
368
- let edit_list = edit_list( left. clone( ) , right. clone( ) ) ;
369
- apply_edits_to_right( & edit_list, & right) == left
405
+ ) -> TestResult {
406
+ match edit_list( left. clone( ) , right. clone( ) ) {
407
+ Difference :: Equal => TestResult :: from_bool( left == right) ,
408
+ Difference :: Editable ( edit_list) => {
409
+ TestResult :: from_bool( apply_edits_to_right( & edit_list, & right) == left)
410
+ }
411
+ Difference :: Unrelated => {
412
+ if left == right {
413
+ TestResult :: failed( )
414
+ } else {
415
+ TestResult :: discard( )
416
+ }
417
+ }
418
+ }
370
419
}
371
420
}
372
421
0 commit comments