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