· 8 years ago · Apr 16, 2018, 06:34 PM
1## words/word.py
2
3import pdb
4
5import scriptutil as SU
6import re
7
8import psycopg2
9from psycopg2.extras import DictCursor
10from psycopg2.extensions import adapt
11
12try:
13 db = psycopg2.connect(database="scrabble", user="python", password="python")
14 cur = db.cursor(cursor_factory=psycopg2.extras.DictCursor)
15# cur.execute ("CREATE TABLE words (name varchar, probability int, frequency int, catches varchar, hangs varchar);")
16except:
17 print "I am unable to connect to the database"
18 sys.ext()
19
20try:
21 "trying to find a wordlist reference file"
22except:
23 "failing to find a wordlist reference file. You're on your own, you database-dependent chump!"
24
25
26class Word:
27 """legal scrabble words
28
29 1) in official lists, and
30 2) have point/frequency attributes that are derived --- not from it's own letters ---
31 but rather from the point/prob sums of all the possible _derivative_ scrabble-legal words
32
33 # raw data from official scrabble lists. Can be downloaded from hasbro into "./*.txt"
34 # other dependencies: special words lists in "./words/*.txt" and python-scriptutil.py
35 """
36
37 letters = "_ a b c d e f g h i j k l m n o p q r s t u v w x y z".split()
38 frequencies = (2, 9, 2, 2, 4, 12, 2, 3, 2, 9, 1, 1, 4, 2, 6, 8, 2, 1, 6, 4, 6, 4, 2, 2, 1, 2, 1)
39 points = (0, 1, 3, 3, 2, 1, 4, 2, 4, 1, 8, 5, 1, 3, 1, 1, 3, 10, 1, 1, 1, 1, 4, 4, 8, 4, 10)
40
41
42 letter_frequencies = dict(zip(letters,frequencies))
43 letter_points = dict(zip(letters,frequencies))
44
45 def calculate_probability(self):
46 return sum(map(lambda letter: letter_points[letter], self.catches))
47 def calculate_frequencies(self):
48 return sum(map(lambda letter: letter_frequencies[letter], self.catches))
49
50 def __init__(self,name,points=None,frequency=None,catches=None,hangs=None):
51 self.name = name
52
53 if catches is None: self.catches = catches
54 if frequency is None: self.frequency = frequency
55 if points is None: self.points = points
56 if hangs is None: self.hangs = hangs
57
58 @staticmethod
59 def count(finder_sql = ""):
60 """rails-style finder
61 """
62 cur.execute("select * from words {0}".format(finder_sql))
63 return cur.rowcount
64
65
66 def hangs(self):
67 """ one-lettter shorter
68 """
69 return self.name[0:-1]
70
71 @staticmethod
72 def find_or_create_all_by_name(names):
73 """routes finder calls to database AND/OR word lists ... or creates.
74
75 merge
76 VS
77 cur.copy_in( ... scratch ... )
78 insert into words select * from (select distinct * from scratch) uniq where not exists (select 1 from words where words.name = uniq.name);
79 """
80
81 # MYTODO escape names ... learning exercise.
82 matches = Word.find_all("""where words.name in {0}""".format(tuple(names)))
83 unmatched = set(names) - set(map(lambda w: w.name, matches))
84 pdb.set_trace()
85
86 invalid_words = []
87 created_words = []
88 for n in unmatched:
89 w = Word(n)
90 try:
91 w.new()
92 created_words.append(w)
93 except NameError:
94 invalid_words.append(n)
95 # MYTODO: hose invalid words over to the output somehow ... through a logger, if nothing else
96
97 if not len(created_words) == 0: db.commit()
98 return created_words.extend(matches) or []
99
100
101 def new(self):
102 """ vaguely rails-AR-like new()
103
104 validates, find-greps for catches, and pre-commits instance to the db
105
106 #MYTODO: profiling. Is it worth it to split up the two grep searches? (above)
107 """
108 self.validate_against_local_lists()
109 grepd_catches = self.fgrep_catches_in_directories(("./words",))
110
111 flat_catches = []
112 for c in grepd_catches: flat_catches.extend(c) #split()
113 self.catches = "".join(map(lambda catch: catch+" ", set(flat_catches))).strip()
114
115 cur.execute("""INSERT INTO words VALUES {0}""".format(
116 (
117 self.name,
118 self.calculate_probability(),
119 self.calculate_frequencies(),
120 self.catches,
121 # hangs
122 self.name[1:] + " " + self.name[:-1],
123 )
124 ))
125
126
127 def validate_against_local_lists(self, lists=(".",)):
128 """if not found in any text file => not a legal word!
129
130 this will also catch all the weird things people might throw. Like numbers.
131 """
132 if [self.name] not in self.fgrep_in_directories(lists):
133 raise NameError, "not in ./words/*.txt. Look again, shall we?"
134 pass
135
136 def fgrep_in_directories(self, directories=(".",),search_string=None):
137 """ grep in dir ("." by default)
138
139 find a word in local .txt files
140 """
141 if search_string is None:
142 search_tuple = (("^{0}$".format(self.name), re.I),)
143 else:
144 search_tuple = ((search_string, re.M),)
145
146 result = map(lambda directory:
147 SU.ffindgrep(directory, namefs=(lambda s: s.endswith('.txt'),),
148 regexl=search_tuple
149 ).values(),
150 directories)
151
152 return [catch[0] for catch in result if len(catch) is not 0]
153
154 def fgrep_catches_in_directories(self, directories=(".",)):
155 """find all _catches_
156
157 find a word in local .txt files
158 """
159 temp = []
160 temp.extend(self.fgrep_in_directories(("./words",), "^{0}.$".format(self.name)))
161 temp.extend(self.fgrep_in_directories(("./words",), "^.{0}$".format(self.name)))
162 return temp
163
164# raise ArgumentError
165 @staticmethod
166 def find_all(finder_sql = ""):
167 """rails-style finder
168 """
169 cur.execute("select * from words {0}".format(finder_sql))
170 return map(lambda properties: Word(*properties), cur.fetchall())
171
172
173 def flatten(l):
174 if l is []:
175 pass
176 elif isinstance(l,list):
177 return sum(map(flatten,l))
178 else:
179 return l