@@ -278,9 +278,8 @@ def to_partial(self, *, retain_names: bool = False, minify: bool = True) -> Self
278
278
Self
279
279
An equivalent partial DFA.
280
280
"""
281
- if self .allow_partial :
282
- return self .copy ()
283
281
282
+ # Still want to try and remove dead states in the case of a partial DFA
284
283
graph = self ._get_digraph ()
285
284
live_states = get_reachable_nodes (graph , [self .initial_state ])
286
285
non_trap_states = get_reachable_nodes (graph , self .final_states , reversed = True )
@@ -338,7 +337,12 @@ def to_complete(self, trap_state: Optional[DFAStateT] = None) -> Self:
338
337
An equivalent complete DFA.
339
338
340
339
"""
341
- if not self .allow_partial :
340
+ is_partial = any (
341
+ len (lookup ) != len (self .input_symbols )
342
+ for lookup in self .transitions .values ()
343
+ )
344
+
345
+ if not is_partial :
342
346
return self .copy ()
343
347
344
348
if trap_state is None :
@@ -624,7 +628,9 @@ def _minify(
624
628
x for x in count (- 1 , - 1 ) if x not in reachable_states
625
629
)
626
630
for trap_symbol in input_symbols :
627
- transition_back_map [trap_symbol ][trap_state ] = []
631
+ transition_back_map [trap_symbol ][trap_state ] = [
632
+ trap_state
633
+ ]
628
634
629
635
reachable_states .add (trap_state )
630
636
@@ -681,6 +687,11 @@ def _minify(
681
687
if trap_state not in eq
682
688
}
683
689
690
+ # If only one equivalence class with the trap state,
691
+ # return empty language.
692
+ if not back_map :
693
+ return cls .empty_language (input_symbols )
694
+
684
695
new_input_symbols = input_symbols
685
696
new_states = frozenset (back_map .values ())
686
697
new_initial_state = back_map [initial_state ]
@@ -1276,6 +1287,8 @@ def predecessor(
1276
1287
* ,
1277
1288
strict : bool = True ,
1278
1289
key : Optional [Callable [[Any ], Any ]] = None ,
1290
+ min_length : int = 0 ,
1291
+ max_length : Optional [int ] = None ,
1279
1292
) -> Optional [str ]:
1280
1293
"""
1281
1294
Returns the first string accepted by the DFA that comes before
@@ -1291,6 +1304,10 @@ def predecessor(
1291
1304
key : Optional[Callable], default: None
1292
1305
Function for defining custom lexicographical ordering. Defaults to using
1293
1306
the standard string ordering.
1307
+ min_length : int, default: 0
1308
+ Limits generation to words with at least the given length.
1309
+ max_length : Optional[int], default: None
1310
+ Limits generation to words with at most the given length.
1294
1311
1295
1312
Returns
1296
1313
------
@@ -1303,7 +1320,13 @@ def predecessor(
1303
1320
Raised if the language accepted by self is infinite, as we cannot
1304
1321
generate predecessors in this case.
1305
1322
"""
1306
- for word in self .predecessors (input_str , strict = strict , key = key ):
1323
+ for word in self .predecessors (
1324
+ input_str ,
1325
+ strict = strict ,
1326
+ key = key ,
1327
+ min_length = min_length ,
1328
+ max_length = max_length ,
1329
+ ):
1307
1330
return word
1308
1331
return None
1309
1332
@@ -1313,6 +1336,8 @@ def predecessors(
1313
1336
* ,
1314
1337
strict : bool = True ,
1315
1338
key : Optional [Callable [[Any ], Any ]] = None ,
1339
+ min_length : int = 0 ,
1340
+ max_length : Optional [int ] = None ,
1316
1341
) -> Generator [str , None , None ]:
1317
1342
"""
1318
1343
Generates all strings that come before the input string
@@ -1328,6 +1353,10 @@ def predecessors(
1328
1353
key : Optional[Callable], default: None
1329
1354
Function for defining custom lexicographical ordering. Defaults to using
1330
1355
the standard string ordering.
1356
+ min_length : int, default: 0
1357
+ Limits generation to words with at least the given length.
1358
+ max_length : Optional[int], default: None
1359
+ Limits generation to words with at most the given length.
1331
1360
1332
1361
Returns
1333
1362
------
@@ -1341,14 +1370,23 @@ def predecessors(
1341
1370
Raised if the language accepted by self is infinite, as we cannot
1342
1371
generate predecessors in this case.
1343
1372
"""
1344
- yield from self .successors (input_str , strict = strict , reverse = True , key = key )
1373
+ yield from self .successors (
1374
+ input_str ,
1375
+ strict = strict ,
1376
+ reverse = True ,
1377
+ key = key ,
1378
+ min_length = min_length ,
1379
+ max_length = max_length ,
1380
+ )
1345
1381
1346
1382
def successor (
1347
1383
self ,
1348
1384
input_str : Optional [str ],
1349
1385
* ,
1350
1386
strict : bool = True ,
1351
1387
key : Optional [Callable [[Any ], Any ]] = None ,
1388
+ min_length : int = 0 ,
1389
+ max_length : Optional [int ] = None ,
1352
1390
) -> Optional [str ]:
1353
1391
"""
1354
1392
Returns the first string accepted by the DFA that comes after
@@ -1364,13 +1402,23 @@ def successor(
1364
1402
key : Optional[Callable], default: None
1365
1403
Function for defining custom lexicographical ordering. Defaults to using
1366
1404
the standard string ordering.
1405
+ min_length : int, default: 0
1406
+ Limits generation to words with at least the given length.
1407
+ max_length : Optional[int], default: None
1408
+ Limits generation to words with at most the given length.
1367
1409
1368
1410
Returns
1369
1411
------
1370
1412
str
1371
1413
The first string accepted by the DFA lexicographically before input_string.
1372
1414
"""
1373
- for word in self .successors (input_str , strict = strict , key = key ):
1415
+ for word in self .successors (
1416
+ input_str ,
1417
+ strict = strict ,
1418
+ key = key ,
1419
+ min_length = min_length ,
1420
+ max_length = max_length ,
1421
+ ):
1374
1422
return word
1375
1423
return None
1376
1424
@@ -1381,6 +1429,8 @@ def successors(
1381
1429
strict : bool = True ,
1382
1430
key : Optional [Callable [[Any ], Any ]] = None ,
1383
1431
reverse : bool = False ,
1432
+ min_length : int = 0 ,
1433
+ max_length : Optional [int ] = None ,
1384
1434
) -> Generator [str , None , None ]:
1385
1435
"""
1386
1436
Generates all strings that come after the input string
@@ -1398,6 +1448,10 @@ def successors(
1398
1448
the standard string ordering.
1399
1449
reverse : bool, default: False
1400
1450
If True, then predecessors will be generated instead of successors.
1451
+ min_length : int, default: 0
1452
+ Limits generation to words with at least the given length.
1453
+ max_length : Optional[int], default: None
1454
+ Limits generation to words with at most the given length.
1401
1455
1402
1456
Returns
1403
1457
------
@@ -1446,6 +1500,8 @@ def successors(
1446
1500
if (
1447
1501
not reverse
1448
1502
and should_yield
1503
+ and min_length <= len (char_stack )
1504
+ and (max_length is None or len (char_stack ) <= max_length )
1449
1505
and candidate == first_symbol
1450
1506
and state in self .final_states
1451
1507
):
@@ -1456,7 +1512,9 @@ def successors(
1456
1512
else self ._get_next_current_state (state , candidate )
1457
1513
)
1458
1514
# Traverse to child if candidate is viable
1459
- if candidate_state in coaccessible_nodes :
1515
+ if candidate_state in coaccessible_nodes and (
1516
+ max_length is None or len (char_stack ) < max_length
1517
+ ):
1460
1518
state_stack .append (candidate_state )
1461
1519
char_stack .append (cast (str , candidate ))
1462
1520
candidate = first_symbol
@@ -1465,6 +1523,8 @@ def successors(
1465
1523
if (
1466
1524
reverse
1467
1525
and should_yield
1526
+ and min_length <= len (char_stack )
1527
+ and (max_length is None or len (char_stack ) <= max_length )
1468
1528
and candidate is None
1469
1529
and state in self .final_states
1470
1530
):
@@ -1480,6 +1540,8 @@ def successors(
1480
1540
if (
1481
1541
reverse
1482
1542
and should_yield
1543
+ and min_length <= len (char_stack )
1544
+ and (max_length is None or len (char_stack ) <= max_length )
1483
1545
and candidate is None
1484
1546
and state in self .final_states
1485
1547
):
@@ -1707,13 +1769,18 @@ def from_prefix(
1707
1769
1708
1770
states = frozenset (transitions .keys ())
1709
1771
final_states = {last_state }
1772
+
1773
+ is_partial = any (
1774
+ len (lookup ) != len (input_symbols ) for lookup in transitions .values ()
1775
+ )
1776
+
1710
1777
return cls (
1711
1778
states = states ,
1712
1779
input_symbols = input_symbols ,
1713
1780
transitions = transitions ,
1714
1781
initial_state = 0 ,
1715
1782
final_states = final_states if contains else states - final_states ,
1716
- allow_partial = as_partial ,
1783
+ allow_partial = is_partial ,
1717
1784
)
1718
1785
1719
1786
@classmethod
0 commit comments