-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathanagram.py
More file actions
executable file
·119 lines (99 loc) · 3.45 KB
/
Copy pathanagram.py
File metadata and controls
executable file
·119 lines (99 loc) · 3.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/usr/bin/env python
#
# anagram.py
# ----------
# Copyright (c) 2017 Chandranath Gunjal. Available under the MIT License
#
# usage: anagram.py [-h] [-c char] [-l x..y] jumble
#
import getopt
import sys
from collections import Counter
DEFAULT_DICT = "/usr/share/dict/words"
class Anagram:
"""Find anagrams for a given jumble of letters including words of shorter or
longer lengths
"""
def __init__(
self, jumble: str, compulsory: str | None = None, min_len: int = -1, max_len: int = -1
) -> None:
self.jumble = jumble.lower()
self.jdist = Counter(self.jumble) # letter count/distribution
self.jlen = len(self.jumble)
self.compulsory = compulsory
self.min_len = self.jlen if (min_len < 0) else min_len
self.max_len = self.jlen if (max_len < 0) else max_len
self.matches = set()
self.find_matches()
return
def find_matches(self, dictionary: str = DEFAULT_DICT) -> None:
with open(dictionary, "rt") as fh:
for w in fh:
w = w.strip()
wlen = len(w)
if (wlen < self.min_len) or (wlen > self.max_len):
continue
if (self.compulsory is not None) and (self.compulsory not in w):
continue
# compare letter counts
wdist = Counter(w.lower())
if (
((wlen > self.jlen) and (self.jdist <= wdist))
or ((wlen < self.jlen) and (wdist <= self.jdist))
or ((wlen == self.jlen) and (self.jdist == wdist))
):
self.matches.add(w)
return
def print_results(self) -> None:
for w in sorted(self.matches):
print(w)
return
# Main
# ====
if __name__ == "__main__":
synopsis = "usage: anagram.py [-h] [-c char] [-l m..M] jumble"
helptxt = (
synopsis
+ """
-c char compulsory letter
-h help
-l m..M min..max word lengths (incomplete specs are valid)
"""
)
def print_usage():
"""Print usage and exit"""
print(helptxt)
sys.exit(2)
# parse word length
def parse_range(
spec: str, sep: str = "..", default_low: int = 1, default_high: int = 40
) -> tuple[int, int]:
"""Parse range specified as x..y or a part thereof"""
parsed = spec.split(sep)
hi = lo = int(parsed[0]) if parsed[0] != "" else default_low
if len(parsed) == 2:
hi = int(parsed[1]) if parsed[1] != "" else default_high
return lo, hi
# parse command line
def parse_args(argv: list) -> tuple[str, str, int, int]:
try:
opts, args = getopt.getopt(argv, "c:hl:", ["help"])
except getopt.GetoptError:
print_usage()
p_compulsory, p_min, p_max = None, -1, -1
for opt, arg in opts:
if opt in ["-h", "--help"]:
print_usage()
elif opt == "-c":
p_compulsory = arg
elif opt == "-l":
p_min, p_max = parse_range(arg)
if len(args) != 1:
print_usage()
p_jumble = args[0]
return p_jumble, p_compulsory, p_min, p_max
# parse command line
jumble, compulsory, len_min, len_max = parse_args(sys.argv[1:])
# solve the anagram & print
anagram = Anagram(jumble, compulsory, len_min, len_max)
anagram.print_results()