-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathmarkov.py
More file actions
229 lines (178 loc) · 5.76 KB
/
Copy pathmarkov.py
File metadata and controls
229 lines (178 loc) · 5.76 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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Version: Python 3
# Author: fm4d
import random
import shelve
class ObjectLikeDbfilenameShelf():
"""
Proxy class that allows object-like manipulation with DbfilenameShelf object
"""
def __init__(self, ds):
"""
Args:
ds: DbfilenameShelf object
"""
super(ObjectLikeDbfilenameShelf, self).__setattr__('_ds', ds)
def __getattr__(self, name):
if name in self._ds:
return self._ds[name]
else:
raise AttributeError("{} object has no attribute {}".format(
self._ds.__class__.__name__, name
))
def __setattr__(self, name, value):
self._ds[name] = value
def __delattr__(self, name):
if name in self._ds:
del self._ds[name]
else:
raise AttributeError("{} object has no attribute {}".format(
self._ds.__class__.__name__, name
))
def __repr__(self):
return '\n'.join("{}: {}".format(k, v.order)
for k, v in self._ds.items())
def __iter__(self):
for key in self._ds.keys():
yield key
def close(self):
self._ds.close()
class WalkByGroup:
"""
Iterator that walks throught iterable by step_size of elements
"""
def __init__(self, iterable, step_size):
"""
Args:
iterable: iterable object (object that can be turned into iterator)
step_size: number of elements returned at once
"""
self.iterator = iter(iterable)
self.step_size = step_size
self.buffer = (None, ) + tuple(next(self.iterator) for x in range(step_size-1))
def __iter__(self):
return self
def __next__(self):
self.buffer = self.buffer[1:] + (next(self.iterator), )
return self.buffer
def parse(filename, encoding=None):
"""
!DEMO!
Simple file parsing generator
Args:
filename: absolute or relative path to file on disk
encoding: encoding string that is passed to open function
"""
with open(filename, encoding=encoding) as source:
for line in source:
for word in line.split():
yield word
class MarkovChain():
"""
Single markov chain
"""
def __init__(self, order, content=None):
self.order = order
self.content = {} if content is None else content
self._start_words = None
@property
def startwords(self):
"""
!DEMO!
Cached list of keys that can be used to generate sentence.
"""
if self._start_words is not None:
return self._start_words
else:
self._start_words = list(filter(
lambda x: str.isupper(x[0][0]) and x[0][-1] not in ['.', '?', '!'],
self.content.keys()
))
return self._start_words
def decache(self):
"""
Delete cached value of startwords
"""
self._start_words = None
class MarkovGenerator():
"""
Base object used to build chains and generate sentences.
"""
def __init__(self, shelve_file='chains_shelve'):
"""
Args:
shelve_file: path to shelve file on disk
"""
self.ds = shelve.open(shelve_file, writeback=True)
self.chains = ObjectLikeDbfilenameShelf(self.ds)
def add_chain(self, name, order):
"""
Add chain to current shelve file
Args:
name: chain name
order: markov chain order
"""
if name not in self.chains:
setattr(self.chains, name, MarkovChain(order=order))
else:
raise ValueError("Chain with this name already exists")
def remove_chain(self, name):
"""
Remove chain from current shelve file
Args:
name: chain name
"""
if name in self.chains:
delattr(self.chains, name)
else:
raise ValueError("Chain with this name not found")
def build_chain(self, source, chain):
"""
Build markov chain from source on top of existin chain
Args:
source: iterable which will be used to build chain
chain: MarkovChain in currently loaded shelve file that
will be extended by source
"""
for group in WalkByGroup(source, chain.order+1):
pre = group[:-1]
res = group[-1]
if pre not in chain.content:
chain.content[pre] = {res: 1}
else:
if res not in chain.content[pre]:
chain.content[pre][res] = 1
else:
chain.content[pre][res] += 1
chain.decache()
def generate_sentence(self, chain):
"""
!DEMO!
Demo function that shows how to generate a simple sentence starting with
uppercase letter without lenght limit.
Args:
chain: MarkovChain that will be used to generate sentence
"""
def weighted_choice(choices):
total_weight = sum(weight for val, weight in choices)
rand = random.uniform(0, total_weight)
upto = 0
for val, weight in choices:
if upto + weight >= rand:
return val
upto += weight
sentence = list(random.choice(chain.startwords))
while not sentence[-1][-1] in ['.', '?', '!']:
sentence.append(
weighted_choice(
chain.content[tuple(sentence[-2:])].items()
)
)
return ' '.join(sentence)
def close(self):
"""
Close current shelve db file
"""
self.chains.close()